# HG changeset patch # User dwinter # Date 1375271353 -7200 # Node ID 015d06b10d37d0dbb4ea64e22cbb5cf0c9e96b02 initial diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/Copy of digitalobjects-item.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/Copy of digitalobjects-item.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,105 @@ + + +

Hello

+ + +

> + +

+ + + +
+ +
+

Bibliographic information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
Author: +
Title: +
Date: +
+ + +

Permanent URL

+ + + + + + + + +
Document ID: +
Permanent URL:
+
+ + +

Presentation context

+ +
+ + +

Copyright information

+ + + + + +
+ + + + + + + + + + +
Copyright:Max Planck Institute for the + History of Science (unless stated otherwise)
License:CC-BY-SA (unless stated otherwise)Internal use only, please contact library@mpiwg-berlin.mpg.de + (unless stated otherwise) +
+
+
+
+ + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobject_reader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobject_reader.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,130 @@ +data)){ + return drupal_json_decode($request->data); + } + else { + return null; + } + +} + +function digitalobjects_getBibFormattedMetaData($bibdata){ + $bd = drupal_json_encode($bibdata); + + $base = variable_get('digitalobjects_bibdata_path'); + #$path="http://localhost:18080/metadata/getBibFormattedMetaDataJSON"; + $path = $base ."/getBibFormattedMetaDataJSON"; + $options = array( + + 'method' => 'POST', + 'data' => 'bibdata=' . $bd + ); + $request = drupal_http_request($path, $options); + return drupal_json_decode($request->data); + +} + +function digitalobjects_getBibFormattedLabel($bibdata){ + $bd = drupal_json_encode($bibdata); + + $base = variable_get('digitalobjects_bibdata_path'); + #$path="http://localhost:18080/metadata/getBibFormattedMetaDataJSON"; + $path = $base ."/getBibFormattedLabelJSON"; + $options = array( + + 'method' => 'POST', + 'data' => 'bibdata=' . $bd + ); + $request = drupal_http_request($path, $options); + return drupal_json_decode($request->data); + +} + + + +function digitalobjects_getBibMappedData($bibdata){ + $bd = drupal_json_encode($bibdata); + + $base = variable_get('digitalobjects_bibdata_path'); + #$path="http://localhost:18080/metadata/getBibFormattedMetaDataJSON"; + $path = $base ."/getBibMappedDataJSON"; + + $options = array( + + 'method' => 'POST', + 'data' => 'bibdata=' . $bd + ); + $request = drupal_http_request($path, $options); + return drupal_json_decode($request->data); + +} +#here.metadata.getBibFormattedMetaData(bibdata=docinfo.get('bib', None)) + + + +function digitalobjects_readMetadata($objid,$format="long"){ + + $md = digitalobject_getMetadata($objid); + + switch($format){ + case "long": + $bib = isset($md['bib']) ?digitalobjects_getBibFormattedMetaData($md['bib']): null; + break; + + case "short": + $bib = isset($md['bib']) ?digitalobjects_getBibFormattedLabel($md['bib']): null; + } + if ($bib == null) { + $bibdata = array ( + "Author" => $md['creator'], + "Title" => $md['title'], + "Date" => $md['date'] + ); + $bib=""; + foreach ($bibdata as $key => $content){ + $bib = $bib . '' . $key . ' + ' . $content . ''; + } + + } + + $access_type = isset($md['accessType']) ? $md['accessType'] : 'mpiwg'; + $titlepage = isset($md['titlePage']) ? $md['titlePage'] : 1; + $data = array( + "thumburl" => create_thumburl_from_dri($objid,$titlepage=$titlepage), + "viewerurl" => create_url_from_dri($objid,$access_type = $access_type), + "md" => $md, + "bibdata" => $bib, + "access_type" => $access_type, + ); + + return $data; +} + + +function create_url_from_dri($dri,$access_type = "closed"){ + + if ($access_type=="free"){ + return "http://echo.mpiwg-berlin.mpg.de/".$dri; + } + else + return "http://libcoll.mpiwg-berlin.mpg.de/".$dri; +} + +function create_thumburl_from_dri($dri,$titlepage=1){ + return "http://md.mpiwg-berlin.mpg.de/purls/image/".$dri."?pn=" . $titlepage; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-current-collection.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-current-collection.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,2 @@ +

Current collection

+

title,"node/".$node->nid) ?>

diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-item-short.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-item-short.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,26 @@ + + + + +
+ + + + + +
+ + +intern + + + + + + +
+
+ +

: restricted - do to copyright reasons

+ + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-item-tools.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-item-tools.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,17 @@ +
+ + + + + + + + + + + + + + + +
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-item-usage.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-item-usage.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,12 @@ +

Collections where the object is used

+ + $title):?> + + + + + + + + +
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-item.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-item.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,40 @@ + + + + +
+ + + +intern + + + + + +
+
+ + + + + + + + + + + +
Document ID:
Permanent URL:
+ +
+ + + +
+ +

: restricted - do to copyright reasons

+ + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects-user-admin-form.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects-user-admin-form.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,7 @@ + +

+
+ +
+
+
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,35 @@ + 'textfield', + '#title' => t('bibdataprovider URL'), + '#default_value' => variable_get('digitalobjects_bibdata_path',"http://localhost:18080/metadata"), + '#maxlength' => 255, + '#description' => t('URL to the bibdataprovider (no "/" at the end!)'), + + ); + + $form['digitalobjects_docuviewer_path'] = array( + '#type' => 'textfield', + '#title' => t('docuviewer URL'), + '#default_value' => variable_get('digitalobjects_docuviewer_path',"http://localhost:18080/ECHOdocuView"), + '#maxlength' => 255, + '#description' => t('URL to the docuviewer (no "/" at the end!)'), + + ); + + return system_settings_form($form); + +} + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.collections.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.collections.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,239 @@ +field_digitalobjects_cid['und'][0]['objid']; + + $form['digitalobjects_user_admin_form']['digitalcollection']['#default_value']=""; + $form['digitalobjects_user_admin_form']['digitalcollection']['#value']=$val; + $form['digitalobjects_user_admin_form']['userid']['#value']=$key; + + //$form['digitalcollection_chooser'] = drupal_get_form("digitalcollection_chooser"); + + + # $element = $form; + + $form['chooser']=drupal_get_form("digitalcollection_chooser"); + + $form['chooser']['pager'] = array('#markup' => theme('pager')); + $form['chooser']['userid']['#value']=$key; + + #$element += $form3; + + # + #$element += $form2; + + return $form; +} + +function digitalcollection_chooser(array $form){ + // Build the sortable table header. + $header = array( + 'title' => array('data' => t('Title'), 'field' => 'n.title'), + 'nid' => array('data' => t('nid'), 'field' => 'n.nid') + ); + + + #$result = db_query("SELECT nid FROM node WHERE type = :ntype ", array ('ntype' => 'digitalcollection')); + $result = db_query("SELECT n.nid FROM {node} n "); + + $nids = array(); + foreach ($result as $record) { + $nids[] = $record->nid; + } + //Get the node data. + //$nids = $query + //->fields('n',array('nid')) + //->limit(50) + //->orderByHeader($header) + //->execute() + //->fetchCol(); + + + $nodes = node_load_multiple($nids); + + //Build the rows. + $options = array(); + $l_options=array(""); + $destination = drupal_get_destination(); + $languages = language_list(); + foreach ($nodes as $node) { + $langcode = entity_language('node', $node); + $l_options = $langcode != LANGUAGE_NONE && isset($languages[$langcode]) ? array('language' => $languages[$langcode]) : array(); + + $options[$node->nid] = array( + 'title' => array( + 'data' => array( + '#type' => 'link', + '#title' => $node->title, + '#href' => 'node/' . $node->nid, + '#options' => $l_options, + '#suffix' => ' ' , + ), + ), + 'nid' => $node->nid, + ); + + } + + $destination = drupal_get_destination(); + + /* + $operations = array(); + if (node_access('update', $node)) { + $operations['edit'] = array( + 'title' => t('edit'), + 'href' => 'node/' . $node->nid . '/edit', + 'query' => $destination, + ); + } + if (node_access('delete', $node)) { + $operations['delete'] = array( + 'title' => t('delete'), + 'href' => 'node/' . $node->nid . '/delete', + 'query' => $destination, + ); + } + $options[$node->nid]['operations'] = array(); + $options[$node->nid]['operations'] = array( + 'data' => array( + '#theme' => 'links__node_operations', + '#links' => $operations, + '#attributes' => array('class' => array('links', 'inline')), + ), + ); + + + */ + + + + + $form['digitalcollection_chooser'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#empty' => t('No content available.'), + '#attributes' => array('class' => array('search-form', 'ctools-auto-submit-full-form')), + '#multiple' => False, + + ); + + #form['chooser']['change'] = array('#type' => 'actions'); + $form+= $form['submit2'] = array('#type' => 'submit', '#value' => t('Choose')); + #$form['chooser']['#submit'][] = 'digitalobjects_user_change_form_submit'; + $form['submit2']['#submit'][] = 'digitalobjects_user_change_form_submit'; + #$form['chooser']['#attributes']= array('class' => array('ctools-use-ajax', 'ctools-auto-submit-click')); + + $form['userid'] = array( + '#type' => 'hidden', + ); + + return $form; +} + + +function digitalobjects_user_change_form_submit($form,$formstate){ + $input = $formstate['input']['digitalcollection_chooser']; + $userid = $formstate['values']['userid']; + + $user=user_load($userid); + + + $user->field_digitalobjects_cid['und'][0]['objid'] = $input; + + user_save($user); + +} +#füge objid zur aktuelle sammlung hinzu +function digitalobjects_addToCurrentCollection($objid){ + global $user; + #hole current collection + $user_full = user_load($user->uid); + $val = $user_full->field_digitalobjects_cid['und'][0]['objid']; + + #hole sammlung + + $node = node_load($val); + + foreach ( $node->field_objid['und'] as $obj){ + if ($obj['objid']==$objid){ + return drupal_set_message(t("Already in the current collection"),"error"); + } + + } + + $node->field_objid['und'][]['objid']=$objid; + + $node = node_submit($node); + + node_save($node); + + drupal_goto("node/".$node->nid); +} + + + +function digitalobjects_digitalcollectionsManage(){ + + $data = $_GET; + + + global $user; + #hole current collection + $user_full = user_load($user->uid); + $val = $user_full->field_digitalobjects_cid['und'][0]['objid']; + + #hole sammlung + + $node = node_load($val); + + foreach ($data['digitalobjects_items_select'] as $objid){ + /*foreach ( $node->field_objid['und'] as $obj){ + if ($obj['objid']==$objid){ + return drupal_set_message(t("Already in the current collection"),"error"); + } + + } + */ + $node->field_objid['und'][]['objid']=$objid; + + } + + + $node = node_submit($node); + + node_save($node); + + if (isset($data['redirect'])){ + + drupal_goto(urldecode($data['redirect'])); + } else { + drupal_goto("node/".$node->nid); + } + +/* + $output['digitalobjects_item']= array( + '#theme' => 'digitalobjects_item', + '#objid' => ''); + return $output; + */ +} + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,23 @@ +/* +Cascading Style Sheets, level 2 revision 1. +CSS 2.1 Specification is available at +http://www.w3.org/TR/2006/WD-CSS21-20061106/ +*/ + +td.digitalobjects_item_image { +vertical-align:top; +} + +div.digitalobjects_item { + padding-top: 10px; + padding-bottom: 10px; +} + +table.digitalobjects_item_short td{ + vertical-align:top; +} + + +div.digitalobjects_item_tools{ + display:inline; +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.import.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.import.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,159 @@ + $page){ + + $page = base64_decode($page); + digitalobjects_importPage($page,$key); + $cnt++; + + } + + +} + + +function digitalobjects_importPage($page,$key){ + + $baseUrl = "http://localhost:18080/echo_nav/echo_pages/content/scientific_revolution/harriot/maps/"; + + $fnames = explode("/",$key); + + if ($fnames[1] != "maps"){ + dpm($fnames[0]); + return; + } + + $fname = str_replace(".pt","1_1.png",$fnames[sizeof($fnames)-1]); + + $image = file_get_contents($baseUrl . $fname); // string + $file = file_save_data($image, 'public://' . $fname,FILE_EXISTS_REPLACE); + + + + + $node = new stdClass(); + $node->type = 'map_page'; + node_object_prepare($node); + + $node->body['und'][0]['value'] = check_markup($page,'full_html'); + + $node->body['und'][0]['format'] = 'full_html'; + + $node->path['alias']="harriot/maps/" . $fnames[sizeof($fnames)-1]; + + $node->title = "_".$key; + + + $node->language = LANGUAGE_NONE; + + + $node->field_echopath['und'][0]['value']=$key; + + $node->field_image[LANGUAGE_NONE]['0']['fid'] = $file->fid; + $node = node_submit($node); + + + + + /*$node->field_admintag['und'][0] = array ( + 'tid' => $tid, + + );*/ + // Try to set your custom field + + $node = node_submit($node); + + node_save($node); + + } + + + +function digitalobjects_importCollectionFromFile() { + + + $filename = "/tmp/china.json"; + $handle = fopen($filename, "r"); + $contents = fread($handle, filesize($filename)); + fclose($handle); + + $collection = drupal_json_decode($contents); + digitalobjects_importCollection($collection); +} + +function digitalobjects_importCollection($collection,$tid){ + + $node = new stdClass(); + $node->type = 'digitalcollection'; + node_object_prepare($node); + + $node->body['und'][0]['value'] = check_markup($collection['description'],'full_html'); + + $node->body['und'][0]['format'] = 'full_html'; + + + $node->title = $collection['title']; + + + $node->language = LANGUAGE_NONE; + + + $node->field_echopath['und'][0]['value']=$collection['echo_path']; + $node->field_label['und'][0]['value']=$collection['label']; + $node = node_submit($node); + + + $cnt=0; + foreach ($collection['content'] as $cont){ + print $cont; + if (($cont != "") && ($cont !=null)){ + $node->field_objid['und'][$cnt]['objid']=$cont; + $cnt++; + + + } + } + + $node->field_admintag['und'][0] = array ( + 'tid' => $tid, + + ); + // Try to set your custom field + + $node = node_submit($node); + + node_save($node); + +} + +function digitalobjects_importCollections($tid) { + $filename = "/tmp/export.json"; + # $filename = "/tmp/echo.json"; + $handle = fopen($filename, "r"); + $contents = fread($handle, filesize($filename)); + fclose($handle); + + $collections = drupal_json_decode($contents); + + $cnt=0; + foreach ($collections as $key => $collection){ + digitalobjects_importCollection($collection,$tid); + $cnt++; + + } + +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,18 @@ +name = digitalobjects +description = Allows access to the digital library +core = 7.x + + +configure = admin/config/media/digitalobjects/settings + +files[] = +files[] = digitalobjects.module +files[] = digitalobjects.install +files[] = digitalobjects.item.inc +files[] = digitalobjects.admin.inc +files[] = digitalobjects-item.tpl.php +files[] = digitalobject_reader.php +files[] = digitalobjects.collections.inc + + +stylesheets[all][] = digitalobjects.css \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,35 @@ + array('type' => 'varchar', 'length' => 20, 'not null' => FALSE), + ); + $indexes = array( + 'objid' => array('objid'), + ); + return array( + 'columns' => $columns, + 'indexes' => $indexes, + ); +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.item.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/digitalobjects/digitalobjects.item.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,42 @@ + 'field_digitalobjects_cid', + 'type' => 'digitalobjects_digitalcollection', + ); + field_create_field($field); + + // Create the instance on the bundle. + $instance = array( + 'field_name' => 'field_digitalobjects_cid', + 'entity_type' => 'user', + 'label' => 'Current collection', + 'bundle' => 'user', + // If you don't set the "required" property then the field wont be required by default. + 'required' => False, + 'settings' => array( + // Here you inform either or not you want this field showing up on the registration form. + 'user_register_form' => 1, + ), + 'widget' => array( + 'type' => 'digitalcollection_default_textfield', + ), + ); + field_create_instance($instance); + } +} + +function digitalobjects_menu(){ + + $items['digitalcollections/manageCurrent'] = array( + 'description' => 'show an object.', + 'page callback' => 'digitalobjects_digitalcollectionsManage', + 'access arguments' => array('access content'), + 'file' => 'digitalobjects.collections.inc', + + ); + + + $items['digitalobject'] = array( + 'description' => 'show an object.', + 'page callback' => 'digitalobjects_page', + 'access arguments' => array('access content'), + + ); + $items['admin/config/media/digitalobjects'] = array( + 'title' => 'Digitalobject', + 'description' => 'Set path to the metadaprovider for digitalobjects', + 'weight' => 10, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('digitalobjects_settings'), + 'access arguments' => array('administrate digitalobjects'), + 'file' => 'digitalobjects.admin.inc', + + ); + + + + $items['user/%/collections'] = array( + 'title' => 'Collections', + 'description' => 'Set path to the metadaprovider for digitalobjects', + 'weight' => 10, + 'page callback' => 'digitalobjects_user_admin_collection', + 'page arguments' => array(1), + 'access arguments' => array('manage private collections'), + 'file' => 'digitalobjects.collections.inc', + 'type' => MENU_LOCAL_TASK, + ); + + + + $items['digitalobject/%/view'] = array( + 'title' => 'View', + 'description' => 'show an object.', + 'page callback' => 'digitalobjects_page', + 'access arguments' => array('access content'), + 'page arguments' => array(1), + + 'access arguments' => array('access content'), + 'type' => MENU_LOCAL_TASK, + + ); + + $items['digitalobject/%/edit'] = array( + 'title' => 'Edit', + 'description' => 'Set path to the metadaprovider for digitalobjects', + 'weight' => 10, + 'page callback' => 'digitalobjects_edit', + 'page arguments' => array(1), + + 'access arguments' => array('administrate digitalobjects'), + 'type' => MENU_LOCAL_TASK, + + ); + + $items['digitalobject/%/usage'] = array( + 'title' => 'Usage', + 'description' => 'Set path to the metadaprovider for digitalobjects', + 'weight' => 10, + 'page callback' => 'digitalobjects_show_usage', + 'page arguments' => array(1), + + 'access arguments' => array('access content'), + 'type' => MENU_LOCAL_TASK, + + ); + + $items['digitalobject/%/add'] = array( + 'title' => 'Add', + 'description' => 'Add an object to the current collection', + 'weight' => 10, + 'page callback' => 'digitalobjects_addToCurrentCollection', + 'page arguments' => array(1), + + 'access arguments' => array('administrate digitalobjects'), + 'file' => 'digitalobjects.collections.inc', + 'type' => MENU_LOCAL_TASK, + + ); + return $items; + +} + + +function digitalobjects_create_primary_node($objid){ +$node = new stdClass(); +$node->type = 'digitalobject'; +node_object_prepare($node); +global $user; +#dpm($user); +#$node->uid=$user->uid; +$node->field_single_objid['und'][0]['objid']=$objid; + +$node->title = $objid; + +$node->language = LANGUAGE_NONE; +#$node = node_submit($node); + +#$node->field_object_type['und'][0]=array('tid'=> '6'); + +$foo = taxonomy_get_term_by_name('primary'); +foreach($foo as $term) { + + if(($term->vocabulary_machine_name) == 'digitalobject_types') { + + $node->field_object_type['und'][]['tid'] = $term->tid; + } +} +$node = node_submit($node); + +node_save($node); + +return $node; +} + +function digitalobjects_edit($objid){ + #suche ob es schon ein locales object zu dieser id gibt. + $query = new EntityFieldQuery(); + $entities = $query->entityCondition('entity_type', 'node') + ->entityCondition('bundle', 'digitalobject') + ->fieldCondition('field_single_objid', 'objid', $objid,"=") + ->execute(); + + if ($entities == null) #existiert noch nicht, dann anlegen + { + $node =digitalobjects_create_primary_node($objid); + #$node->field_object_type['und'][0]=array('tid'=> 'autocreate', + #'name' => "primary", + #'vocabulary_machine_name' => 'digitialobject_types'); + + } else { + + + $nodes = node_load_multiple(array_keys($entities['node'])); + + #gibt es eins, dann gib dieses zum editieren zurück + #TODO falls mehrere existieren, was dann?? + + #gehe durch alle gefundene + foreach ($nodes as $node){ + #$typeTag = $entity->object_type['und'] + + if (isset($node->field_object_type['und'])){ + $tid = $node->field_object_type['und'][0]['tid']; + $tax =taxonomy_term_load($tid); + if ($tax->name == "primary"){ #'primary gefunden' + module_load_include('inc', 'node', 'node.pages'); + return drupal_get_form('digitalobject_node_form', $node); + }} + } # keine primary dann lege diese an: + + $node =digitalobjects_create_primary_node($objid); + + #$vals = array_values($nodes); + #$node= array_shift($vals); + } + + $node = node_submit($node); + + #node_save($node); + module_load_include('inc', 'node', 'node.pages'); + + return drupal_get_form('digitalobject_node_form', $node); + +} + + +function digitalobjects_show_usage($objid){ + $query = new EntityFieldQuery(); + $entities = $query->entityCondition('entity_type', 'node') + ->fieldCondition('field_objid', 'objid', $objid,"=") + ->execute(); + + + $data = array(); + + #treffer gefunden + if (isset($entities['node'])){ + + $nodes = node_load_multiple(array_keys($entities['node'])); + + + foreach ($nodes as $node){ + $data[$node->nid] = $node->title; + + } + + + $output['digitalobjects_item_usage']= array( + '#theme' => 'digitalobjects_item_usage', + '#data' => $data); + + return $output; + } + + return "

object not used in a collection

"; + } + + +function digitalobjects_page($keys = ''){ + + + #suche ob es schon ein locales object zu dieser id gibt. + $query = new EntityFieldQuery(); + $entities = $query->entityCondition('entity_type', 'node') + ->entityCondition('bundle', 'digitalobject') + ->fieldCondition('field_single_objid', 'objid', $keys,"=") + ->execute(); + + if ($entities == null) #existiert noch nicht, dann generische Anzeige + { + + $output['digitalobjects_item']= array( + '#theme' => 'digitalobjects_item', + '#objid' => $keys); + + return $output; + } else { + $nodes = node_load_multiple(array_keys($entities['node'])); + + #gibt es eins, dann gib dieses zum editieren zurück + #TODO falls mehrere existieren, was dann?? + + #gehe durch alle gefundene + foreach ($nodes as $node){ + if (isset($node->field_object_type['und'])){ + $tid = $node->field_object_type['und'][0]['tid']; + $tax =taxonomy_term_load($tid); + + if ($tax->name == "primary"){ #'primary gefunden' + return node_view($node, $view_mode = 'full'); + } + } + } + #keine primary node gefunden, generische ausgabe + $output['digitalobjects_item']= array( + '#theme' => 'digitalobjects_item', + '#objid' => $keys); + return $output; + } +} + +/* implements digitalobject_theme */ + +function digitalobjects_theme(){ + + return array( + 'digitalobjects_item' => array( + 'variables' => array('objid' => NULL), + 'file' => 'digitalobjects.item.inc', + 'template' => 'digitalobjects-item', + ), + 'digitalobjects_item_short' => array( + 'variables' => array('objid' => NULL), + 'file' => 'digitalobjects.item.inc', + 'template' => 'digitalobjects-item-short', + ), + 'digitalobjects_item_tools' => array( + 'variables' => array('objid' => NULL), + 'template' => 'digitalobjects-item-tools', + ), + 'digitalobjects_item_usage' => array( + 'variables' => array('data' => NULL), + 'template' => 'digitalobjects-item-usage', + ), + + 'digitalobjects_currentCollection_block' => array( + 'variables' => array('node' => NULL), + 'template' => 'digitalobjects-current-collection', + ), + /* 'digitalobjects_user_admin_page' => array( + 'variables' => array('digitalobjects_user_admin','uid' =>1), + 'template' => 'digitalobjects-user-admin-form' + ),*/ + ); +} + +function digitalobjects_forms($form_id, $args) { + + $forms['digitalobjects_user_admin_form']= array( + 'callback' => 'digitalobjects_user_admin', + 'callback arguments' => array('digitalobjects_user_admin_page'), + ); + return $forms; +}; + + + + +function digitalobjects_user_admin(array $form, array &$form_state) { + + $form['digitalcollection'] = array( + '#type' => 'textfield', + '#title' => t('Active collection'), + '#size' => 15, + '#default_value' => 'xxxx', + '#attributes' => array('title' => t('Enter the terms id of the current collection.')), + ); + + + $form['userid'] = array( + '#type' => 'hidden', + ); + #$form['actions'] = array('#type' => 'actions'); + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['submit']['#submit'][] = 'digitalobjects_user_admin_form_submit'; + + return $form; +} +/* implements a new field type for the digitalobject id */ + +function digitalobjects_field_info(){ + + return array('digitalobjects_digitalobject' => array ( + 'label' => t('Digital Object ID'), + 'description' => t('This field stores a digital object ID'), + 'settings' => array('max_length' =>20), + 'instance_settings' => array('text_processing' => 0), + 'default_widget' => 'digitalobject_default_textfield', + 'default_formatter' => 'digitalobjects_fullmetadata', + ), + 'digitalobjects_digitalcollection' => array ( + 'label' => t('Digital Collection ID'), + 'description' => t('This field stores a digital object ID'), + 'settings' => array('max_length' =>20), + 'instance_settings' => array('text_processing' => 0), + 'default_widget' => 'digitalcollection_default_textfield', + 'default_formatter' => 'digitalcollection_fullmetadata', + ), + + ); +} + +function digitalobjects_field_formatter_info() { + + return array( + // This formatter shows the obejct with fullmetadata + 'digitalobjects_fullmetadata' => array( + 'label' => t('Fullmetadata formatter'), + 'field types' => array('digitalobjects_digitalobject'), + ), + 'digitalobjects_label' => array( + 'label' => t('Lable formatter'), + 'field types' => array('digitalobjects_digitalobject'), + ) + ); + } + + + + + + + function digitalobjects_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + + + switch ($display['type']) { + // This formatter simply outputs the field as text and with a color. + case 'digitalobjects_fullmetadata': + foreach ($items as $delta => $item) { + if ($item['objid']!=""){ + $element[$delta]['#markup'] = theme('digitalobjects_item_tools', array("objid" => $item['objid'])); + $element[$delta]['#markup'] .= theme('digitalobjects_item', array("objid" => $item['objid'])); + + } + } + break; + + case 'digitalobjects_label': + foreach ($items as $delta => $item) { + if ($item['objid']!=""){ + + $element[$delta]['#markup'] = theme('digitalobjects_item_short', array("objid" => $item['objid'])); + $element[$delta]['#markup'] .= theme('digitalobjects_item_tools', array("objid" => $item['objid'])); + } + } + break; + } + return $element; + } + + +function digitalobjects_field_widget_info() { + #TODO:both types should check if field is valid + return array( + 'digitalobject_default_textfield' => array( + 'label' => t('digitalobject id'), + 'field types' => array('digitalobjects_digitalobject'), + ), + 'digitalcollection_default_textfield' => array( + 'label' => t('digitalobject id'), + 'field types' => array('digitalobjects_digitalcollection'), + ) + ); + } + + function digitalobjects_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + + + $field_name = $field['field_name']; + $field_type = $field['type']; + + + $value = isset($items[$delta]['objid']) ? $items[$delta]['objid'] : ''; + + $widget = $element; + $widget['#delta'] = $delta; + + switch ($instance['widget']['type']) { + + + + case 'digitalobject_default_textfield': + $widget += array( + '#type' => 'textfield', + '#default_value' => $value, + '#size' => 20, + '#maxlength' => 20, + ); + $element['objid'] = $widget; + break; + + case 'digitalcollection_default_textfield': + $widget += array( + '#type' => 'textfield', + '#default_value' => $value, + '#size' => 20, + '#maxlength' => 20, + ); + break; + + + } + + + + + return $element; + } + + + function digitalobjects_field_is_empty($item, $field){ + return empty($item['objid']); + } + + + +function digitalobjects_user_admin_form_submit($form,$formstate){ + $value = $formstate['values']['digitalcollection']; + $userid = $formstate['values']['userid']; + + $user=user_load($userid); + + + $user->field_digitalobjects_cid['und'][0]['objid'] = $value; + + user_save($user); + +} + +function digitalobjects_block_info(){ + $blocks['digitalobjects_currentCollection'] = array( + 'info' => t('Current Collection'), + 'visibility' => BLOCK_VISIBILITY_PHP, + 'pages' => 'uid == 0) return FALSE; else return TRUE; ?>' + ); + return $blocks; +} + +function digitalobjects_block_view($delta){ + + switch($delta){ + + case 'digitalobjects_currentCollection': + /* display current cullection */ + + global $user; + + $user_full=user_load($user->uid); + + if (isset($user_full->field_digitalobjects_cid['und'][0]['objid'])){ + $val = $user_full->field_digitalobjects_cid['und'][0]['objid']; + } else { + return; + } + $node = node_load($val); + + #$title = $node=>title; + + $block['content'] = theme('digitalobjects_currentCollection_block',array('node' => $node)); + + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['submit']['#submit'][] = 'digitalobjects_user_admin_collection'; + + $submitForm=drupal_get_form('digitalobjects_change_collection_button_form'); + $block['content'] .= drupal_render($submitForm); + } + + + return $block; +} + +function digitalobjects_change_collection_button_form(){ + include_once 'digitalobjects.collections.inc'; + global $user; + $form['userid'] = array( + + '#type' => 'hidden', + '#value' => $user->uid + ); + + $form['submit'] = array('#type' => 'submit', '#value' => t('change')); + $form['submit']['#submit'][] = 'digitalobjects_user_admin_collection_submit'; + + return $form; +} + +function digitalobjects_permission() { + return array( + 'manage private collections' => array( + 'title' => t('Manage private collections'), + 'description' => t('Allow users create and manage private collections.'), + ), + 'administrate digitalobjects' => array( + 'title' => t('Administrate digitalobjects'), + 'description' => t('Can add commentaries to digital objects.'), + ), + ); +} + + +function digitalobjects_pathologic_alter(&$url_params, $parts, $settings){ +#bilder ohne jegliche pfad angaben werden auf sites/default/files/.. + if (preg_match('~^([^/]*)\.(png|gif|jpe?g)$~', $url_params['path'])){ + + $url_params['path'] = 'sites/default/files/' . $url_params['path']; + } + + if (preg_match('~^./([^/]*)\.pt$~', $url_params['path'],$matches)){ + + $url_params['path'] = 'harriot/maps/' . $matches[1] . '.pt'; + } + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/digitalobjects/digitalobjects.user.inc diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/mediathek/mediathek-theme-mediathek-mediaviewer.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/mediathek/mediathek-theme-mediathek-mediaviewer.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,12 @@ + + +
+
+
+ +
+
+
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/mediathek/mediathek.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/mediathek/mediathek.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,7 @@ +name = mediathek +description = Allows access to the mediathek +core = 7.x + +files[] = mediathek.module + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/mediathek/mediathek.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/mediathek/mediathek.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,38 @@ + array('type' => 'varchar', 'length' => 600, 'not null' => FALSE), + 'width' => array('type' => 'varchar', 'length' => 10, 'not null' => FALSE), + 'height' => array('type' => 'varchar', 'length' => 10, 'not null' => FALSE), + ); + $indexes = array( + 'url' => array('url'), + ); + return array( + 'columns' => $columns, + 'indexes' => $indexes, + ); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/mediathek/mediathek.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/mediathek/mediathek.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,198 @@ + 'field_mediathek_entry', + 'type' => 'mediathek_mediathek_entry', + ); + field_create_field($field); + + // Create the instance on the bundle. + $instance = array( + 'field_name' => 'field_mediathek_entry', + 'entity_type' => 'node', + 'label' => 'Mediathek Entry', + 'bundle' => 'node', + // If you don't set the "required" property then the field wont be required by default. + 'required' => False, + 'settings' => array( + ), + 'widget' => array( + 'type' => 'mediathek_default_mediathek_entry', + ), + ); + field_create_instance($instance); + } +} + +/* implements a new field type for the medithek id */ + +function mediathek_field_info(){ + + return array('mediathek_mediathek_entry' => array ( + 'label' => t('Mediathek Item'), + 'description' => t('This field stores an mediathek Item'), + 'settings' => array('max_length' =>20), + 'instance_settings' => array('text_processing' => 0), + 'default_widget' => 'mediathek_default_mediathek', + 'default_formatter' => 'mediathek_mediaviewer', + ), + + ); +} + +function mediathek_field_is_empty($item, $field){ + return empty($item['url']); +} + +function mediathek_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + + + $field_name = $field['field_name']; + $field_type = $field['type']; + + + $url = isset($items[$delta]['url']) ? $items[$delta]['url'] : ''; + $height = isset($items[$delta]['url']) ? $items[$delta]['height'] : ''; + $width = isset($items[$delta]['url']) ? $items[$delta]['width'] : ''; + + $widget = $element; + $widget['#delta'] = $delta; + + switch ($instance['widget']['type']) { + + + + case 'mediathek_default_mediathek': + + $fieldset_info = element_info('fieldset'); + $process = array_merge($fieldset_info['#process'], array('mediathek_media_entry_process')); + + + $widget+= + array( + '#type' => 'fieldset', + '#title' => t('Contact settings'), + '#weight' => 5, + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#tree' => TRUE, + '#process' => $process, + ); + + + $widget['url'] = array( + '#title' => t('Url'), + '#type' => 'textfield', + '#default_value' => $url, + '#size' => 150, + '#maxlength' => 500, + ); + + $widget['height'] = array( + '#title' => t('Height'), + '#type' => 'textfield', + '#default_value' => $height, + '#size' => 20, + '#maxlength' => 20, + ); + + + + $widget['width'] = array( + '#title' => t('Width'), + '#type' => 'textfield', + '#default_value' => $width, + '#size' => 20, + '#maxlength' => 20, + ); + + + + + + $element['media'] = $widget; + break; + + + + } + + + + + return $element; +} + +function mediathek_media_entry_process(&$form, &$form_state, $complete){ + + + array_pop($form['#parents']); + return $form; +} + +function mediathek_field_widget_info() { + #TODO:both types should check if field is valid + return array( + 'mediathek_default_mediathek' => array( + 'label' => t('Media Item'), + 'field types' => array('mediathek_mediathek_entry'), + ), + + ); +} + + +function mediathek_field_formatter_info() { + + return array( + // This formatter shows the obejct with fullmetadata + 'mediathek_mediaviewer' => array( + 'label' => t('Mediathek viewer'), + 'field types' => array('mediathek_mediathek_entry'), + ) + ); +} + + +function mediathek_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + + + switch ($display['type']) { + // This formatter simply outputs the field as text and with a color. + case 'mediathek_mediaviewer': + + foreach ($items as $delta => $item) { + + $url = $item['url']; + $width = $item['width']; + $height= $item['height']; + + if ($url!=""){ + $element[$delta]['#markup'] = theme('mediathek_theme_mediathek_mediaviewer',array('item' => $item )); + } + + } + + break; + } + + return $element; +} + +function mediathek_theme(){ +return array( + 'mediathek_theme_mediathek_mediaviewer' => array( + 'variables' => array('item' => NULL), + 'template' => 'mediathek-theme-mediathek-mediaviewer', + ) +); + +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/CHANGELOG.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/CHANGELOG.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,820 @@ +Apache Solr integration 7.x-1.x, xxxx-xx-xx +----------------------------- +#1249616 by agentrickard, pwolanin, Nick_vh | R.Muilwijk: Fixed Apachesolr access makes assumptions that don't apply to modules like Domain Access. +#1926896 by mkalkbrenner: Fixed missing highlighted snippets. +#1927032 by mkalkbrenner: Fixed Avoid spaces between words and punctution marks in search result snippets. +#1915614 by mkalkbrenner, Nick_vh: Added Complete admin settings for non-default environments. +#1918012 by mkalkbrenner, Nick_vh, pwolanin: Added Solr query needs to be aware of more context such as Search Page ID. +#1896470 by Nick_vh, mkalkbrenner: Fixed Integrate Apache Solr Common Configurations for Solr 3.x and 4.x (Highlighting broken). +#1918030 by mkalkbrenner, pwolanin: Allow contrib modules to add custom settings to search pages. +#1918566 by mkalkbrenner: Added Devel integration: debug index documents on all environments / indexes. +#1915418 by mkalkbrenner, Nick_vh: Fixed Unable to index a node bundle in a dedicated index / environment. +#1900036 by remimikalsen, swentel | albert78: Fixed node reference with multiple values. +#1900006 by Nick_vh | msellers: Fixed Undefined variable causes warning on node type uninstall. +#1874420 by Nick_vh, j0rd | dirtabulous: Fixed Solr4 Entites Not Being Removed with deleteByQuery. +#1871866 by Nick_vh, grom358: Fixed Type bias for all entities. +#1861544 by fizk: Fixed Incorrect use of $field_map() in SolrBaseQuery::getSolrsortUrlQuery. +#1897556 by Nick_vh: Update documentation for all the functions and make sure it reflects reality. +#1836262 by ianmthomasuk, Nick_vh: Added Allow indexing of a set number of items using drush. +#1898166 by Nick_vh: Fixed Make better use of the entity cache. +#1868870 by cpliakas | Nick_vh: Revert changes to interface cause fatal errors for modules that implement the interface. +#1803512 by David_Rothstein | amanire: Fixed The OR operator returns no results for facets with colons in the value (such as entity reference facets). +#1679392 by David_Rothstein, brianV: Move logic from apachesolr_default_node_facet_info() into a separate function so other modules can use it for non-node entities. +#1814080 by Nick_vh, JordanMagnuson: Fixed Apachesolr sort block fills cache_block() table without stopping. +#1550964 by Nick_vh, drzraf, rajivk, rjbrown99, j0rd | tinflute: Support Solr 4.0 schema. +#1840430 by Nick_vh: Fixed add alter callbacks from facetapi to the mappings array. +#1827320 by drumm: Fixed Upgrading from 6.x-3.x does not need apachesolr_update_7000-15. +#1828040 by david.gil, Nick_vh: Fixed Cannot access empty property in query_type_numeric_range().inc on line 77. +#1828014 by DeFr, Nick_vh: Fixed Mass re-indexation can miss (a lot of) content. +#1823590 by Nick_vh, Josh Waihi: Added Tag Apachesolr index table select queries to allow other modules to alter them. +#1825840 by firebird: Fixed Typo in menu description. + +Apache Solr integration 7.x-1.1, 2012-10-15 +----------------------------- +#1780200 by pwolanin: document basic auth in README. +#1226274 by pwolanin: Fixes for schema.xml version numbering and organization. + +Apache Solr integration 7.x-1.0, 2012-10-13 +----------------------------- +#761990 by pwolanin, jhedstrom, Nick_vh, jpmckinney | morningtime: Fixed 400 Bad Status if URL length limit exceeded. +#1811364 by Nick_vh: Fixed Add newly created content type to the indexed bundles for all environments. +#1811456 by rupl: Fixed Improve documentation of addFilter in apachesolr.api.php. +#1773506 by drzraf: Fixed drush solr-search notice when file entity are part of the result. +#1688150 by HalfChem, cam8001, jhedstrom, Nick_vh: Fixed Search query string gets double encoded when core Search block form is submitted. +#1794602 by pwolanin, Nick_vh | ppmr: Fixed Undefined index: path in ApacheSolrDocument->__get() (line 304 of ...\apachesolr\Apache_Solr_Document. +#1773506 by drzraf: Fixed drush solr-search notice when file entity are part of the result. +#1764334 by cpliakas: Fixed Negative percentage of documents sent to the server for indexing. +#1806300 by Nick_vh: Separate out the creation of the document from the index process. +#1807552 by Nick_vh, wimvds: Fixed Site and url wrong when indexing multilingual content (using i18n module). +#1759190 by mkalkbrenner: Fixed obsolete form code?. +#1802288 by Nick_vh: Fixed Improve testing of node deletion. + +Apache Solr integration 7.x-1.0-rc5, 2012-10-01 +----------------------------- +#1698050 by duellj: fix for apachesolr_clean_text() should strip extra spaces. +#1764450 by cpliakas: Improve the docblocks in apachesolr.index.inc. +#1800324 by pwolanin: Clean up and align node access tests. +#1799330 by pwolanin: fix for Stats table missing t() calls. +#1787370 by pwolanin: Don't complain about schema version if it doesn't match expected naming pattern. +#1790590 by pwolanin: Fix DrupalSolrOnlineWebTestCase so it works with any multicore setup. +#1799032 by pwolanin: Remove uneeded object reference in indexing function. +#1793610 by zengenuity: Fix for regession - spelling suggestions missing. + +Apache Solr integration 7.x-1.0-rc4, 2012-09-21 +----------------------------- +#1790894 by pwolanin: fix for Cloning an environment doesn't clone the bundles to be indexed. +#1778050 by Nick_vh, pwolanin: fix for stale cache when CTools is already enabled. +#1778266 by Nick_vh, mkalkbrenner: Refactoring of DrupalSolrOnlineWebTestCase to be used by Others. +#1778266 by mkalkbrenner: Refactoring of DrupalSolrOnlineWebTestCase to be used by Others. +#1789526 by pwolanin: Clarify lack of upgrade path from 6.x-1.x. +#1765938 by cpliakas: Added Move the variable_get() for 'apachesolr_environments()" after the cache_set() so that URLs can be modified dynamically. +#1782436 by cspitzlay: Fixed Error and obsolete hint in code comment. +#1732900 by cpliakas: Added Change the wording of the generic 'Apache Solr server unavailable" error message. +#1781040 by mkalkbrenner: Fixed Switch 'Enable spell check" does not work - spell check is allways on. +#1786450 by Nick_vh: Fixed apachesolr.interface.inc is not always loaded. +#1760592 by cpliakas, pwolanin: fix for core_search page does not use the current default search environment. +#1759004 by mkalkbrenner, pwolanin: fix for apachesolr_search_custom_page_search_form_submit() kills all $_GET parameters. +#1778150 by cpliakas, pwolanin: fix for SQL error in apachesolr_environment_load_subrecords(). +#1743138 by mundanity: Fixed Invalid argument when running Drush. +#1751640 by cpliakas: Added drush commands to see the ID of the last item indexes as well as the next item queued for indexing. +#1708150 by ianmthomasuk, Nick_vh, pwolanin: Added additional typing to function definitions. +#1759110 by Nick_vh: Fixed process response fails if variable was set but never removed. +#1741066 by Nick_vh: Added geo query type to make contrib's modules life easier. +#1730840 by pwolanin: Normalize boolean and other values for parameters like hl. +#1729836 by pwolanin: fix for "Results per page" option for a search page is broken. +#1719302 by Nick_vh, msti: Fixed Running an empty facet query so the facet blocks can be displayed. +#1722966 by greggles: Fixed make apachesolr_index_batch_index_finished() message more translateable. +#1717628 by Nick_vh, pwolanin: Added 'Save and Edit" functionality for configuration pages - Follow ups. + +Apache Solr integration 7.x-1.0-rc3, 2012-08-10 +------------------------------ +#1682004 by Nick_vh: Add template hints for search results based on search page. +#1722012 by mkalkbrenner, Nick_vh: fix for Missing argument 4 for apachesolr_multilingual_apachesolr_index_documents_alter(). +#1717490 by pwolanin: fix for Search result template suggestions relies on non-required fields. +#1708212 by ianmthomasuk, Nick_vh: Fixed Duplicate field error when indexing single value fields. +#1702788 by mkalkbrenner: Fixed content and teaser are empty if node->language is not default. +#1708166 by ianmthomasuk: Fixed Thow exception if asked for invalid solr environment. +#1717628 by Nick_vh | cpliakas: Added 'Save and Edit" functionality for configuration pages. +#1718172 by Nick_vh, pwolanin: Fixed Move search result alter hook earlier, and make sure needed data is set. +#1702526 by Nick_vh: Fixed apachesolr_search_page_load() is quirky. +#1717958 by pwolanin: Fix for text_und fields should be exposed for searching in the admin section. +#1708538 by Nick_vh: Fixed Move the start param above the query alter. +#1700472 by pwolanin: Make 'apachesolr_search_process_response_callback' a search environment variable. +#1439564 by recidive, neochief, Nick_vh, dawehner, jhedstrom: Added Index bundles Export/Import. +#1652746 by martins.bertins | chrisssi: Fixed Notice: Undefined offset: 3 in _menu_translate(). +#1670198 by alanmackenzie | ruedu: Fixed Unable to export/import settings using features module. +#1681946 by Josh Waihi: Fixed apachesolr_index_nodeapi_mass_delete() doesn't work. +#1650096 by Nick_vh | cappadona: Fixed addParam() creates duplicate filters when dealing with fq. +#1677050 by cpliakas: Fix for Cancel link when creating a new environment. +#1673086 by pwolanin: Add drush commands to get, set, del environment variables. +#1682004 by cpliakas: Added template hints for search results based on search page. +#1672738 by Nick_vh: Added Allow a dynamic apachesolr_process_results() function. +#1473722 by levialliance: Added Bundle specific overrides. +#1408844 by Nick_vh, jrbeeman: Added As a site builder I want to override the environment settings in settings.php. +#1652746 by chrisssi: Fixed Notice: Undefined offset: 3 in _menu_translate() (Zeile 775 von /var/www/includes/menu.inc). +#1402748 by Nick_vh, killua99: Fixed Check in apachesolr_do_query() if the static of the query with that name already exists, and if so return it. + +Apache Solr integration 7.x-1.0-rc2, 2012-06-21 +------------------------------ +Renamed schema.xml and solrconfig.xml to rc2 + +Apache Solr integration 7.x-1.0-rc1, 2012-06-21 +------------------------------ +#1642140 by Nick_vh: Fixed Make the search query also escape the slash and move away from menu_tail() to just 1 argument. +#1518108 by grendzy: Added Devel interface for inspecting solr documents. +#1402748 by Nick_vh: Fixed Check in apachesolr_do_query() if the static of the query with that name already exists, and if so return it. +#1631702 by Nick_vh | khaled.zaidan: Fixed Report at admin/reports/apachesolr doesn't always display data from the current default solr server. +#1647668 by paulmckibben: Fixed Error messages when indexing - Notice: Undefined property: stdClass::$nid in DatabaseStatementBase->fetchAllAssoc(). +#1475014 by ceng, Nick_vh: Fixed Drush command solr-delete-index ignores type arguments. +#1588256 by InternetDevels.Com, Nick_vh | RaulMuroc: Fixed Warning: Invalid argument supplied for foreach() in apachesolr_entity_update() (line 1766 of /apachesolr/apachesolr.module). +#1612530 by Nick_vh, willmoy: Added Results say '0 comments' when comment.module is disabled. +#1522174 by pwolanin: Fixed hook_node_type_delete() is wrong. +#1576710 by skwashd: Added Indexing with drush cron generates a lot of invalid cron notifications. +#1543754 by iamEAP: Fixed Schema inconsistency introduced by apachesolr_update_7013. +#1481564 by milesw: Added Allow other modules to react when there are no results. +#1470794 by pwolanin: Added Show size of index on disk in index report. +#1586320 by cpliakas, pwolanin: Added support for the ExternalFileField field type. +#1613328 by cpliakas: Fixed The service_class() is not passed to apachesolr_server_status() when checking each server's status on the settings page. +#1621142 by pwolanin: Fixed Broken logic in indexing of extra data. +#1627484 by gnucifer: Fixed 'allowed operator" not transfered from field definitions. +#1627604 by Nick_vh: Fixed Add query type for taxonomy_term_reference(). +#1609184 by pwolanin: Minor code cleanup in MLT response handling . +#1611338 by remimikalsen: Added Allowing several bundles to share the same field name within a given entity type after field_mapping_alter(). +#1568126 by fearlsgroove, pwolanin: Fixed Apachesolr causes all simpletests to fail. +#1609030 by pwolanin: Fix for Missing search page still executes search. +#1572722 by eporama, cpliakas, wonder95: Added integration with the Entity References module. +#1567614 by pwolanin: fix for Uninstall is not complete. +#1559880 by pwolanin: fix for 'retain filters' checkbox does not work. +#1558626 by pwolanin: fix for broken OR facet when value has spaces (committed in 16ef187). +#1538244 by by julianmancera: Fix for Solr base query adds an extra slash in getPath() when there is no 'q' param. +#1532214 by setvik, pwolanin: Fox for mergePolicy syntax has changed and throws error in Apache Solr 3.6, update config versions. +#1536936 by pwolanin: Fix for Use json.nl=map for Luke and other json data. +#1536628 by Nick_vh: Added exclude hook that allows to skip content without removing it from the table. +#1536600 by Nick_vh: Fixed Performance optimizations when doing hundreds of deletes. +#1515822 by pwolanin, Nick_vh: Added Support notion of parent entity for derived documents. +#1519900 by pwolanin, Nick_vh, Georgique: Fixed Error in apachesolr_index_get_entities_to_index() function. + +Apache Solr integration 7.x-1.0-beta19, 2012-04-05 +------------------------------ +#1515580 by Nick_vh | yannou: Fixed Deleting node is impossible, stdClass problem. + +Apache Solr integration 7.x-1.0-beta18, 2012-04-04 +------------------------------ +#1514314 by Nick_vh, Georgique: Fixed Error in apachesolr_entity_update() Follow-up. +#1514314 by Nick_vh, Georgique: Fixed Error in apachesolr_entity_update(). + +Apache Solr integration 7.x-1.0-beta17, 2012-04-03 +------------------------------ +#1509790 by Nick_vh: Fixed Status is not checked when a reindex is initiated. +#1476228 by ygerasimov: Fixed Items to index determined not correct. +#1477732 by ygerasimov: Fixed apachesolr_index_entities() sets index position that leads to cycle. +#1508434 by lazysoundsystem, Nick_vh: Fixed whitespace problem. +#1481326 by Nick_vh | milesw: Fixed Queries run twice after submitting search forms on a custom search page. +#1502088 by Nick_vh | sanpi: Fixed Missing Search dependency for Apache Solr framework. +#1498490 by Nick_vh, milesw: Fixed Menu paths for custom search pages are broken when exported as features. +#1475010 by Nick_vh: Fixed Date filtering to minute returns faulty results. +#1469484 by Nick_vh, Josh Waihi: Fixed Limit select queries when checking index node table to prevent memory overload. +#1442358 by johnennew, Nick_vh: Fixed Nodes not queued to be removed from index when unpublished. +#1456368 by Nick_vh, pwolanin: Fixed Entities not removed when excluded, unnecessary re-indexing. +#1470042 by klaasvw: Fixed Existing node table error on drupal update. +#1458696 by Nick_vh | m1r1k: Fixed Apache Solr bug with re-index. +#1418834 by levialliance | drewmacphee: Beta15+ problems with i18n and entity_translation(). +#1404284 by pwolanin: Fix for possible undefined function apachesolr_default_environment() during update. +#1441066 by pwolanin: Fix for incorrect hook_schema() definition for {apachesolr_index_entities}. +#1411066 by Nick_vh | heacu: Fixed Unchecking 'Custom filter" has no effect if custom filter has been entered on Edit Search Page. +#1424578 by Nick_vh | cpliakas: Fixed Errors thrown on admin/reports/apachesolr when the server is unavailable. +#1439492 by recidive: Fixed Features importing of search page not working. +#1446694 by edb: Fixed env_id() length is only 32 characters on apachesolr_search_page() table. +#1437842 by duellj: Added Facetapi block titles on empty search page should use theme_facetapi_title(). +#1442358 by johnennew, Nick_vh: Fixed Nodes not queued to be removed from index when unpublished. +#1440394 by grendzy: fix for Field bias setting has no effect. +#1399584 by halcyonCorsair | Nick_vh: Added As a developer I want to change environment variables with drush. +#1423130 by pwolanin | rickmanelius: Fixed apachesolr_user_update() Issues. +#1403810 by Nick_vh | SangersDrupalDude: Fixed some warning in admin area. +#1419290 by Nick_vh, paulmckibben: Fixed When $conditions is empty, search_data() results in 'The Apache Solr search engine is not available" (but results are returned). +#1429230 by Nick_vh: Fixed Default values for for mapping per-field is not added correctly. +#1427288 by Nick_vh: Fixed Change the MergePolicy for all the solrconfigs to use LogByteMergePolicy. + +Apache Solr integration 7.x-1.0-beta16, 2012-02-02 +------------------------------ +#1408190 by DeFr: Fix port check for url with credentials. +#1411354 by Nick_vh: fix for Incorrect reporting of documents indexed. +#1357588 by becw, Bußmeyer, pwolanin: improved ctools export/import support. +#1418136 by zengenuity, pwolanin: fix for undefined function apachesolr_index_set_last_updated() in apachesolr_access. +#995526 by chx, pwolanin: another fix for Key length greater than 1000 bytes causes install error on MyISAM. +#1409878 by becw: Fixed Uncaught exception when the default environment does not exist (patch). +#1398100 by thecarlhall: Fixed Configuration throws exception when starting Solr. Follow up for 7.x-1.x +#1409076 by Nick_vh, sdrycroft: Fixed Entities that use the 'apachesolr_index_entities()" table are not indexed. +#1409146 by Nick_vh: Fixed Add all possible index_types() to apachesolr_index_key(). +#1408856 by Nick_vh: Remove DrupalQueue from the install.php. +#1402746 by Nick_vh: Clean up apachesolr_do_query() so the page variable will be inside the query object. +#1408192 by becw: Fixed Add apachesolr.interface.inc to the .info file (patch). +#1405040 by Nick_vh | chrisssi: Fixed SQLSTATE[42S22]: Column not found: 1054. + +Apache Solr integration 7.x-1.0-beta15, 2012-01-12 +------------------------------ +#1397526 by Nick_vh, cpliakas: Add an option to select which search page facets link to when displayed on non-search pages. Follow-Up small bugfix +#1397138 by Nick_vh: Create Simpletests to verify base functionality of the module. +#1402688 by Nick_vh: Fixed Upgrade from Beta13 to Beta14 does not take the excluded types into account properly. + +Apache Solr integration 7.x-1.0-beta14, 2012-01-12 +------------------------------ +#1397526 by Nick_vh, cpliakas: Add an option to select which search page facets link to when displayed on non-search pages. +#713142 by ygerasimov, pwolanin, Nick_vh: Added configuration option to a search environment if we want to use Dismax or EDismax. +#1379128 by Nick_vh, bdragon: Fixed Treat cardinality=1 fields as single-value + caching stats response - Follow up. +#370855 by Damien Tournoud, Nick_vh: Add configuration option to a search page if we allow user input using the url or not. +#1392940 by Nick_vh | pwolanin: Create a 3.x solrconfig.xml that sets luceneMatchVersion. +#1031250 by EugenMayer, Nick_vh: Added an option to index as another user instead of only anonymous. +#1388498 by Nick_vh | pwolanin: Remove s (sortable) fields from schema.xml. +#1379128 by Nick_vh, bdragon: Fixed Treat cardinality=1 fields as single-value + caching stats response. +#1361422 by Nick_vh: Fixed coder issues as mentioned by the drupal testing bot. +#1398122 by Nick_vh | thecarlhall: Fixed Trying to get property of non-object in apachesolr_search_block_view(). +#1398100 by thecarlhall: Fixed Configuration throws exception when starting Solr. +#1396606 by cpliakas: Fixed Fatal error 'Call to undefined function apachesolr_index_status()" when visiting admin/config/search/settings. +#1392742 by Nick_vh, pwolanin: Fixed Add solr.LengthFilterFactory to the text fields. +#592522 by Nick_vh, pwolanin | quaoar: Fixed Hooks node_type(), taxonomy and user knocks out our database server. +#1394276 by pwolanin, Nick_vh, Fix for logic for last indexed timestamp. +#1388916 by rwohleb, pwolanin: Fix for ctype_digit() port validation fails for ports < 256. +#1391850 by cpliakas: Fix for Fatal error when Solr server is not available on non-search pages. +#1389306 by Nick_vh: Fixed Wrong helptext link if facetapi is not installed. +#1252648 by cpliakas, Nick_vh: Added Allow for enabling facet blocks on non-search pages. +#1161608 by scor, Nick_vh: Added Index format_username()($account). +#1380448 by Nick_vh | longwave: Fixed Menu item description for /admin/reports/apachesolr. +#1387272 by cpliakas: Fixed The cancel links and button submission redirects in facet configuration forms are broken. +#1387088 by swentel, Nick_vh: Fixed apachesolr_index_mark_for_reindex() not found during enable/disable and drush can't load apachesolr.index.inc. +#1313698 by Nick_vh, denikin: Fixed Support for search of multiword content in facets/fields . +#966796 by Nick_vh, scor, BarisW, wesnick, swentel, LSU_JBob | Crell: Added Separate indexer for multiple entity types. + +Apache Solr integration 7.x-1.x-beta13, 2011-12-21 +------------------------------ +#1376278 by Nick_vh: Fixed Inconsistent behavior of data facets producing unlimited filtering, inconsistent counts, or gap mismatches. +#1372952 by pwolanin | albertosouza: Fixed Wrong variable in apachesolr.module. + +Apache Solr integration 7.x-1.0-beta12, 2011-12-12 +------------------------------ +#1369208 by pwolanin: Add configure links in .info files. +#1369184 by pwolanin: Restore MLT block edit use of block module configure form. +#1369144 by pwolanin: Fix for cleanup settings table and restore cron index operation. +#1353422 by Nick_vh | Marty2081: Added Using view modes in 'more like this" block. +#1212610 by Nick_vh | jeff.maes: Fixed Notice: Undefined index: module in apachesolr_search_form_search_form_alter(). - follow up +#1365304 by brianV: Fixed Date field minimum/maximum range callbacks don't handle some cases; cause Solr exceptions. +#1365940 by Nick_vh: Fixed Move all indexing functions to apachesolr.module. +#1364564 by Nick_vh, pwolanin: Clarify % on the index page. +#1357820 by Nick_vh | vrc3: Fixed Titles for taxonomy pages are double encoded. +#1358730 by Nick_vh | rooby: Fixed Facet only searching isn't working. +#1362294 by pwolanin: Fix for undefined variable: title in apachesolr_get_user_title(). +#1059372 by jpmckinney, Georgique, Nick_vh: Fixed References integration is broken. +#1323676 by Nick_vh: Global functions should be context driven. +#1359294 by Nick_vh: Follow-up : Fixed Test is not enabling the correct modules. +#1359386 by Nick_vh: Replace all instances of $_GET['q'] with current_path(). +#1359294 by Nick_vh: Fixed Test is not enabling the correct modules. +#1358158 by pwolanin | SangersDrupalDude: Fixed Schema issues on update to D7. +#1351908 by Nick_vh: Added UI comments and improvements. +#1349532 by Nick_vh: Added UI remake of the bias pages. +#1357548 by Nick_vh: Make the unit tests for solr succeed if there is no solr environment available. +#1356038 by Nick_vh: Added More like this / Blocks Admin UI rework. +#1356018 by Nick_vh: Fixed Remove admin/system from solrconfig.xlm. +#1344690 by Nick_vh: Added Search page should be cloneable. +#1333904 by Nick_vh | vrc3: Added Please restore flexible 'results per page" option - follow up. +#1292364 by Nick_vh, scor: Added UI redesign. +#1336324 by Nick_vh | tinker: Added Get Solr version number to determine feature sets like facet ranges and location search. +#1347092 by Nick_vh | cfuller12: Fixed Solrsort appears to be broken in beta11. +#1344576 by Nick_vh: Fixed More like this should be environment dependent. +#1344570 by Nick_vh: Fixed When installing solr the 'visit the admin page" link is not working. + +Apache Solr integration 7.x-1.0-beta11, 2011-11-16 +------------------------------ +#1328886 by drasgardian, Nick_vh, pwolanin: fix for access module fails for realm names with spaces. +#1340232 by milesw, Nick_vh: Added Did You Mean suggestions should be broken out from core search form. +#1343646 by Nick_vh: Fixed Empty Search behavior is not working. +#1301646 by Nick_vh: Fixed Coder Review + Drupal coding standard. +#1097976 by Nick_vh, jpmckinney: Fixed Use ShowFileRequestHandler, gettableFiles is deprecated. +#1342134 by Nick_vh: Fixed 'Creating default object from empty value" Notice in node access and indexing test. +#1341860 by pwolanin: Fixed Notice: Undefined index: fq in apachesolr_search_conditions_default() (line 401 of apachesolr_search().module). +#1053126 by jpmckinney, Nick_vh | pwolanin: Remove duplicate sort hooks in 7.x? follow-up. +#1053126 by jpmckinney, Nick_vh | pwolanin: Remove duplicate sort hooks in 7.x?. +#1341854 by pwolanin: Pass the query object into hooks altering search results. +#1340552 by pwolanin: Make facet generation more flexible/overrideable. +#1341840 by pwolanin: Fix for undefined index 'core_search' while running tests. +#1271964 by Nick_vh | wmostrey: Fix for no way to delete content recommendation blocks. +#1314406 by Nick_vh, scor: Fixed De-duplication of the apachesolr_search_execute() and apachesolr_search_user_defined_search_page(). +#1161538 by Nick_vh, pwolanin | domidc: Fixed The numeric field id should not be used for Solr index field names. +#1333904 by Nick_vh | vrc3: Restore flexible 'results per page' option. +#1161444 by Nick_vh | cpliakas: Modify Facet API field definitions to reflect API change for the 'query type" key. +#1334216 by Nick_vh: Fixed Convert all static parameters to drupal_static(). +#1328854 by Josh Waihi: Added Tag cron database queries to allow modules to alter conditions of updating index table. +#1323758 by Nick_vh | Frippuz: Fixed hook_apachesolr_process_results() make no impact on results. +#1265124 by InternetDevels.Com: Fixed Not output pager in search results. +#1324842 by Nick_vh: Added setAvailableSorts to the api. +#1000532 by craigmc, jpmckinney: Fixed Non-current/valid Node Types not excluded from index. +#1320906 by Nick_vh | brianV: Fixed Taxonomy Search page title should be dynamic based on term. +#1320076 by Nick_vh | egarias: Fixed Results per page should be non-negative integer less than 200. +#1320634 by Nick_vh | jummonk: Fixed Search pages saved in static array before they are created in DB => PDO exception on creation of menu_router() records. +#1319542 by jgalletta: Fixed Undefined index: get in apachesolr_search_user_defined_search_form_submit(). +#1313698 by Nick_vh, denikin: Fixed Support for search of date content in facets/fields +#1316578 by Nick_vh | rooby: Added some documentation somewhere that says you need the facetapi module for facets. + + +Apache Solr integration 7.x-1.0-beta10, 2011-10-19 +------------------------------ +#1312718 by pwolanin: fix for menu rebuild problems when installing apachesolr_search. +#1134610 by pcambra, JoeMcGuire, Ravi.J: schema support for ctools export of settings. +#1314664 by Nick_vh, brianV: fix for Search pages completely ignore the title set in the configuration. +#1314260 by Nick_vh: Fixed hook_apachesolr_query_prepare() not also correctly documented. +#1313698 by Nick_vh, denikin: Fixed Support for search of multiword content in facets/fields . +#1309572 by Nick_vh: Fixed Creating new search page gives error when trying to find the search page variable. +#1204480 by Nick_vh | chriscalip: Fixed Please update apachesolr.api.php naming convention from HOOK_ to hook_. +#1212610 by Nick_vh: Fixed Notice: Undefined index: module in apachesolr_search_form_search_form_alter(). +#1309564 by Nick_vh: Fixed Simplifying syntax between isset and empty for search_box() in custom page. + +Apache Solr integration 7.x-1.0-beta9, 2011-10-13 +------------------------------ +#1307526 by pwolanin, Nick_vh: Fixes for update path, core search functionality. +#1264786 by grndlvl: fix for double ellipses on search snippets. +#1305282 by pwolanin, Nick_vh: Fixed Search pages problems. +#1283924 by rjmackay, lazysoundsystem, jweowu: fix for notice undefined index errors. +#1305052 by cpliakas: fix for Negative facets not displayed when the mincount is 0. +#1279164 by pwolanin, fix for 'bundle' is not a required field, but apachesolr treats it as such. +#1300380 by Nick_vh, pwolanin: Search environments not clear about being active or online. +#1294846 by Nick_vh: Added Refactoring of the search pages. +#1188824 by pwolanin: fix for very large watchdog entries when index is in read-only mode. +#1270826 by pwolanin, brianV: fix for search page regex fails for complex queries. +#989398 by pwolanin, Nick_vh: Fix tests after moving conf files. +#1167136 by cpliakas: fix for fields attached to nodes added as facets regardless off the collection type. +#1225554 by pwolanin: fix for pagination missing in the search page administration. +#817286 by pfrenssen: API docs cleanup. +#901376 by LiuShaz, pwolanin: insure UTF-8 encoding is used for POST searches. +#1288080 by Nick_vh, brianV: make facets based on date fields work. +#1201534 by BrianV, pwolanin: restore date field indexing. +#1230380 by Shawn_Smiley: fix for Undefined index: 'facet mincount allowed'. +#1292328 by Nick_vh: add clone environment feature, tweak the UI. +#1187888 by pwolanin: move conf to a subdirectory and start supporting solr 3.3+. +#1237472 by DeFr, sfyn: Fix for _constructURL method misforms urls with username/password. +#1258658 by pwolanin: Fix failing node access test due to core 7.3 change. +#1248366 by Dave Reid: Fix declaration of getInfo() test functions. +#1219178 by cpliakas: Added support for customizable minimum facet counts. +#1216184 by cpliakas: remove unneeded static variable. +#1204450 by blazey: fix for parameter error in apachesolr_taxonomy module. +#1150174 by mr.andrey, pwolanin: Strip content of script and similar tags when indexing. +#1183742 by MrHaroldA, pwolanin: Index all numeric field API fields by default. +#1188614 by ASupinski: Expand Hook_Hook_Info to include other hooks. + +Apache Solr integration 7.x-1.0-beta8, 2011-06-13 +------------------------------ +#1174960 by pwolanin: fix critical indexing bug from DBTNG error in apachesolr_cron_check_node_table(). +#1148612 by pwolanin: fix regression; Clicking "Relevancy" has no effect after choosing another sort. + +Apache Solr integration 7.x-1.0-beta7, 2011-05-24 +------------------------------ +#1162600 by pwolanin: Display an error if the schema version in use is incompatible. +#1162078 by jpmckinney: fix for Undefined variable: stats_summary. +#1159172 by jpmckinney: Remove unused facet functions. +#1167172 by cpliakas: Improve the breadcrumb handling for facet settings forms. +#1157864 by cpliakas: Integrate with Facet API's depencency plugin system. +#912758 by pwolanin: use Facet API's support for faceting missing values. +#1098860 by jpmckinney: Add apachesolr_cron_check_node_table back to cron. +#1159172 by cpliakas: Remove unused facet functions. +#926564 by jpmckinney: Add get_subqueries(). +#1064972 by jpmckinney: Use is_callable not function_exists, where applicable. +#1097988 by pwolanin: Add omitHeader to save on bandwidth. +#1154770 by jpmckinney, pwolanin: Must double quote filter query values if containing space or colon. +#1152382 by cpliakas, pwolanin: Modify Facet API adapter for api changes. +#1131288 by jpmckinney: Fix install and other follow-ups for renaming "server" to "search environment". +#1150988 by pwolanin: Fix for facet blocks based on custom fields displaying field keys instead of labels. +#1150220 by pwolanin: Fix for MLT requests are not going through query alter. +#1150306 by pwolanin: fix for double URL encoding of plus sign (+) on search, after changing sort filters. + +Apache Solr integration 7.x-1.0-beta6, 2011-05-06 +------------------------------ +#1148768 by pwolanin: move the read-only index setting to the environment edit page. +#1146976 by pwolanin: Rename hook_apachesolr_modify_query to hook_apachesolr_query_alter and other API clean up. +#1146296 by pwolanin: integrate with Facet API current search block code. +#1145036 by slip, pwolanin: Add support for facet browsing to custom search pages. +#1131288 by pwolanin: Rename "server" to "search environment" for better conceptual clarity. +#1122186 by slip, pwolanin: Allow custom user-specified search pages, supporting API changes. +#1127520 by cpliakas, pwolanin: make the breadcrumb look reasonable on facet form. +#1127302 by Janusman: some variables were not removed on uninstall. +#1126806 by pwolanin, Janusman: restore facet browsing functionality. +#1126488 by cpliakas: Implemented hierarchical taxonomy facets. +#1126284 by pwolanin: add enabled filters tab on every server edit page. +#1126282 by pwolanin: fix facet mincount param. +#1124844 by pwolanin: Port over facet API adapter and hooks. +#1121170 by pwolanin: remove facet-related code to prep for Facet API integration. +#1122348 by pwolanin: Rework query class to provide uniform methods of getting and setting params. + +Apache Solr integration 7.x-1.0-beta-5, 2011-04-07 +------------------------------ +#799970 by pwolanin: update README for config changes. +#1118646 by cpliakas, pwolanin: Fields now displayed on search index report page. +#1117152 by cpliakas: Added a cancel link to the server edit page. +#1117606 by cpliakas: Resolved inconsistencies with the caller parameter. +#1118508 by pwolanin: Make the Apache Solr config link show up next to Search module. +#1117128 by jpmckinney: Follow-up to #1088208 + +Apache Solr integration 7.x-1.0-beta4, 2011-04-04 +------------------------------ +#1116030 by cpliakas, pwolanin: Added titles to settings pages to add transparency as to which server's settings are being edited. +#1112022 by pwolanin, elliotttf: index node last_comment_timestamp too. +#1114798 by pwolanin: enhancements to config based on Solr 3.1 examples. +#1108618 by pwolanin: make numeric fields in 7.x use a sortable data type by default. +#1103602 by pwolanin: prevent PHP Notice when there is an invalid term reference field. +#1097988 by jpmckinney: avoid 'using default converter' warning on Solr startup. +#920482 by jpmckinney: $info_split['date'] template variable should contain changed, not created, date. +#379512 by pwolanin: separate schema field for indexing comments and "extra" information. +#871440 by jpmckinney: Solr taxonomy page displays search form and blocks when the user has no access. +#761990 by jhedstrom, pwolanin: switch to POST for long search ULRs. +#1112362 by pwolanin: cleanup following #1107502 to better use drupal_http_request. +#1107502 by pwolanin: Merge relevant parts of php client into DrupalApacheSolrService. +#899590 by jpmckinney, elliotttf: Support indexing of attached fields that are not facets. +#993476 by jpmckinney, pwolanin: allow arbitrary results per page in the 0-200 range. +#1080652 by elliotttf, pwolanin: Allow other modules to return search results on solr failure. +#562214 by ecofinn, wmostry, jpmckinney: Problems with double-encoded ampersands. +#997480 by jpmckinney | davidwhthomas: Facet checkboxes are duplicated following other javascript activity on page. +#1098038 by pwolanin, mgifford: fix spelling suggestion incorrect use of LABEL tag. +#1099390 by jpmckinney: Fatal error: Call to undefined function apachesolr_nodeaccess_build_subquery(). +#1098222 by pwolanin: Rename and make the nodeaccess module more generic. +#1092910 by pwolanin: missing date field conversions from #1088208. +#1090530 by pwolanin: Further schema-related code fixes following from #1088208. +#783366 by elliotttf: Invoke hook_apachesolr_prepare_query() in apachesolr_search_browse +#1078766 by elliotttf: code-style cleanup according Coder module. +#1049114 by Steven Jones, james.williams, Janusman: restore hierarchical taxonomy facets. +#1088208 by pwolanin | Janusman: simplify the schmea to eliminate node-specific fields. +#1089342 by elliotttf: Content Biasing not working. +#996800 by elliotttf: trim host, port, and path strings to prevent connection errors. +#1020780 by jpmckinney, pwolanin: cleanup of variable_get to apachesolr_server_variable_get conversion. +#1085630 by pwolanin: Index taxonomy term ancestors into term reference-based field. +#1050000 by pwolanin: More generically prevent calling nodeapi update_index when indexing. +#1060536 by jpmckinney: Long title and Default shortcut link obscured in overlay. +#1060550 by jpmckinney: admin/build/block should be admin/structure/block. +#704190 by pwolanin | robertDouglass | jpmckinney: Add page callbacks to display conf files in the index in reports. +#925608 by jpmckinney | janusman: Quick perf improvement: cache term ancestors on indexing. +#1059380 by jpmckinney: apachesolr_fields_list_display_callback doesn't return if $facet in list_allowed_values. +#1059368 by jpmckinney: apachesolr_clear_cache as a #submit callback doesn't work. +#1072884 by justinrandell: incorrect use of query->condition() in apachesolr_cron(). +#901720 by jpmckinney | robertDouglass: Highlighting snippets in search results not flexible enough, and logic improvement. +#1064782 by jpmckinney: Use module_load_include, module_load_install instead of include_once. +#791916 by Network | jpmckinney: Allow facet search block to have children always show. +#937328 by Davy Van Den Bremt | pwolanin: Drush support for indexing remaining nodes. +#1060698 by dww | jpmckinney: Always display the current value of apachesolr_cron_limit in the admin UI. +#904312 by pounard: Use drupal_get_breadcrumb() not menu_get_active_breadcrumb(). +#616888 by bangpound: Pass delta to MLT blocks' theme function. +#864146 by pwolanin | jpmckinney: Fixed When moving fq to q.alt, we should parenthesize each fq. +#896324 by ahankinson | jpmckinney: Fixed module_invoke() called too late. +#840358 by pwolanin | torstenzenk: Fixed Error searching Taxonomies. +#991444 by Nick_vh: None of start, end, gap should show up as date facets. +#835674 by pwolanin: Remove search module dependency from apachesolr. +#961570 by jpmckinney: if apachesolr_search was the default search module in D6, make it so in D7. +#878996 by pwolanin | weri: Added Don't break the loop. +#1050044 by pwolanin: hook_theme API fix, and revert to using core theme('search_results'). +#1026916 by dmitry_bezer: apachesolr_get_enabled_facets() mandatory parameter was omitted. +#528086 by pwolanin: Fix for special html entity search and display bugs. +#1020780 by pwolanin: Store variable settings per server for better flexibility. +#891962 by jurcello, pwolanin: avoid incorrect filter substring matches, emit correct query string. +#1018768 by pwolanin: fix notices during failed search request. + +Apache Solr integration 7.x-1.0-BETA3, 2011-01-06 +------------------------------ +#1017836 by Janusman, pwolanin: fix empty search behavior. +#1017624 by pwolanin: Fix for "Did you mean" suggestion does not show. +#1007848 by pwolanin: Fixes for apachesolr_nodeaccess for Drupal 7. +#1017258 by pwolanin: Fix help text when there are no search results. +#1013122 by pwolanin: some JS cleanup to use jQuery proxy. +#1013136 by pwolanin: fix retain filters checkbox. +#1009398 by pwolanin: fix content bias forms. +#1007860 by pwolanin: API fix for hook_apachesolr_modify_query, add type hinting. +#1007824 by pwolanin: use contextual links to avoid block caching mode issues. +#1003500 fix icon and icon path. +#957652 by aegnor, pwolanin: fix str_replace may remove a substring in filter_extract. + +Apache Solr integration 7.x-1.0-BETA2, 2010-12-18 +------------------------------ +#983458 by craig_ : mass update and mass delete ignore setting apachesolr_read_only. +#996976 by scor: list item class should be an array, not a string. +#995526 by pwolanin: Alter server schema so we don't exceed the MyISAM key size limit. +#1000396 by dmitry_bezer: Edit server form ignored "Make this server the default" checkbox. +#997240 by VladGh: fix parameters for apachesolr_server_edit_form for PHP 5.3. +#993448 by pwolanin, scor: get Solr result docs as stdClass instead of Apache_Solr_Document. + +Apache Solr integration 7.x-1.0-BETA1, 2010-12-09 +------------------------------ +#979198 by pwolanin, janusman: Missing 'module' property on facet $block objects. +#992860 by pwolanin, davereid: Taxonomy indexing and faceting has to be per field, not per vocab. +#991590 by larskleiner: fix for API change to timezone param in format_date(). +#989730 by pwolanin: Using tdate instead of data cases java exception in using rord(). +#989658 by pwolanin: Drupal 7 allows multiple term refernece fields for the same vocabulary. +#989398 by pwolanin: get some actual tests working. +#983892 by pwolanin: update schema to use long instead of int, plus tdate fields. +#983894 by pwolanin: add a set of conf files that can be used to create a test core. +#983572 by amateescu, pwolanin: fix for empty filter values causing Solr error. +#795912 by pwolanin patch 64: fixes various settings forms and variables, avoids a notice, removes use of md5() in favor of drupal_hash_base64(). +#982846 by pwolanin: split out (and deprecate) the taxonomy path hijack feature. +#982840 by pwolanin: OO cleanup for Drupal 7 coding standards. +#982490 by pwolanin: rip out Drupal 6.x update functions, update README. +#904100 by das-peter, pwolanin: prevent missing table error when comment module not enabled. +#795912 by pwolanin patch 62: requirements fixes, add a button to test server settings. +#795912 by pwolanin patch 61: fix up server add/edit/delete funcitonality. +#795912 by pwolanin, crell patch 60: - makes Field API handling more generic, adds a framework + for handling multiple Solr servers, and moves the nodeaccess module out of the contrib dir. +#885950 by pwolanin, csevb10: preserve added/removed filters when filterstring is re-parsed. +#864160 by pwolanin: Allow the caller a last chance to modify the query and params. +#536990 by pwolanin | jpmckinney, janusman: always index content as an anonymous user. +#835850 by pwolanin: add more replicated files to solrconfig.xml master section. +#830976 by eosrei, pwolanin: make sure we return a non-zero ping time on success. +#795912 by tjwallace, dmitry_bezer, jpmckinney, pwolanin: inital Drupal 7 port. + +Apache Solr integration 6.x-2.0-BETA1, 2010-04-08 +------------------------------ +#660754 by jhedstrom: Added Allow key sorting of facets. +#614644 by netsensei | robertDouglass: Fixed Forms attached to Apachesolr search results won't work. +#747346 by robertDouglass, pwolanin | lazysoundsystem: Fixed Typo in apachesolr_search().module. +#763072 by robertDouglass, justinrandell | pwolanin: Fixed warnings when indexing old, crappy html. +#658278 by cpliakas | JThan: Fixed Errors when building the search index in PHP 5.3. +#765486 by robertDouglass: Fixed Several cases where Luke cache not getting cleared and resulting in errors. +#765448 by robertDouglass: Fixed Facet blocks for hierarchical taxonomy broken. +#751420 by pwolanin, skwashd | Damien Tournoud, Scott Reynolds: Fixed apachesolr_site_hash() calls md5() twice. +#750426 by mkalkbrenner, pwolanin | robertDouglass: Fixed fieldType textTight conficts with fieldType text and textSpell. + +Apache Solr integration 6.x-2.0-BETA1, 2010-03-24 +------------------------------ +#649038 by brunodbo, slip | robertDouglass: Fixed Search not working on 404 page. + +Apache Solr integration 6.x-2.0-ALPHA3, 2010-03-22 +------------------------------ +#610656 by pwolanin, claudiu.cristea | Scott Reynolds: Fixed Facets requests for non-enabled modules. +#686390 by pwolanin | rjbrown99: Fixed Wrong number of initial items in taxonomy facet under certain conditions. +#573734 by drewish | robertDouglass: Added Index controls should be radio buttons with one form submission button. +#736540 by drewish | Scott Reynolds: Changed Minimize UPDATE queries in apachesolr_nodeapi_mass_update(). +#687738 by David Lesieur | anantagati: Fixed Avoid introducing empty 'filters' query string. +#733116 by pwolanin | drewish: Changed Implement hook_flush_caches(). +#719356 by robertDouglass, mathieu | flk: Fixed Indexing cron triggers sigsegv in apachesolr.module line 387. +#744038 by siliconmeadow: Changed Change of Drush extension command naming conventions. +#558160 by robertDouglass, mihha | DenRaf, mcarbone, haxney: Added date facet for cck fields. +#666936 by pwolanin, robertDouglass, claudiu.cristea | justindodge: Fixed apachesolr.js - Drupal.behaviors.apachesolr does not respect context. +#708424 by janusman: Changed Change gmdate() to Drupal format_date() in date facets to support localization. + +Apache Solr integration 6.x-2.0-ALPHA2, 2010-01-08 +------------------------------ +#679522 by pwolanin, Add gettableFiles to solr admin interface config. +http://drupal.org/cvs?commit=309746 by robertDouglass, add entity='comment' to comments on indexing. +#672882 by David Lesieur: Fixed Broken 'Show more' link on taxonomy facets. +#604566 by robertDouglass | jhedstrom: Fixed index_value() never set for CCK fields that aren't of type text, node or user referrence. +#672530 by robertDouglass: Fixed Change array key names from display callback to display_callback() and indexing callback to indexing_callback(). +#672518 by robertDouglass: Fixed Add new trie prefixes to helper function . +#551582 by drewish: Fixed Show value instead of key in CCK facets. +#668396 by pwolanin, closer to fix from #655006 for PHP notices. +#664818 by robertDouglass, pounard, pwolanin | Scott Reynolds: Fixed Wrong watchdog() usage. +#657648 by kcoop: Added Add Smaller Limit Options to Apache Solr Cron Indexing. + +Apache Solr integration 6.x-2.0-ALPHA1, 2009-12-26 +------------------------------ +#664818 by robertDouglass, pounard, pwolanin | Scott Reynolds: Fixed Wrong watchdog() usage. +#662232 by pwolanin | anarchivist: Changed Use language-neutral code like D7. +#666648 by pwolanin: Changed Make hook_apachesolr_update_index() more generic by taking a namespace param also. +#667110 by pwolanin: Fixed Replace bogus use of pager_query(). +#667124 by pwolanin: Fixed Use current query not altered query for the breadcrumb. +#667650 by Dave Reid: Fixed Results of apachesolr_process_response() should return absolute URLs. +#664572 by pwolanin: Added Add schema and core name to admin screen. +#664860 by pounard: Fixed Wrong t() usage in apachesolr_field_name_map(). +#528086 by pwolanin, better (but still problematic) handling of entities. +#662232 by pwolanin, index zxx as the Language neutral code. +#401234 by mkalkbrenner, janusman, and pwolanin, reflect hierarchical taxonomy vocabulary in facets. +#661952 by pwolanin, fix no results help text for dismax syntax. +#348668 by pwolanin, add indexing of the 'node' entity string. +#641954 by anarchivist, swentel, pwolanin, update schema.xml. +#651044 by kcoop use node title for comment title when comment has no title. +#655006 by Scott Reynolds, pwolanin fix warnings on constants. +#652512 by robertDouglass enable use of more than just the default solr server. +#642602 by robertDouglass, change 'content type' to 'content_type' in facet definitions. +#641452 by robertDouglass, prevent admin from trying to re-index when in read-only mode. +#372767 by socki, robertDouglass, pwolanin allow MLT blocks to be filtered by type and custom filters. +#372336 by der, janusman, robertDouglass, allow name sorting of facet links. +#611670 by pwolanin, allow modules to abort the building of documents for indexing. +#628080 by pwolanin, update to use Solr PHP library r22 and check for it in hook_requirements. +#638236 by mkalkbrenner and robertDouglass, undocumented dependency on taxonomy module. +#562458 by janusman, fix typo preventing menu_rebuild regarding taxonomy hijack. +#630798 by joshk, robertDouglass make cache_apachesolr table to facilitate better memcache utilization. +#623046 by robertDouglass make the results that come back from a search more useful. +#622120 by robertDouglass make the "Show more" block selection dynamic to accommodate other modules. +#621922 by robertDouglass make the "Show more" js more robust. +#612024 by pwolanin, Add method to allow requests to additional Solr servelets. +#561082 by pwolanin, consolidate Solr delete queries on cron. +#580404 by pwolanin per content type comment exclusion. +#597174 by Frando, add hook_apachesolr_prepare_query() to enable custom sorts. +#591278 by robertDouglass fix bug that was preventing hook_apachesolr_modify_query from working correctly. +#590982 by swentel fix warnings on indexing. +#554136 by emackn, Jaza make results-per-page alterable. +#580764 by robertDougalss Add a new contrib module that allows searching on just comments. +#548160 by robertDouglass get rid of functions that begin with underscore. Yuck. +#580404 by robertDouglass make indexing of comments optional. New variable, apachesolr_index_comments_with_node. +#538636 by robertDouglass allow modules to register document handlers so that multiple documents can be indexed per entity. +#557382 by Josh Waihi, Scott Reynolds mlt blocks were double encoding titles. +#578008 by robertDouglass improve performance by not including unused facet queries. +#552152 by robertDouglass OR operator for facet blocks. +#576092 by robertDouglass use Drush to search the site using Solr. +#576040 by robertDouglass use Drush to download the SolrPhpClient: drush solr phpclient +#457826 by janusman Make the behavior of empty searches configurable. +#573038 by robertDouglass Automatically create facets for user and node reference CCK fields. +#570476 by robertDouglass add initial Drush support with commands drush solr delete index and drush solr reindex. +#570476 by robertDouglass allow for deleting or reindexing single content types. +#456420 by anarchivist, janusman, robertDouglass Reindex using Batch API. +#551510 by Scott Reynolds Add in ability to theme different facet blocks differently. +#551620 by robertDouglass Type dependent facet blocks. +#549664 by Scott Reynolds Ignore node_access for Solr Views queries. +#551582 by robertDouglass make CCK breadcrumbs, facets, and current search show the value, not the key. +#551278 by robertDouglass CCK mappings don't respect shared fields +#535654 by drunken monkey Add apachesolr_server_status() function +#543226 by drunken monkey validate port on settings form. +#502976 by Scott Reynolds followup to Other GET parameters ignored by Apache Solr Facet Blocks +#473554 by janusman Add an "unclick" link to search keys +#545094 by loganfsmyth add getter and setter methods for a query's keys. +#530910 by Damien Tournoud fix offset problem in field settings administration. +#526344 by drunken monkey Remove apachesolr_read_only check from Drupal_ Apache_Solr_Service::_sendRawPost(). +#525980 by robertDouglass Clarify the API of apachesolr_index_updated. +#530196 by pwolanin, fix facecount form function calls in apachesolr_og. +#548102 by robertDouglass change wording on enabled filters page to improve usability. +#529606 by Damien Tournoud update schema.xml with WhitespaceTokenizerFactory and SnowballPorterFilterFactory. Note that you need to stop solr, replace schema.xml, delete your index, and re-index your site. +#528002 by janusman, Add RSS discovery to taxonomy hijack page +#528888 by robertDouglass turn spellchecker on by default +#528596 part 1 by robertDouglass add JS enabled checkboxes to facet and unclick links +#525918 by robertDouglass be more forceful when reindexing; rebuild the apachesolr_search_node table completely. +#528516 by robertDouglass add apachesolr-facet and apachesolr-unclick CSS classes to unclick and facet links. +#528484 by robertDouglass switch to Drupal.behaviors in apachesolr.js +#515682 by robertDouglass, add confirmation form to re-index button. +#509526 by pwolanin, {apachesolr_search_node} table should be rebuilt when index is deleted. +#457826 by janusman, robertDouglass - show browsable facet blocks in the search well when no search term is present to allow browsing. Hello 6.2 branch of ApacheSolr :D + +Apache Solr integration 6.x-1.x, xxxx-xx-xx +------------------------------ +#508364 by pwolanin, Don't offer non-indexed fields as search options. +#508548 by pwolanin, Don't implode params['fq'] if it's not set. +#708424 by janusman: Changed Change gmdate() to Drupal format_date() in date facets to support localization. + +Apache Solr integration 6.x-1.0-RC1, 2009-07-02 +------------------------------ +#502976 by pwolanin, Scott Reynolds, robertDouglass facet links and form submissions respect non ApacheSolr $_GET parameters. Note that this changes the interface API: get_url_querystring is now get_url_queryvalues and returns and array instead of a string. +#507708 by pwolanin, fix sort parameters to use field aliases, validate in query object. +#299539 by kleung11 and pwolanin, use 'administer search' for permission checking. +#503644 by pwolanin and Jeremy, make sure we strip ctrl chars last, add logging. +#505652 by bdurbin, add apachesolr-showhide class to Show more link. +#472600 by janusman, JacobSingh, and pwolanin, optionally hijack taxonomy pages. +#496650 by mkalkbrenner, make unclick links work after #463900. +#495258 comment out timeAllowed params (partial roll-back of #490076) +#495012 by pwolanin, fix Anonymous user and other 0-value facets. +#463900 by pwolanin and JacobSingh, facet theme function clean-up. +#405206 by pwolanin, allow Apache Solr to be the default, let core search index 0 nodes. +#453310 by pwolanin, allow easier theming of username display. +#490076 by pwolanin, spellcheck more popular, maxqt to 20, limit search time. +#358166 by David Lesieur, janusman, cptnCauliflower, and pwolanin, search for just facet(s). +#489654 by JacobSingh, and pwolanin, allow users to set their index as "read only". + +Apache Solr integration 6.x-1.0-beta11, 2009-06-11 +------------------------------ +#348218 by David Lesieur, janusman, and pwolanin, retain filters for next search. +#401046 by pwolanin, revist urlencoding of query strings. +#467810 by aufumy, Pass in page number and caller to apachesolr_search_execute. +#481838 by JacobSingh and pwolanin, enable plus sign in search when using clean URLs. +#480814 by mkalkbrenner and pwolanin, add more detail to logging on errors. +#464758 by pwolanin, 4th param to htmlspecialchars breaks PHP < 5.2.3. +#466328 by pwolanin, fix classes for sort links. + +Apache Solr integration 6.x-1.0-beta10, 2009-05-14 +------------------------------ +#449414 by pwolanin, aufumy, & Scott Reynolds, refactor apachesolr_search_search(). +#462836 by pwolanin, catch fatal error in _nodeaccess if no solr. +#461506 by pwolanin, do nothing if there are no nodes to index. +#459930 by Scott Reynolds and pwolanin, clean up hook_enable(), uninstall, update_6004 +#453338 by pwolanin and JacobSingh, move mlt functionality into the framework module. +#365495 by pwolanin, improve admin screens and usability of field weights. +#454608 by pwolanin, fix current search block. +#453182 by pwolanin, use stored path rather than forcing node/$nid. +#448298 by JacobSingh and pwolanin, use a confirm form for index deletion. +#454352 by Damien Tournoud, make optimize interval configurable, document variables. + +Apache Solr integration 6.x-1.0-beta9, 2009-04-30 +------------------------------ +#435924 by pwolanin, only clear cache on cron after updates and if the server is available. +#405780 by blackdog and pwolanin, skip excluded node types during counting and indexing. +#441628 by aufumy and pwolanin, update _og for negative facets, minor fixes, install/enable/update hooks. +#447622 by pwolanin and mkalkbrenner, better encoding of html entities and CCK facets. +#447890 by pwolanin, properly respect 'access content' permission in _nodeaccess. +#271753 by pwolanin, more granular CCK field mappings via _alter hook. +#436074 by pwolanin, better query class handling of negative queries. +#442198 by Scott Reynolds and pwolanin, update the Drupal_Solr_Query_Interface interface. +#443252 by Scott Reynolds, (bugfix for regression) make protected id public again. +#337737 by David Lesieur, mikejoconnor and Scott Reynolds, localize arg(1) dependence. + Changes query get_path() to facilitate generating facets outside the search page. + +Apache Solr integration 6.x-1.0-beta8, 2009-04-16 +------------------------------ +#343252 by pwolanin, fix nodeaccess for method name changes, make multi-site aware. +#432946 by pwolanin, query class and sort cleanups. +#393480 by pwolanin and Jody Lynn, provide a book facet and facets for missing fields. +#432140 by Damien Tournoud, use format_interval() for more attractive, localizable time intervals. +#348029 by pwolanin, Handle negative filters and improve date facet block code. +#254565 by drunken monkey and Scott Reynolds, change the query class to enable Views integration. + +Apache Solr integration 6.x-1.0-beta7, 2009-04-03 +------------------------------ +#410330 by pwolanin and bhuga, return more information for error 0. +#293989 by bjaspan and vladimir.dolgopolov, add date facets for created and changed dates. +#420290 by mkalkbrenner and pwolanin, add spaces around tags to avoid running words together. +#368688 by hurleyit and pwolanin, send MLT docs instead of processed links to theme function. +#383478 by pwolanin and JacobSingh, provide more information about autocommit lag, pending deletes. +#339490 by aufumy, pwolanin, and JacobSingh, Organic groups Apachesolr integration, new _alter hook. + +Apache Solr integration 6.x-1.0-beta6, 2009-03-20 +------------------------------ +#305370 by pwolanin, Handle failed delete requests so unpublished/deleted content doesn't stay in the index. +#407570 by pwolanin and moshe weitzman, _alter for sort links, hide for < 2 results. +#392978 by pwolanin and ncameron, workaround for those using php 5.1, update README. +#402984 by JacobSingh and pwolanin, put MLT menu under the general ApacheSolr settings. +#401442 by Janusman and pwolanin, no sort block when 0 results. +#405732 by JacobSingh, pwolanin: Update to new SolrPhpClient (r6) and make ping() use drupal_http_request. +#405722 by JacobSingh, increase ping timeout and make it variable. +#400882 by mkalkbrenner, fix faceting bug due to static counter in method add_field. +#382358 by pwolanin, use tokenizer solr.CharStreamAwareWhitespaceTokenizerFactory to fix highlighting. + +Apache Solr integration 6.x-1.0-beta5, 2009-02-27 +------------------------------ +#305370 by pwolanin, don't delete from apachesolr table if Solr query fails. +#385348 by moshe weitzman, use key in sort links array. +#385362 by pwolanin, Shorten hash from 32 chars to 12. +#383804 by JacobSingh, fix query building that broke nodeaccess. + +Apache Solr integration 6.x-1.0-beta4, 2009-02-23 +------------------------------ +#380670 by pwolanin, only add a bq param for a node-type boost > 'Normal'. +#379518 by pwolanin, correct mismatch in default boost between + solrconfig.xml and apachesolr_search. +#380594 by pwolanin, empty the spellcheck dictionary if the index is deleted. +#380644 by JacobSingh, Backwards compatability for old sort fields. +#380538 by pwolanin, fix code to find vid for taxonomy facet blocks. + +Apache Solr integration 6.x-1.0-beta3, 2009-02-20 +------------------------------ +#378222 by janusman and pwolanin, add boost settings for "sticky" and "promote". +#378566 by pwolanin, nodeaccess not correctly marking single nodes for re-indexing. +#378270 by pwolanin, suppress MLT admin link when there is no block content. +#378196 by pwolanin, remove PHP client from CVS per Drupal.org policy. +#231200 by janusman and pwolanin, turn on mapping of accented to non-accented + ISO characters for indexing and search. +#377494 by pwolanin, Update text type in schema to new example. +#376270 by pwolanin, also add option to bias on recent comments/changes. +#337879 by pwolanin and blackdog, Store relative not absolute paths. +#376255 by pwolanin, Index more node fields, use boolean fields. +#376966 by JacobSingh and pwolanin, requesting the top terms from luke is very + expensive, so normally request 0, and only get them for smaller indexes. +#375991 by pwolanin, build spellcheck index on optimize. +#370707 by pwolanin, make sort field names consistent, make ignored multiValued. +#375723 by pwolanin, prevent fatal error if available facet list changes. +#373921 by JacobSingh, show pending docs on index page from autocommit. +#371858 by pwolanin, re-fill the Luke cache after we empty it on cron. +#372120 by pwolanin, create one MLT block by default on install of apachesolr_mlt. +#370707 compact field names, create "order by" fields in schema.xml + by pwolanin and Damien Tournoud. +#370796 avoid repeated looping/indexing in apachesolr_index_nodes() by Damien Tournoud. +#369944 Add field aliases and further clean-up the query class, by pwolanin. +#366959 make usage of solrsort consistent in Solr_Base_Query::solrsort by Damien Tournoud. +# Update errant watchdog calls that were using D5 signature by robertDouglass. +#369780 Rearrange code for better organization and performance by robertDouglass. +#365901 Fix bug where indexing might hang & improved API by adding a separate + hook for modules to indicate that a node is excluded, by pwolanin. +#367361 Use the machine-readable name for disabled node types by pwolanin. +#366957 Add a "configure" link to the more like this block by JacobSingh. +#365901 Add a bias on node type (and node-type exclusion) by Damien Tournoud and pwolanin. + +Apache Solr integration 6.x-1.0-beta2, 2009-01-28 (changes since 6.x-1.0-alpha6 2009-Jan-08) +------------------------------ +#365684 Get PHP library from new svn home by pwolanin +#365620 clear stale data on hook_enable by pwolanin +#365312 don't redirect after enabling filters by pwolanin +#365245 invalid foreach when no facets available reported by Damien Tournoud +# don't let attachements be enabled since it seems be broken by pwolanin +#363972 fix ApacheSolr to Apache Solr +#363972 Text improvements for UI by horncologne and pwolanin +#365063 fix module name in admin screen by pwolanin +#365022 fatal error in MLT when no Solr server by pwolanin +#364446 fix space problem and clean up query class by pwolanin +#355525 fix mis-named variables, patch by pwolanin, bug reported by flexer +#339467 centralize/register facets blocks by paul.lovvik, JacobSingh and + pwolanin +#362389 make the _image module work by pwolanin +#364140 fix mlt schema bug, thanks to webrascal +#364384 reorder selects to have bigger numbers at the top by pwolanin, suggested + by horncologne +#350330 make sure to index dates as GMT, thanks to webrascal +#363416 reindex without blowing away either the solr index or the core search index by pwolanin +#360227 strip ctl chars() also on path, reported by flexer +#348215 cleanup - simplify branching, numerically index array by pwolanin +#348215 add missing js file with minor text chenges (js file by vladimir.dolgopolov) +#359923 separate cron limit for apachesolr by pwolanin +# remove lang module - code was already added to apachesolr_search +#292662 commit after we delete the index by pwolanin +#356696 by pwolanin: copies the author's name to a string field for use in + multisite search or sorting by author. Also snuck in non-compression on + the body for performance. +#348215 by vladimir.dolgopolov: More link for additional facets. +#292662 update README by pwolanin +#344249 obey 32 char limit for block deltas by pwolanin +#355479 fix PHP warning when request fails by pwolanin +#355544 Add a ->clearCache(); in apachesolr_index_page(), by flexer and pwolanin diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/Drupal_Apache_Solr_Service.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/Drupal_Apache_Solr_Service.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,903 @@ + + */ + +/** + * Additional code Copyright (c) 2008-2011 by Robert Douglass, James McKinney, + * Jacob Singh, Alejandro Garza, Peter Wolanin, and additional contributors. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program as the file LICENSE.txt; if not, please see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + */ + +/** + * Starting point for the Solr API. Represents a Solr server resource and has + * methods for pinging, adding, deleting, committing, optimizing and searching. + */ + +class DrupalApacheSolrService implements DrupalApacheSolrServiceInterface { + /** + * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values + * are 'map' (default) or 'flat'. + * + */ + const NAMED_LIST_FORMAT = 'map'; + + /** + * Servlet mappings + */ + const PING_SERVLET = 'admin/ping'; + const UPDATE_SERVLET = 'update'; + const SEARCH_SERVLET = 'select'; + const LUKE_SERVLET = 'admin/luke'; + const SYSTEM_SERVLET = 'admin/system'; + const STATS_SERVLET = 'admin/stats.jsp'; + const STATS_SERVLET_4 = 'admin/mbeans?wt=xml&stats=true'; + + /** + * Server url + * + * @var array + */ + protected $parsed_url; + + /** + * Constructed servlet full path URLs + * + * @var string + */ + protected $update_url; + + /** + * Default HTTP timeout when one is not specified (initialized to default_socket_timeout ini setting) + * + * var float + */ + protected $_defaultTimeout; + protected $env_id; + protected $luke; + protected $stats; + protected $system_info; + + /** + * Flag that denotes whether to use soft commits for Solr 4.x, defaults to FALSE. + * + * @var bool + */ + protected $soft_commit = FALSE; + + /** + * Call the /admin/ping servlet, to test the connection to the server. + * + * @param $timeout + * maximum time to wait for ping in seconds, -1 for unlimited (default 2). + * @return + * (float) seconds taken to ping the server, FALSE if timeout occurs. + */ + public function ping($timeout = 2) { + $start = microtime(TRUE); + + if ($timeout <= 0.0) { + $timeout = -1; + } + $pingUrl = $this->_constructUrl(self::PING_SERVLET); + // Attempt a HEAD request to the solr ping url. + $options = array( + 'method' => 'HEAD', + 'timeout' => $timeout, + ); + $response = $this->_makeHttpRequest($pingUrl, $options); + + if ($response->code == 200) { + // Add 0.1 ms to the ping time so we never return 0.0. + return microtime(TRUE) - $start + 0.0001; + } + else { + return FALSE; + } + } + + /** + * Flags whether to use soft commits for Solr 4.x. + * + * @param bool $soft_commit + * Whether or not to use soft commits for Solr 4.x. + */ + public function setSoftCommit($soft_commit) { + $this->soft_commit = (bool) $soft_commit; + } + + /** + * Returns the flag that denotes whether to use soft commits for Solr 4.x. + * + * @return bool + * Whether to use soft commits for Solr 4.x. + */ + public function getSoftCommit() { + return $this->soft_commit; + } + + /** + * Call the /admin/system servlet + * + * @return + * (array) With all the system info + */ + protected function setSystemInfo() { + $url = $this->_constructUrl(self::SYSTEM_SERVLET, array('wt' => 'json')); + if ($this->env_id) { + $this->system_info_cid = $this->env_id . ":system:" . drupal_hash_base64($url); + $cache = cache_get($this->system_info_cid, 'cache_apachesolr'); + if (isset($cache->data)) { + $this->system_info = json_decode($cache->data); + } + } + // Second pass to populate the cache if necessary. + if (empty($this->system_info)) { + $response = $this->_sendRawGet($url); + $this->system_info = json_decode($response->data); + if ($this->env_id) { + cache_set($this->system_info_cid, $response->data, 'cache_apachesolr'); + } + } + } + + /** + * Get information about the Solr Core. + * + * @return + * (string) system info encoded in json + */ + public function getSystemInfo() { + if (!isset($this->system_info)) { + $this->setSystemInfo(); + } + return $this->system_info; + } + + /** + * Sets $this->luke with the meta-data about the index from admin/luke. + */ + protected function setLuke($num_terms = 0) { + if (empty($this->luke[$num_terms])) { + $params = array( + 'numTerms' => "$num_terms", + 'wt' => 'json', + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + $url = $this->_constructUrl(self::LUKE_SERVLET, $params); + if ($this->env_id) { + $cid = $this->env_id . ":luke:" . drupal_hash_base64($url); + $cache = cache_get($cid, 'cache_apachesolr'); + if (isset($cache->data)) { + $this->luke = $cache->data; + } + } + } + // Second pass to populate the cache if necessary. + if (empty($this->luke[$num_terms])) { + $this->luke[$num_terms] = $this->_sendRawGet($url); + if ($this->env_id) { + cache_set($cid, $this->luke, 'cache_apachesolr'); + } + } + } + + /** + * Get just the field meta-data about the index. + */ + public function getFields($num_terms = 0) { + return $this->getLuke($num_terms)->fields; + } + + /** + * Get meta-data about the index. + */ + public function getLuke($num_terms = 0) { + if (!isset($this->luke[$num_terms])) { + $this->setLuke($num_terms); + } + return $this->luke[$num_terms]; + } + + /** + * Get the current solr version. This could be 1, 3 or 4 + * + * @return int + * 1, 3 or 4. Does not give a more details version, for that you need + * to get the system info. + */ + public function getSolrVersion() { + $system_info = $this->getSystemInfo(); + // Get our solr version number + if (isset($system_info->lucene->{'solr-spec-version'})) { + return $system_info->lucene->{'solr-spec-version'}[0]; + } + return 0; + } + + /** + * Sets $this->stats with the information about the Solr Core form + */ + protected function setStats() { + $data = $this->getLuke(); + $solr_version = $this->getSolrVersion(); + // Only try to get stats if we have connected to the index. + if (empty($this->stats) && isset($data->index->numDocs)) { + if ($solr_version >= 4) { + $url = $this->_constructUrl(self::STATS_SERVLET_4); + } + else { + $url = $this->_constructUrl(self::STATS_SERVLET); + } + if ($this->env_id) { + $this->stats_cid = $this->env_id . ":stats:" . drupal_hash_base64($url); + $cache = cache_get($this->stats_cid, 'cache_apachesolr'); + if (isset($cache->data)) { + $this->stats = simplexml_load_string($cache->data); + } + } + // Second pass to populate the cache if necessary. + if (empty($this->stats)) { + $response = $this->_sendRawGet($url); + $this->stats = simplexml_load_string($response->data); + if ($this->env_id) { + cache_set($this->stats_cid, $response->data, 'cache_apachesolr'); + } + } + } + } + + /** + * Get information about the Solr Core. + * + * Returns a Simple XMl document + */ + public function getStats() { + if (!isset($this->stats)) { + $this->setStats(); + } + return $this->stats; + } + + /** + * Get summary information about the Solr Core. + */ + public function getStatsSummary() { + $stats = $this->getStats(); + $solr_version = $this->getSolrVersion(); + + $summary = array( + '@pending_docs' => '', + '@autocommit_time_seconds' => '', + '@autocommit_time' => '', + '@deletes_by_id' => '', + '@deletes_by_query' => '', + '@deletes_total' => '', + '@schema_version' => '', + '@core_name' => '', + '@index_size' => '', + ); + + if (!empty($stats)) { + if ($solr_version <= 3) { + $docs_pending_xpath = $stats->xpath('//stat[@name="docsPending"]'); + $summary['@pending_docs'] = (int) trim(current($docs_pending_xpath)); + $max_time_xpath = $stats->xpath('//stat[@name="autocommit maxTime"]'); + $max_time = (int) trim(current($max_time_xpath)); + // Convert to seconds. + $summary['@autocommit_time_seconds'] = $max_time / 1000; + $summary['@autocommit_time'] = format_interval($max_time / 1000); + $deletes_id_xpath = $stats->xpath('//stat[@name="deletesById"]'); + $summary['@deletes_by_id'] = (int) trim(current($deletes_id_xpath)); + $deletes_query_xpath = $stats->xpath('//stat[@name="deletesByQuery"]'); + $summary['@deletes_by_query'] = (int) trim(current($deletes_query_xpath)); + $summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query']; + $schema = $stats->xpath('/solr/schema[1]'); + $summary['@schema_version'] = trim($schema[0]); + $core = $stats->xpath('/solr/core[1]'); + $summary['@core_name'] = trim($core[0]); + $size_xpath = $stats->xpath('//stat[@name="indexSize"]'); + $summary['@index_size'] = trim(current($size_xpath)); + } + else { + $system_info = $this->getSystemInfo(); + $docs_pending_xpath = $stats->xpath('//lst["stats"]/long[@name="docsPending"]'); + $summary['@pending_docs'] = (int) trim(current($docs_pending_xpath)); + $max_time_xpath = $stats->xpath('//lst["stats"]/str[@name="autocommit maxTime"]'); + $max_time = (int) trim(current($max_time_xpath)); + // Convert to seconds. + $summary['@autocommit_time_seconds'] = $max_time / 1000; + $summary['@autocommit_time'] = format_interval($max_time / 1000); + $deletes_id_xpath = $stats->xpath('//lst["stats"]/long[@name="deletesById"]'); + $summary['@deletes_by_id'] = (int) trim(current($deletes_id_xpath)); + $deletes_query_xpath = $stats->xpath('//lst["stats"]/long[@name="deletesByQuery"]'); + $summary['@deletes_by_query'] = (int) trim(current($deletes_query_xpath)); + $summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query']; + $schema = $system_info->core->schema; + $summary['@schema_version'] = $schema; + $core = $stats->xpath('//lst["core"]/str[@name="coreName"]'); + $summary['@core_name'] = trim(current($core)); + $size_xpath = $stats->xpath('//lst["core"]/str[@name="indexSize"]'); + $summary['@index_size'] = trim(current($size_xpath)); + } + } + + return $summary; + } + + /** + * Clear cached Solr data. + */ + public function clearCache() { + // Don't clear cached data if the server is unavailable. + if (@$this->ping()) { + $this->_clearCache(); + } + else { + throw new Exception('No Solr instance available when trying to clear the cache.'); + } + } + + protected function _clearCache() { + if ($this->env_id) { + cache_clear_all($this->env_id . ":stats:", 'cache_apachesolr', TRUE); + cache_clear_all($this->env_id . ":luke:", 'cache_apachesolr', TRUE); + } + $this->luke = array(); + $this->stats = NULL; + } + + /** + * Constructor + * + * @param $url + * The URL to the Solr server, possibly including a core name. E.g. http://localhost:8983/solr/ + * or https://search.example.com/solr/core99/ + * @param $env_id + * The machine name of a corresponding saved configuration used for loading + * data like which facets are enabled. + */ + public function __construct($url, $env_id = NULL) { + $this->env_id = $env_id; + $this->setUrl($url); + + // determine our default http timeout from ini settings + $this->_defaultTimeout = (int) ini_get('default_socket_timeout'); + + // double check we didn't get 0 for a timeout + if ($this->_defaultTimeout <= 0) { + $this->_defaultTimeout = 60; + } + } + + function getId() { + return $this->env_id; + } + + /** + * Check the reponse code and thow an exception if it's not 200. + * + * @param stdClass $response + * response object. + * + * @return + * response object + * @thows Exception + */ + protected function checkResponse($response) { + $code = (int) $response->code; + if ($code != 200) { + if ($code >= 400 && $code != 403 && $code != 404) { + // Add details, like Solr's exception message. + $response->status_message .= $response->data; + } + throw new Exception('"' . $code . '" Status: ' . $response->status_message); + } + return $response; + } + + /** + * Make a request to a servlet (a path) that's not a standard path. + * + * @param string $servlet + * A path to be added to the base Solr path. e.g. 'extract/tika' + * + * @param array $params + * Any request parameters when constructing the URL. + * + * @param array $options + * @see drupal_http_request() $options. + * + * @return + * response object + * + * @thows Exception + */ + public function makeServletRequest($servlet, $params = array(), $options = array()) { + // Add default params. + $params += array( + 'wt' => 'json', + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + + $url = $this->_constructUrl($servlet, $params); + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making a GET operation against this Solr Server + */ + protected function _sendRawGet($url, $options = array()) { + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making a POST operation against this Solr Server + */ + protected function _sendRawPost($url, $options = array()) { + $options['method'] = 'POST'; + // Normally we use POST to send XML documents. + if (!isset($options['headers']['Content-Type'])) { + $options['headers']['Content-Type'] = 'text/xml; charset=UTF-8'; + } + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making the actual http request to the Solr Server + * + * This is just a wrapper around drupal_http_request(). + */ + protected function _makeHttpRequest($url, array $options = array()) { + if (!isset($options['method']) || $options['method'] == 'GET' || $options['method'] == 'HEAD') { + // Make sure we are not sending a request body. + $options['data'] = NULL; + } + + $result = drupal_http_request($url, $options); + + if (!isset($result->code) || $result->code < 0) { + $result->code = 0; + $result->status_message = 'Request failed'; + $result->protocol = 'HTTP/1.0'; + } + // Additional information may be in the error property. + if (isset($result->error)) { + $result->status_message .= ': ' . check_plain($result->error); + } + + if (!isset($result->data)) { + $result->data = ''; + $result->response = NULL; + } + else { + $response = json_decode($result->data); + if (is_object($response)) { + foreach ($response as $key => $value) { + $result->$key = $value; + } + } + } + return $result; + } + + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * NOTE: inside a phrase fewer characters need escaped, use {@link DrupalApacheSolrService::escapePhrase()} instead + * + * @param string $value + * @return string + */ + static public function escape($value) + { + //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters + $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Escape a value meant to be contained in a phrase for special query characters + * + * @param string $value + * @return string + */ + static public function escapePhrase($value) + { + $pattern = '/("|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Convenience function for creating phrase syntax from a value + * + * @param string $value + * @return string + */ + static public function phrase($value) + { + return '"' . self::escapePhrase($value) . '"'; + } + + /** + * Return a valid http URL given this server's host, port and path and a provided servlet name + * + * @param $servlet + * A string path to a Solr request handler. + * @param $params + * @param $parsed_url + * A url to use instead of the stored one. + * + * @return string + */ + protected function _constructUrl($servlet, $params = array(), $added_query_string = NULL) { + // PHP's built in http_build_query() doesn't give us the format Solr wants. + $query_string = $this->httpBuildQuery($params); + + if ($query_string) { + $query_string = '?' . $query_string; + if ($added_query_string) { + $query_string = $query_string . '&' . $added_query_string; + } + } + elseif ($added_query_string) { + $query_string = '?' . $added_query_string; + } + + $url = $this->parsed_url; + return $url['scheme'] . $url['user'] . $url['pass'] . $url['host'] . $url['port'] . $url['path'] . $servlet . $query_string; + } + + /** + * Get the Solr url + * + * @return string + */ + public function getUrl() { + return $this->_constructUrl(''); + } + + /** + * Set the Solr url. + * + * @param $url + * + * @return $this + */ + public function setUrl($url) { + $parsed_url = parse_url($url); + + if (!isset($parsed_url['scheme'])) { + $parsed_url['scheme'] = 'http'; + } + $parsed_url['scheme'] .= '://'; + + if (!isset($parsed_url['user'])) { + $parsed_url['user'] = ''; + } + else { + $parsed_url['host'] = '@' . $parsed_url['host']; + } + $parsed_url['pass'] = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $parsed_url['port'] = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + + if (isset($parsed_url['path'])) { + // Make sure the path has a single leading/trailing slash. + $parsed_url['path'] = '/' . ltrim($parsed_url['path'], '/'); + $parsed_url['path'] = rtrim($parsed_url['path'], '/') . '/'; + } + else { + $parsed_url['path'] = '/'; + } + // For now we ignore query and fragment. + $this->parsed_url = $parsed_url; + // Force the update url to be rebuilt. + unset($this->update_url); + return $this; + } + + /** + * Raw update Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed xml document. + * + * @param string $rawPost + * @param float $timeout Maximum expected duration (in seconds) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function update($rawPost, $timeout = FALSE) { + // @todo: throw exception if updates are disabled. + if (empty($this->update_url)) { + // Store the URL in an instance variable since many updates may be sent + // via a single instance of this class. + $this->update_url = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => 'json')); + } + $options['data'] = $rawPost; + if ($timeout) { + $options['timeout'] = $timeout; + } + return $this->_sendRawPost($this->update_url, $options); + } + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of ApacheSolrDocument instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * + * @return response objecte + * + * @throws Exception If an error occurs during the service call + */ + public function addDocuments($documents, $overwrite = NULL, $commitWithin = NULL) { + $attr = ''; + + if (isset($overwrite)) { + $attr .= ' overwrite="' . empty($overwrite) ? 'false"' : 'true"'; + } + if (isset($commitWithin)) { + $attr .= ' commitWithin="' . intval($commitWithin) . '"'; + } + + $rawPost = ""; + foreach ($documents as $document) { + if (is_object($document) && ($document instanceof ApacheSolrDocument)) { + $rawPost .= ApacheSolrDocument::documentToXml($document); + } + } + $rawPost .= ''; + + return $this->update($rawPost); + } + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $optimize Defaults to true + * optimizes the index files. Only valid for solr versions <= 3 + * @param boolean $waitFlush + * block until index changes are flushed to disk. Only valid for solr versions <= 3 + * @param boolean $waitSearcher + * block until a new searcher is opened and registered as the main query searcher, making the changes visible. + * @param float $timeout + * Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) { + $optimizeValue = $optimize ? 'true' : 'false'; + $flushValue = $waitFlush ? 'true' : 'false'; + $searcherValue = $waitSearcher ? 'true' : 'false'; + $softCommit = $this->soft_commit ? 'true' : 'false'; + + $solr_version = $this->getSolrVersion(); + if ($solr_version <= 3) { + $rawPost = ''; + } + else { + $rawPost = ''; + } + + $response = $this->update($rawPost, $timeout); + $this->_clearCache(); + return $response; + } + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteById($id, $timeout = 3600) { + return $this->deleteByMultipleIds(array($id), $timeout); + } + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteByMultipleIds($ids, $timeout = 3600) { + $rawPost = ''; + + foreach ($ids as $id) { + $rawPost .= '' . htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8') . ''; + } + $rawPost .= ''; + + return $this->update($rawPost, $timeout); + } + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return stdClass response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteByQuery($rawQuery, $timeout = 3600) { + $rawPost = '' . htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8') . ''; + + return $this->update($rawPost, $timeout); + } + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * block until index changes are flushed to disk Removed in Solr 4.0 + * @param boolean $waitSearcher + * block until a new searcher is opened and registered as the main query searcher, making the changes visible. + * @param float $timeout + * Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function optimize($waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) { + $flushValue = $waitFlush ? 'true' : 'false'; + $searcherValue = $waitSearcher ? 'true' : 'false'; + $softCommit = $this->soft_commit ? 'true' : 'false'; + + $solr_version = $this->getSolrVersion(); + if ($solr_version <= 3) { + $rawPost = ''; + } + else { + $rawPost = ''; + } + + return $this->update($rawPost, $timeout); + } + + /** + * Like PHP's built in http_build_query(), but uses rawurlencode() and no [] for repeated params. + */ + protected function httpBuildQuery(array $query, $parent = '') { + $params = array(); + + foreach ($query as $key => $value) { + $key = ($parent ? $parent : rawurlencode($key)); + + // Recurse into children. + if (is_array($value)) { + $params[] = $this->httpBuildQuery($value, $key); + } + // If a query parameter value is NULL, only append its key. + elseif (!isset($value)) { + $params[] = $key; + } + else { + $params[] = $key . '=' . rawurlencode($value); + } + } + + return implode('&', $params); + } + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function search($query = '', array $params = array(), $method = 'GET') { + // Always use JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning + $params['wt'] = 'json'; + // Additional default params. + $params += array( + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + if ($query) { + $params['q'] = $query; + } + // PHP's built in http_build_query() doesn't give us the format Solr wants. + $queryString = $this->httpBuildQuery($params); + // Check string length of the query string, change method to POST + $len = strlen($queryString); + // Fetch our threshold to find out when to flip to POST + $max_len = apachesolr_environment_variable_get($this->env_id, 'apachesolr_search_post_threshold', 3600); + + // if longer than $max_len (default 3600) characters + // we should switch to POST (a typical server handles 4096 max). + // If this class is used independently (without environments), we switch automatically to POST at an + // limit of 1800 chars. + if (($len > 1800) && (empty($this->env_id) || ($len > $max_len))) { + $method = 'POST'; + } + + if ($method == 'GET') { + $searchUrl = $this->_constructUrl(self::SEARCH_SERVLET, array(), $queryString); + return $this->_sendRawGet($searchUrl); + } + else if ($method == 'POST') { + $searchUrl = $this->_constructUrl(self::SEARCH_SERVLET); + $options['data'] = $queryString; + $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + return $this->_sendRawPost($searchUrl, $options); + } + else { + throw new Exception("Unsupported method '$method' for search(), use GET or POST"); + } + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/LICENSE.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/README.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,194 @@ + +This module integrates Drupal with the Apache Solr search platform. Solr search +can be used as a replacement for core content search and boasts both extra +features and better performance. Among the extra features is the ability to have +faceted search on facets ranging from content author to taxonomy to arbitrary +Field API fields. + +The module comes with a schema.xml, solrconfig.xml, and protwords.txt file which +must be used in your Solr installation. + +This module depends on the search framework in core. When used in combination +with core search module, Apache Solr is not the default search. Access it via a +new tab on the default search page, called "Site". You may configure it +to be default at ?q=admin/config/search/settings + +Updating from 6.x +----------------- + +IMPORTANT: there is no upgrade path from 6.x-1.x or 6.x-2.x. If you previously +installed those modules you must disable and uninstall them prior to +installing 7.x-1.x. + +You will have to install the new schema.xml and solrconfig.xml files, and restart +the Solr server (or core) and delete your index and reindex all content. + +Installation +------------ + +Prerequisite: Java 5 or higher (a.k.a. 1.5.x). PHP 5.2.4 or higher. + +Install the Apache Solr Drupal module as you would any Drupal module. Note +that the Drupal 7.x-1.x branch does not require the SolrPhpClient to +be installed. All necessary code is now included with this module. + +Before enabling the module, you must have a working Solr server, or be +subscribed to a service like Acquia Search. + +The Debian/Ubuntu packages for Solr should NOT be used to install Solr. +For example, do NOT install the solr or solr-jetty packages. + +Download the latest Solr 1.4.x or 3.x release (e.g. 1.4.1 or 3.6.1) from: +http://www.apache.org/dyn/closer.cgi/lucene/solr/ + +Apache Lucene 3.1, 3.2 or 3.3, have a possible index corruption bug on +server crash or power loss (LUCENE-3418) and have bugs that interfere +with the Drupal admin reports. Solr 3.4 has a problem with +SortMissingLast so Solr 3.5.0 or later is strongly preferred. + +Unpack the Solr tarball somewhere not visible to the web (not in your +webserver docroot and not inside of your Drupal directory). + +The Solr download comes with an example application that you can use for +testing, development, and even for smaller production sites. This +application is found at apache-solr-1.4.1/example. + +You must use 3 Solr configuration files that come with the Drupal +module or the integration will not work correctly. + +For Solr 1.4 use the ones found in: +solr-conf/solr-1.4/ + +for Solr 3.5.0 or 3.6.1 use: +solr-conf/solr-3.x/ + +While the Solr 1.4 files will work for Solr 3.5+, they are not optimal +and you will be missing important new features. + +For example, when deploying solr 1.4: + +Move apache-solr-1.4.1/example/solr/conf/schema.xml and rename it to +something like schema.bak. Then move the solr-conf/solr-1.4/schema.xml +that comes with this Drupal module to take its place. + +Similarly, move apache-solr-1.4.1/example/solr/conf/solrconfig.xml and rename +it like solrconfig.bak. Then move the solr-conf/solr-1.4/solrconfig.xml +that comes with this module to take its place. + +Finally, move apache-solr-1.4.1/example/solr/conf/protwords.txt and rename it +protwords.bak. Then move the solr-conf/solr-1.4/protwords.txt that comes +with this module to take its place. + +Make sure that the conf directory includes the following files - the Solr core +may not load if you don't have at least an empty file present: +solrconfig.xml +schema.xml +elevate.xml +mapping-ISOLatin1Accent.txt +protwords.txt +stopwords.txt +synonyms.txt + +Now start the solr application by opening a shell, changing directory to +apache-solr-1.4.1/example, and executing the command java -jar start.jar + +Test that your solr server is now available by visiting +http://localhost:8983/solr/admin/ + +Now, you should enable the "Apache Solr framework" and "Apache Solr search" +modules. Check that you can connect to Solr at ?q=admin/setting/apachesolr +Now run cron on your Drupal site until your content is indexed. You +can monitor the index at ?q=admin/settings/apachesolr/index + +The solrconfig.xml that comes with this modules defines auto-commit, so +it may take a few minutes between running cron and when the new content +is visible in search. + +To use facets you should download facetapi http://drupal.org/project/facetapi +This module will allow you to define and set facets next to your search pages. +Once this module is enabled, enable blocks for facets first at +Administer > Site configuration > Apache Solr > Enabled filters +then position them as you like at Administer > Site building > Blocks. + +Settings.php +------------ +You can override environment settings using the following syntax in your +settings.php + +$conf['apachesolr_environments']['my_env_id']['url'] = 'http://localhost:8983'; + +Configuration variables +----------------------- + +The module provides some (hidden) variables that can be used to tweak its +behavior: + + - apachesolr_luke_limit: the limit (in terms of number of documents in the + index) above which the module will not retrieve the number of terms per field + when performing LUKE queries (for performance reasons). + + - apachesolr_tags_to_index: the list of HTML tags that the module will index + (see apachesolr_add_tags_to_document()). + + - apachesolr_exclude_nodeapi_types: an array of node types each of which is + an array of one or more module names, such as 'comment'. Any type listed + will have any listed modules' hook_node_update_index() implementation skipped + when indexing. This can be useful for excluding comments or taxonomy links. + + - apachesolr_ping_timeout: the timeout (in seconds) after which the module will + consider the Apache Solr server unavailable. + + - apachesolr_optimize_interval: the interval (in seconds) between automatic + optimizations of the Apache Solr index. Set to 0 to disable. + + - apachesolr_cache_delay: the interval (in seconds) after an update after which + the module will requery the Apache Solr for the index structure. Set it to + your autocommit delay plus a few seconds. + + - apachesolr_query_class: the default query class to use. + + - apachesolr_index_comments_with_node: TRUE | FALSE. Whether to index comments + along with each node. + + - apachesolr_cron_mass_limit: update or delete at most this many documents in + each Solr request, such as when making {apachesolr_search_node} consistent + with {node}. + + - apachesolr_index_user: Define with which user you want the index process to + happen. + +Troubleshooting +--------------- +Problem: +You use http basic auth to limit access to your Solr server. + +Solution: +Set the Server URL to include the username and password like +http://username:password@example.com:8080/solr + +Problem: +Links to nodes appear in the search results with a different host name or +subdomain than is preferred. e.g. sometimes at http://example.com +and sometimes at http://www.example.com + +Solution: +Set $base_url in settings.php to insure that an identical absolute url is +generated at all times when nodes are indexed. Alternately, set up a re-direct +in .htaccess to prevent site visitors from accessing the site via more than one +site address. + +Problem: +The 'Solr Index Queries' test fails with file permission errors. + +Solution: +When running this test you should have your tomcat/jetty running as the same user +as the user under which PHP runs (often the same as the webserver). This is +important because of the on-the-fly folder creation within PHP. + + +Themers +---------------- + +See inline docs in apachesolr_theme and apachesolr_search_theme functions +within apachesolr.module and apachesolr_search.module. + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/Solr_Base_Query.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/Solr_Base_Query.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,657 @@ + 'is_uid', '#value' => 0) + * for anonymous content. + */ + protected $fields = array(); + + /** + * An array of subqueries. + */ + protected $subqueries = array(); + + function __construct($operator = 'OR') { + $this->operator = $operator; + $this->id = ++SolrFilterSubQuery::$idCount; + } + + function __clone() { + $this->id = ++SolrFilterSubQuery::$idCount; + } + + public function getFilters($name = NULL) { + if (empty($name)) { + return $this->fields; + } + reset($this->fields); + $matches = array(); + foreach ($this->fields as $filter) { + if ($filter['#name'] == $name) { + $matches[] = $filter; + } + } + return $matches; + } + + public function hasFilter($name, $value, $exclude = FALSE) { + foreach ($this->fields as $pos => $values) { + if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) { + return TRUE; + } + } + return FALSE; + } + + public function addFilter($name, $value, $exclude = FALSE, $local = '') { + // @todo - escape the value if it has spaces in it and is not a range query or parenthesized. + $filter = array( + '#exclude' => (bool) $exclude, + '#name' => trim($name), + '#value' => trim($value), + '#local' => trim($local), + ); + $this->fields[] = $filter; + return $this; + } + + public function removeFilter($name, $value = NULL, $exclude = FALSE) { + // Remove from the public list of filters. + $this->unsetFilter($this->fields, $name, $value, $exclude); + return $this; + } + + protected function unsetFilter(&$fields, $name, $value, $exclude) { + if (!isset($value)) { + foreach ($fields as $pos => $values) { + if ($values['#name'] == $name) { + unset($fields[$pos]); + } + } + } + else { + foreach ($fields as $pos => $values) { + if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) { + unset($fields[$pos]); + } + } + } + } + + public function getFilterSubQueries() { + return $this->subqueries; + } + + public function addFilterSubQuery(SolrFilterSubQuery $query) { + $this->subqueries[$query->id] = $query; + return $this; + } + + public function removeFilterSubQuery(SolrFilterSubQuery $query) { + unset($this->subqueries[$query->id]); + return $this; + } + + public function removeFilterSubQueries() { + $this->subqueries = array(); + return $this; + } + + public function makeFilterQuery(array $filter) { + $prefix = empty($filter['#exclude']) ? '' : '-'; + if ($filter['#local']) { + $prefix = '{!' . $filter['#local'] . '}' . $prefix; + } + // If the field value contains a colon or a space, wrap it in double quotes, + // unless it is a range query or is already wrapped in double quotes or + // parentheses. + if (preg_match('/[ :]/', $filter['#value']) && !preg_match('/^[\[\{]\S+ TO \S+[\]\}]$/', $filter['#value']) && !preg_match('/^["\(].*["\)]$/', $filter['#value'])) { + $filter['#value'] = '"' . $filter['#value'] . '"'; + } + return $prefix . $filter['#name'] . ':' . $filter['#value']; + } + + /** + * Make sure our query matches the pattern name:value or name:"value" + * Make sure that if we are ranges we use name:[ AND ] + * allowed inputs : + * a. bundle:article + * b. date:[1970-12-31T23:59:59Z TO NOW] + * Split the text in 4 different parts + * 1. name, eg.: bundle or date + * 2. The first opening bracket (or nothing), eg.: [ + * 3. The value of the field, eg. article or 1970-12-31T23:59:59Z TO NOW + * 4. The last closing bracket, eg.: ] + * @param string $filter + * The filter to validate + * @return boolean + */ + public static function validFilterValue($filter) { + $opening = 0; + $closing = 0; + $name = NULL; + $value = NULL; + + if (preg_match('/(?P[^:]+):(?P.+)?$/', $filter, $matches)) { + foreach ($matches as $match_id => $match) { + switch($match_id) { + case 'name' : + $name = $match; + break; + case 'value' : + $value = $match; + break; + } + } + + // For the name we allow any character that fits between the A-Z0-9 range and + // any alternative for this in other languages. No special characters allowed + if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) { + return FALSE; + } + + // For the value we allow anything that is UTF8 + if (!drupal_validate_utf8($value)) { + return FALSE; + } + + // Check our bracket count. If it does not match it is also not valid + $valid_brackets = TRUE; + $brackets['opening']['{'] = substr_count($value, '{'); + $brackets['closing']['}'] = substr_count($value, '}'); + $valid_brackets = ($brackets['opening']['{'] != $brackets['closing']['}']) ? FALSE : TRUE; + $brackets['opening']['['] = substr_count($value, '['); + $brackets['closing'][']'] = substr_count($value, ']'); + $valid_brackets = ($brackets['opening']['['] != $brackets['closing'][']']) ? FALSE : TRUE; + $brackets['opening']['('] = substr_count($value, '('); + $brackets['closing'][')'] = substr_count($value, ')'); + $valid_brackets = ($brackets['opening']['('] != $brackets['closing'][')']) ? FALSE : TRUE; + if (!$valid_brackets) { + return FALSE; + } + + // Check the date field inputs + if (preg_match('/\[(.+) TO (.+)\]$/', $value, $datefields)) { + // Only Allow a value in the form of + // http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html + // http://lucene.apache.org/solr/api/org/apache/solr/util/DateMathParser.html + // http://wiki.apache.org/solr/SolrQuerySyntax + // 1976-03-06T23:59:59.999Z (valid) + // * (valid) + // 1995-12-31T23:59:59.999Z (valid) + // 2007-03-06T00:00:00Z (valid) + // NOW-1YEAR/DAY (valid) + // NOW/DAY+1DAY (valid) + // 1976-03-06T23:59:59.999Z (valid) + // 1976-03-06T23:59:59.999Z+1YEAR (valid) + // 1976-03-06T23:59:59.999Z/YEAR (valid) + // 1976-03-06T23:59:59.999Z (valid) + // 1976-03-06T23::59::59.999Z (invalid) + if (!empty($datefields[1]) && !empty($datefields[2])) { + // Do not check to full value, only the splitted ones + unset($datefields[0]); + // Check if both matches are valid datefields + foreach ($datefields as $datefield) { + if (!preg_match('/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:[\d\.]{2,6}Z(\S)*)|(^([A-Z\*]+)(\A-Z0-9\+\-\/)*)/', $datefield, $datefield_match)) { + return FALSE; + } + } + } + } + } + return TRUE; + } + + /** + * Builds a set of filter queries from $this->fields and all subqueries. + * + * Returns an array of strings that can be combined into + * a URL query parameter or passed to Solr as fq paramters. + */ + protected function rebuildFq() { + $fq = array(); + foreach ($this->fields as $pos => $field) { + $fq[] = $this->makeFilterQuery($field); + } + foreach ($this->subqueries as $subquery) { + $subfq = $subquery->rebuildFq(); + if ($subfq) { + $operator = $subquery->operator; + $fq[] = "(" . implode(" $operator ", $subfq) . ")"; + } + } + return $fq; + } + +} + +class SolrBaseQuery extends SolrFilterSubQuery implements DrupalSolrQueryInterface { + + /** + * The parameters that get sent to Solr. + */ + protected $params = array('start' => 0, 'rows' => 10, 'fq' => array()); + + /** + * The search base path. + */ + protected $base_path; + protected $field_map = array(); + + /** + * DrupalApacheSolrService object + */ + protected $solr; + // The array keys must always be real Solr index fields. + protected $available_sorts; + + /** + * The query name is used to construct a searcher string. Mostly the + * environment id + */ + protected $name; + protected $context = array(); + // Makes sure we always have a valid sort. + protected $solrsort = array('#name' => 'score', '#direction' => 'desc'); + // A flag to allow the search to be aborted. + public $abort_search = FALSE; + + // A flag to check if need to retrieve another page of the result set + public $page = 0; + + /** + * @param $name + * The search name, used for finding the correct blocks and other config. + * Typically "apachesolr". + * + * @param $solr + * An instantiated DrupalApacheSolrService Object. + * Can be instantiated from apachesolr_get_solr(). + * + * @param $params + * Array of params to initialize the object (typically 'q' and 'fq'). + * + * @param $sortstring + * Visible string telling solr how to sort - added to GET query params. + * + * @param $base_path + * The search base path (without the keywords) for this query, without trailing slash. + */ + function __construct($name, $solr, array $params = array(), $sortstring = '', $base_path = '', $context = array()) { + parent::__construct(); + $this->name = $name; + $this->solr = $solr; + $this->addContext((array) $context); + $this->addParams((array) $params); + $this->available_sorts = $this->defaultSorts(); + $this->sortstring = trim($sortstring); + $this->parseSortString(); + $this->base_path = $base_path; + } + + protected function defaultSorts() { + return array( + 'score' => array('title' => t('Relevancy'), 'default' => 'desc'), + 'sort_label' => array('title' => t('Title'), 'default' => 'asc'), + 'bundle' => array('title' => t('Type'), 'default' => 'asc'), + 'sort_name' => array('title' => t('Author'), 'default' => 'asc'), + 'ds_created' => array('title' => t('Date'), 'default' => 'desc'), + ); + } + + /** + * Get query name. + */ + public function getName() { + return $this->name; + } + + /** + * Get query searcher name (for facetapi, views, pages, etc). + */ + public function getSearcher() { + return $this->name . '@' . $this->solr->getId(); + } + + /** + * Get context values. + */ + public function getContext() { + return $this->context; + } + + /** + * Set context value. + */ + public function addContext(array $context) { + foreach ($context as $k => $v) { + $this->context[$k] = $v; + } + // The env_id must match that of the actual $solr object + $this->context['env_id'] = $this->solr->getId(); + return $this->context; + } + + protected $single_value_params = array( + 'q' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q + 'q.op' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q.op + 'q.alt' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q + 'df' => TRUE, + 'qt' => TRUE, + 'defType' => TRUE, + 'timeAllowed' => TRUE, + 'omitHeader' => TRUE, + 'debugQuery' => TRUE, + 'start' => TRUE, + 'rows' => TRUE, + 'stats' => TRUE, + 'facet' => TRUE, + 'facet.prefix' => TRUE, + 'facet.limit' => TRUE, + 'facet.offset' => TRUE, + 'facet.mincount' => TRUE, + 'facet.missing' => TRUE, + 'facet.method' => TRUE, + 'facet.enum.cache.minDf' => TRUE, + 'facet.date.start' => TRUE, + 'facet.date.end' => TRUE, + 'facet.date.gap' => TRUE, + 'facet.date.hardend' => TRUE, + 'facet.date.other' => TRUE, + 'facet.date.include' => TRUE, + 'hl' => TRUE, + 'hl.snippets' => TRUE, + 'hl.fragsize' => TRUE, + 'hl.mergeContiguous' => TRUE, + 'hl.requireFieldMatch' => TRUE, + 'hl.maxAnalyzedChars' => TRUE, + 'hl.alternateField' => TRUE, + 'hl.maxAlternateFieldLength' => TRUE, + 'hl.formatter' => TRUE, + 'hl.simple.pre/hl.simple.post' => TRUE, + 'hl.fragmenter' => TRUE, + 'hl.fragListBuilder' => TRUE, + 'hl.fragmentsBuilder' => TRUE, + 'hl.useFastVectorHighlighter' => TRUE, + 'hl.usePhraseHighlighter' => TRUE, + 'hl.highlightMultiTerm' => TRUE, + 'hl.regex.slop' => TRUE, + 'hl.regex.pattern' => TRUE, + 'hl.regex.maxAnalyzedChars' => TRUE, + 'spellcheck' => TRUE, + ); + + public function getParam($name) { + if ($name == 'fq') { + return $this->rebuildFq(); + } + $empty = isset($this->single_value_params[$name]) ? NULL : array(); + return isset($this->params[$name]) ? $this->params[$name] : $empty; + } + + public function getParams() { + $params = $this->params; + $params['fq'] = $this->rebuildFq(); + return $params; + } + + public function getSolrParams() { + $params = $this->getParams(); + // For certain fields Solr prefers a comma separated list. + foreach (array('fl', 'hl.fl', 'sort', 'mlt.fl') as $name) { + if (isset($params[$name])) { + $params[$name] = implode(',', $params[$name]); + } + } + return $params; + } + + protected function addFq($string, $index = NULL) { + $string = trim($string); + $local = ''; + $exclude = FALSE; + $name = NULL; + $value = NULL; + + // Check if we are dealing with an exclude + if (preg_match('/^-(.*)/', $string, $matches)) { + $exclude = TRUE; + $string = $matches[1]; + } + + // If {!something} is found as first character then this is a local value + if (preg_match('/\{!([^}]+)\}(.*)/', $string, $matches)) { + $local = $matches[1]; + $string = $matches[2]; + } + + // Anything that has a name and value + // check if we have a : in the string + if (strstr($string, ':')) { + list($name, $value) = explode(":", $string, 2); + } + else { + $value = $string; + } + $this->addFilter($name, $value, $exclude, $local); + return $this; + } + + public function addParam($name, $value) { + if (isset($this->single_value_params[$name])) { + if (is_array($value)) { + $value = end($value); + } + $this->params[$name] = $this->normalizeParamValue($value); + return $this; + } + // We never actually populate $this->params['fq']. Instead + // we manage everything via the filter methods. + if ($name == 'fq') { + if (is_array($value)) { + array_walk_recursive($value, array($this, 'addFq')); + return $this; + } + else { + return $this->addFq($value); + } + } + + if (!isset($this->params[$name])) { + $this->params[$name] = array(); + } + + if (!is_array($value)) { + // Convert to array for array_map. + $param_values = array($value); + } + else { + // Convert to a numerically keyed array. + $param_values = array_values($value); + } + $this->params[$name] = array_merge($this->params[$name], array_map(array($this, 'normalizeParamValue'), $param_values)); + + return $this; + } + + protected function normalizeParamValue($value) { + // Convert boolean to string. + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + // Convert to trimmed string. + return trim($value); + } + + public function addParams(Array $params) { + foreach ($params as $name => $value) { + $this->addParam($name, $value); + } + return $this; + } + + public function removeParam($name) { + unset($this->params[$name]); + if ($name == 'fq') { + $this->fields = array(); + $this->subqueries = array(); + } + return $this; + } + + public function replaceParam($name, $value) { + $this->removeParam($name); + return $this->addParam($name, $value); + } + + /** + * Handles aliases for field to make nicer URLs. + * + * @param $field_map + * An array keyed with real Solr index field names with the alias as value. + * + * @return DrupalSolrQueryInterface + * The called object. + */ + public function addFieldAliases($field_map) { + $this->field_map = array_merge($this->field_map, $field_map); + // We have to re-parse the filters. + $this->parseSortString(); + return $this; + } + + public function getFieldAliases() { + return $this->field_map; + } + + public function clearFieldAliases() { + $this->field_map = array(); + // We have to re-parse the filters. + $this->parseSortString(); + return $this; + } + + protected function parseSortString() { + // Substitute any field aliases with real field names. + $sortstring = strtr($this->sortstring, $this->field_map); + // Score is a special case - it's the default sort for Solr. + if ('' == $sortstring || 'score desc' == $sortstring) { + $this->solrsort['#name'] = 'score'; + $this->solrsort['#direction'] = 'desc'; + unset($this->params['sort']); + } + else { + // Validate and set sort parameter + $fields = implode('|', array_keys($this->available_sorts)); + if (preg_match('/^(?:(' . $fields . ') (asc|desc),?)+$/', $sortstring, $matches)) { + // We only use the last match. + $this->solrsort['#name'] = $matches[1]; + $this->solrsort['#direction'] = $matches[2]; + $this->params['sort'] = array($sortstring); + } + } + } + + public function getAvailableSorts() { + return $this->available_sorts; + } + + public function setAvailableSort($name, $sort) { + // We expect non-aliased sorts to be added. + $this->available_sorts[$name] = $sort; + // Re-parse the sortstring. + $this->parseSortString(); + return $this; + } + + public function setAvailableSorts($sorts) { + // We expect a complete array of valid sorts. + $this->available_sorts = $sorts; + $this->parseSortString(); + return $this; + } + + public function removeAvailableSort($name) { + unset($this->available_sorts[$name]); + // Re-parse the sortstring. + $this->parseSortString(); + return $this; + } + + public function getSolrsort() { + return $this->solrsort; + } + + public function setSolrsort($name, $direction) { + $this->sortstring = trim($name) . ' ' . trim($direction); + $this->parseSortString(); + return $this; + } + + public function getPath($new_keywords = NULL) { + if (isset($new_keywords)) { + return $this->base_path . '/' . $new_keywords; + } + elseif ($this->getParam('q')) { + return $this->base_path . '/' . $this->getParam('q'); + } + else { + // Return with empty query (the slash). The path for a facet + // becomes $this->base_path . '//facetinfo'; + // We do this so we can have a consistent way of retrieving the query + + // additional parameters + return $this->base_path . '/'; + } + } + + public function getSolrsortUrlQuery() { + $queryvalues = array(); + $solrsort = $this->solrsort; + if ($solrsort && ($solrsort['#name'] != 'score')) { + if (isset($this->field_map[$solrsort['#name']])) { + $solrsort['#name'] = $this->field_map[$solrsort['#name']]; + } + $queryvalues['solrsort'] = $solrsort['#name'] . ' ' . $solrsort['#direction']; + } + else { + // Return to default relevancy sort. + unset($queryvalues['solrsort']); + } + return $queryvalues; + } + + public function search($keys = NULL) { + if ($this->abort_search) { + return NULL; + } + return $this->solr->search($keys, $this->getSolrParams()); + } + + public function solr($method) { + return $this->solr->$method(); + } + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1342 @@ + 'value', + '#value' => $environment['env_id'], + ); + if (isset($environment['export_type']) && $environment['export_type'] == 3) { + $verb = t('Revert'); + } + else { + $verb = t('Delete'); + } + return confirm_form( + $form, + t('Are you sure you want to !verb search environment %name?', array('%name' => $environment['name'], '!verb' => strtolower($verb))), + 'admin/config/search/apachesolr', + t('This action cannot be undone.'), + $verb, + t('Cancel') + ); +} + +/** + * Submit handler for the delete form + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_delete_form_submit(array $form, array &$form_state) { + if (apachesolr_environment_delete($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was deleted')); + } + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; +} + +function apachesolr_environment_edit_delete_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $form_state['values']['env_id'] . '/delete'; + + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Settings page for a specific environment (or default one if not provided) + * + * @param array|bool $environment + * + * @return array Render array for a settings page + */ +function apachesolr_environment_settings_page(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Initializes output with information about which environment's setting we are + // editing, as it is otherwise not transparent to the end user. + $output = array( + 'apachesolr_environment' => array( + '#theme' => 'apachesolr_settings_title', + '#env_id' => $env_id, + ), + ); + $output['form'] = drupal_get_form('apachesolr_environment_edit_form', $environment); + return $output; +} + +/** + * Form to clone a certain environment + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return array output of confirm_form() + */ +function apachesolr_environment_clone_form(array $form, array &$form_state, array $environment) { + $form['env_id'] = array( + '#type' => 'value', + '#value' => $environment['env_id'], + ); + return confirm_form( + $form, + t('Are you sure you want to clone search environment %name?', array('%name' => $environment['name'])), + 'admin/config/search/apachesolr', + '', + t('Clone'), + t('Cancel') + ); +} + +/** + * Submit handler for the clone form + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_clone_form_submit(array $form, array &$form_state) { + if (apachesolr_environment_clone($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was cloned')); + } + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; +} + +/** + * Submit handler for the confirmation page of cloning an environment + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_clone_submit(array $form, array &$form_state) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $form_state['values']['env_id'] . '/clone'; +} + +/** + * Form builder for adding/editing a Solr environment used as a menu callback. + */ +function apachesolr_environment_edit_form(array $form, array &$form_state, array $environment = array()) { + if (empty($environment)) { + $environment = array(); + } + $environment += array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '', 'conf' => array()); + + $form['#environment'] = $environment; + $form['url'] = array( + '#type' => 'textfield', + '#title' => t('Solr server URL'), + '#default_value' => $environment['url'], + '#description' => t('Example: http://localhost:8983/solr'), + '#required' => TRUE, + ); + $is_default = $environment['env_id'] == apachesolr_default_environment(); + $form['make_default'] = array( + '#type' => 'checkbox', + '#title' => t('Make this Solr search environment the default'), + '#default_value' => $is_default, + '#disabled' => $is_default, + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Description'), + '#default_value' => $environment['name'], + '#required' => TRUE, + ); + $form['env_id'] = array( + '#type' => 'machine_name', + '#title' => t('Environment id'), + '#machine_name' => array( + 'exists' => 'apachesolr_environment_load', + ), + '#default_value' => $environment['env_id'], + '#disabled' => !empty($environment['env_id']), // Cannot change it once set. + '#description' => t('Unique, machine-readable identifier for this Solr environment.'), + '#required' => TRUE, + ); + $form['service_class'] = array( + '#type' => 'value', + '#value' => $environment['service_class'], + ); + $form['conf'] = array( + '#tree' => TRUE, + ); + $form['conf']['apachesolr_read_only'] = array( + '#type' => 'radios', + '#title' => t('Index write access'), + '#default_value' => isset($environment['conf']['apachesolr_read_only']) ? $environment['conf']['apachesolr_read_only'] : APACHESOLR_READ_WRITE, + '#options' => array(APACHESOLR_READ_WRITE => t('Read and write (normal)'), APACHESOLR_READ_ONLY => t('Read only')), + '#description' => t('Read only stops this site from sending updates to this search environment. Useful for development sites.'), + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['save'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_submit'), + '#value' => t('Save'), + ); + $form['actions']['save_edit'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_submit'), + '#value' => t('Save and edit'), + ); + $form['actions']['test'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_test_submit'), + '#value' => t('Test connection'), + ); + if (!empty($environment['env_id']) && !$is_default) { + $form['actions']['delete'] = array( + '#type' => 'submit', + '#submit' => array('apachesolr_environment_edit_delete_submit'), + '#value' => t('Delete'), + ); + } + + // Ensures destination is an internal URL, builds "cancel" link. + if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { + $destination = $_GET['destination']; + } + else { + $destination = 'admin/config/search/apachesolr/settings'; + } + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => $destination, + ); + + return $form; +} + +/** + * Submit handler for the test button in the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_test_submit(array $form, array &$form_state) { + $ping = apachesolr_server_status($form_state['values']['url'], $form_state['values']['service_class']); + if ($ping) { + drupal_set_message(t('Your site has contacted the Apache Solr server.')); + } + else { + drupal_set_message(t('Your site was unable to contact the Apache Solr server.'), 'error'); + } + $form_state['rebuild'] = TRUE; +} + +/** + * Validate handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_validate(array $form, array &$form_state) { + $parts = parse_url($form_state['values']['url']); + foreach (array('scheme', 'host', 'path') as $key) { + if (empty($parts[$key])) { + form_set_error('url', t('The Solr server URL needs to include a !part', array('!part' => $key))); + } + } + if (isset($parts['port'])) { + // parse_url() should always give an integer for port. Since drupal_http_request() + // also uses parse_url(), we don't need to validate anything except the range. + $pattern = empty($parts['user']) ? '@://[^:]+:([^/]+)@' : '#://[^@]+@[^:]+:([^/]+)#'; + preg_match($pattern, $form_state['values']['url'], $m); + if (empty($m[1]) || !ctype_digit($m[1]) || $m[1] < 1 || $m[1] > 65535) { + form_set_error('port', t('The port has to be an integer between 1 and 65535.')); + } + else { + // Normalize the url by removing extra slashes and whitespace. + $form_state['values']['url'] = trim($form_state['values']['url'], "/ \t\r\n\0\x0B"); + } + } +} + +/** + * Submit handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_submit(array $form, array &$form_state) { + apachesolr_environment_save($form_state['values']); + if (!empty($form_state['values']['make_default'])) { + apachesolr_set_default_environment($form_state['values']['env_id']); + } + cache_clear_all('apachesolr:environments', 'cache_apachesolr'); + drupal_set_message(t('The %name search environment has been saved.', array('%name' => $form_state['values']['name']))); + if ($form_state['values']['op'] == t('Save')) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; + } + else { + $form_state['redirect'] = current_path(); + } + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Check to see if the facetapi module is installed, and if not put up + * a message. + * + * Only call this function if the user is already in a position for this to + * be useful. + */ +function apachesolr_check_facetapi() { + if (!module_exists('facetapi')) { + $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'facetapi'", 0, 1) + ->fetchField(); + if ($filename && file_exists($filename)) { + drupal_set_message(t('If you enable the facetapi module, Apache Solr Search will provide you with configurable facets.', array('@modules' => url('admin/modules')))); + } + else { + drupal_set_message(t('If you install the facetapi module from !href, Apache Solr Search will provide you with configurable facets.', array('!href' => url('http://drupal.org/project/facetapi')))); + } + } +} + +/** + * Form builder for general settings used as a menu callback. + * + * @param array $form + * @param array $form_state + * + * @return array Output of the system_settings_form() + */ +function apachesolr_settings(array $form, array &$form_state) { + $form = array(); + $rows = array(); + + // Environment settings + $id = apachesolr_default_environment(); + $environments = apachesolr_load_all_environments(); + $default_environment = apachesolr_default_environment(); + apachesolr_check_facetapi(); + + // Reserve a row for the default one + $rows[$default_environment] = array(); + + foreach ($environments as $environment_id => $data) { + // Define all the Operations + $confs = array(); + $ops = array(); + // Whenever facetapi is enabled we also enable our operation link + if (module_exists('facetapi')) { + $confs['facets'] = array( + 'class' => 'operation', + 'data' => l(t('Facets'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/facets', + array('query' => array('destination' => current_path())) + ), + ); + } + // These are our result and bias settings + if (module_exists('apachesolr_search')) { + $confs['result_bias'] = array( + 'class' => 'operation', + 'data' => l(t('Bias'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/bias', + array('query' => array('destination' => current_path())) + ), + ); + } + $confs['index'] = array( + 'class' => 'operation', + 'data' => l(t('Index'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/index' + ), + ); + $ops['edit'] = array( + 'class' => 'operation', + 'data' => l(t('Edit'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/edit', + array('query' => array('destination' => current_path())) + ), + ); + + $ops['clone'] = array( + 'class' => 'operation', + 'data' => l(t('Clone'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/clone', + array('query' => array('destination' => $_GET['q'])) + ), + ); + $env_name = l($data['name'], 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/edit', array('query' => array('destination' => $_GET['q']))); + + // Is this row our default environment? + if ($environment_id == $default_environment) { + $env_name = t('!environment (Default)', array('!environment' => $env_name)); + $env_class_row = 'default-environment'; + } + else { + $env_class_row = ''; + } + // For every non-default we add a delete link + // Allow to revert a search environment or to delete it + $delete_value = ''; + if (!isset($data['in_code_only'])) { + if ((isset($data['type']) && $data['type'] == 'Overridden')) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Revert'), 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/delete'), + ); + } + // don't allow the deletion of the default environment + elseif ($environment_id != $default_environment) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Delete'), 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/delete'), + ); + } + } + $ops['delete'] = $delete_value; + + // When we are receiving a http POST (so the page does not show) we do not + // want to check the statusses of any environment + $class = ''; + if (empty($form_state['input'])) { + $class = apachesolr_server_status($data['url'], $data['service_class']) ? 'ok' : 'error'; + } + + $headers = array( + array('data' => t('Name'), 'colspan' => 2), + t('URL'), + array('data' => t('Configuration'), 'colspan' => count($confs)), + array('data' => t('Operations'), 'colspan' => count($ops)), + ); + + $rows[$environment_id] = array('data' => + array( + // Cells + array( + 'class' => 'status-icon', + 'data' => '
' . $class . '
', + ), + array( + 'class' => $env_class_row, + 'data' => $env_name, + ), + check_plain($data['url']), + ), + 'class' => array(drupal_html_class($class)), + ); + // Add the links to the page + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $confs); + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $ops); + } + + $form['apachesolr_host_settings']['actions'] = array( + '#markup' => '', + ); + $form['apachesolr_host_settings']['table'] = array( + '#theme' => 'table', + '#header' => $headers, + '#rows' => array_values($rows), + '#attributes' => array('class' => array('admin-apachesolr')), + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced configuration'), + '#collapsed' => TRUE, + '#collapsible' => TRUE, + ); + $form['advanced']['apachesolr_set_nodeapi_messages'] = array( + '#type' => 'radios', + '#title' => t('Extra help messages for administrators'), + '#description' => t('Adds notices to a page whenever Drupal changed content that needs reindexing'), + '#default_value' => variable_get('apachesolr_set_nodeapi_messages', 1), + '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), + ); + + // Number of Items to index + $numbers = drupal_map_assoc(array(1, 5, 10, 20, 50, 100, 200)); + $default_cron_limit = variable_get('apachesolr_cron_limit', 50); + + // apachesolr_cron_limit may be overridden in settings.php. If its current + // value is not among the default set of options, add it. + if (!isset($numbers[$default_cron_limit])) { + $numbers[$default_cron_limit] = $default_cron_limit; + } + $form['advanced']['apachesolr_cron_limit'] = array( + '#type' => 'select', + '#title' => t('Number of items to index per cron run'), + '#default_value' => $default_cron_limit, + '#options' => $numbers, + '#description' => t('Reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) + ); + + $options = array('apachesolr:show_error' => t('Show error message')); + $system_info = system_get_info('module'); + if (module_exists('search')) { + foreach (search_get_info() as $module => $search_info) { + // Don't allow apachesolr to return results on failure of apachesolr. + if ($module == 'apachesolr_search') { + continue; + } + $options[$module] = t('Show @name search results', array('@name' => $system_info[$module]['name'])); + } + } + + $options['apachesolr:show_no_results'] = t('Show no results'); + $form['advanced']['apachesolr_failure'] = array( + '#type' => 'select', + '#title' => t('On failure'), + '#options' => $options, + '#default_value' => variable_get('apachesolr_failure', 'apachesolr:show_error'), + ); + + return system_settings_form($form); +} + +/** + * Gets information about the fields already in solr index. + * + * @param array $environment + * The environment for which we need to ask the status from + * + * @return array page render array + */ +function apachesolr_status_page($environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + else { + $env_id = $environment['env_id']; + } + + // Check for availability + if (!apachesolr_server_status($environment['url'], $environment['service_class'])) { + drupal_set_message(t('The server seems to be unavailable. Please verify the server settings at the settings page', array('!settings_page' => url("admin/config/search/apachesolr/settings/{$environment['env_id']}/edit", array('query' => drupal_get_destination())))), 'warning'); + return ''; + } + + try { + $solr = apachesolr_get_solr($environment["env_id"]); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + $data = new stdClass; + $data->fields = array(); + } + + $messages = array(); + if (isset($data->index->numDocs)) { + try { + // Collect the stats + $stats_summary = $solr->getStatsSummary(); + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $status = apachesolr_index_status($environment["env_id"]); + // We need a schema version greater than beta3. This is mostly to catch + // people using the Drupal 6 schema. + if (preg_match('/^drupal-[13]/', $stats_summary['@schema_version'])) { + $minimum = 'drupal-3.0-beta4'; + if (version_compare($stats_summary['@schema_version'], $minimum, '<')) { + drupal_set_message(t('Your schema.xml version is too old. You must update it to at least %minimum and re-index your content.', array('%minimum' => $minimum)), 'error'); + } + } + $pending_msg = $stats_summary['@pending_docs'] ? t('(@pending_docs sent but not yet processed)', $stats_summary) : ''; + $index_msg = $stats_summary['@index_size'] ? t('(@index_size on disk)', $stats_summary) : ''; + $indexed_message = t('@num Items !pending !index_msg', array( + '@num' => $data->index->numDocs, + '!pending' => $pending_msg, + '!index_msg' => $index_msg, + )); + $messages[] = array(t('Indexed'), $indexed_message); + + $remaining_message = t('@items (@percentage% has been sent to the server)', array( + '@items' => format_plural($status['remaining'], t('1 item'), t('@count items')), + '@percentage' => ((int)min(100, 100 * ($status['total'] - $status['remaining']) / max(1, $status['total']))), + ) + ); + $messages[] = array(t('Remaining'), $remaining_message); + + $messages[] = array(t('Schema'), t('@schema_version', $stats_summary)); + if (!empty($stats_summary['@core_name'])) { + $messages[] = array(t('Solr Core Name'), t('@core_name', $stats_summary)); + } + $messages[] = array(t('Delay'), t('@autocommit_time before updates are processed.', $stats_summary)); + $messages[] = array(t('Pending Deletions'), t('@deletes_total', $stats_summary)); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + if (empty($messages)) { + $messages[] = array(t('Error'), t('No data was returned from the server. Check your log messages.')); + } + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $output['apachesolr_index_action_status'] = array( + '#prefix' => '

' . t('@environment: Search Index Content', array('@environment' => $environment['name'])) . '

', + '#theme' => 'table', + '#header' => array(t('Type'), t('Value')), + '#rows' => $messages, + ); + + $output['viewmore'] = array( + '#markup' => l(t('View more details on the search index contents'), 'admin/reports/apachesolr'), + ); + + $write_status = apachesolr_environment_variable_get($env_id, 'apachesolr_read_only', APACHESOLR_READ_WRITE); + if ($write_status == APACHESOLR_READ_WRITE) { + $output['index_action_form'] = drupal_get_form('apachesolr_index_action_form', $env_id); + $output['index_config_form'] = drupal_get_form('apachesolr_index_config_form', $env_id); + } + else { + drupal_set_message(t('Options for deleting and re-indexing are not available because the index is read-only. This can be changed on the settings page', array('!settings_page' => url('admin/config/search/apachesolr/settings/' . $env_id . '/edit', array('query' => drupal_get_destination())))), 'warning'); + } + + return $output; +} + +/** + * Get the report, eg.: some statistics and useful data from the Apache Solr index + * + * @param array $environment + * + * @return array page render array + */ +function apachesolr_index_report(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + drupal_goto('admin/reports/apachesolr/' . $env_id); + } + $environments = apachesolr_load_all_environments(); + $environments_list = array(); + foreach ($environments as $env) { + $var_status = array('!name' =>$env['name']); + $environments_list[] = l(t('Statistics for !name', $var_status), 'admin/reports/apachesolr/' . $env['env_id']); + } + $output['environments_list'] = array( + '#theme' => 'item_list', + '#items' => $environments_list, + ); + + try { + $solr = apachesolr_get_solr($environment['env_id']); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + return $output; + } + + $messages = array(); + $messages[] = array(t('Number of documents in index'), $data->index->numDocs); + + $limit = variable_get('apachesolr_luke_limit', 20000); + if (isset($data->index->numDocs) && $data->index->numDocs > $limit) { + $messages[] = array(t('Limit'), t('You have more than @limit documents, so term frequencies are being omitted for performance reasons.', array('@limit' => $limit))); + $not_found = t('Omitted'); + } + elseif (isset($data->index->numDocs)) { + $not_found = t('Not indexed'); + try { + $solr = apachesolr_get_solr($environment['env_id']); + // Note: we use 2 since 1 fails on Ubuntu Hardy. + $data = $solr->getLuke(2); + if (isset($data->index->numTerms)) { + $messages[] = array(t('# of terms in index'), $data->index->numTerms); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + $data->fields = array(); + } + } + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $fields = (array)$data->fields; + if ($fields) { + $messages[] = array(t('# of fields in index'), count($fields)); + } + + // Output the messages we have for this page + $output['apachesolr_index_report'] = array( + '#theme' => 'table', + '#header' => array('type', 'value'), + '#rows' => $messages, + ); + + if ($fields) { + // Initializes table header. + $header = array( + 'name' => t('Field name'), + 'type' => t('Index type'), + 'terms' => t('Distinct terms'), + ); + + // Builds table rows. + $rows = array(); + foreach ($fields as $name => $field) { + // TODO: try to map the name to something more meaningful. + $rows[$name] = array( + 'name' => $name, + 'type' => $field->type, + 'terms' => isset($field->distinct) ? $field->distinct : $not_found + ); + } + ksort($rows); + // Output the fields we found for this environment + $output['field_table'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + ); + } + else { + $output['field_table'] = array('#markup' => t('No data on indexed fields.')); + } + return $output; +} + +/** + * Page callback to show available conf files. + * + * @param array $environment + * + * @return string + * A non-render array but plain theme output for the config files overview. Could be done better probably + */ +function apachesolr_config_files_overview(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $xml = NULL; + try { + $solr = apachesolr_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('wt' => 'xml')); + $xml = simplexml_load_string($response->data); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + + if ($xml) { + // Retrieve our items from the xml using xpath + $items = $xml->xpath('//lst[@name="files"]/lst'); + + // Add all the data of the file in a files array + $files = array(); + foreach ($items as $item_id => $item) { + // Do not list directories. Always a bool + if (isset($item->bool)) { + break; + } + // Get data from the files. + $name = $item->attributes(); + $name = ((string)$item->attributes()) ? (string)$item->attributes() : t('No name found'); + $files[$item_id]['name'] = l($name, 'admin/reports/apachesolr/' . $env_id . '/conf/' . $name); + + // Retrieve the date attribute + if (isset($item->date)) { + $modified = ((string)$item->date->attributes() == 'modified') ? (string) $item->date : t('No date found'); + $files[$item_id]['modified'] = format_date(strtotime($modified)); + } + + // Retrieve the size attribute + if (isset($item->long)) { + $size = ((string)$item->long->attributes() == 'size') ? (string) $item->long : t('No size found'); + $files[$item_id]['size'] = t('Size (bytes): @bytes', array('@bytes' => $size)); + } + } + // Sort our files alphabetically + ksort($files); + + // Initializes table header. + $header = array( + 'name' => t('File name'), + 'date' => t('Modified'), + 'size' => t('Size'), + ); + + // Display the table of field names, index types, and term counts. + $variables = array( + 'header' => $header, + 'rows' => $files, + ); + $output = theme('table', $variables); + } + else { + $output = '

' . t('No data about any file found.') . "

\n"; + } + return $output; +} + +/** + * Page callback to show one conf file. + * + * @param string $name + * @param array $environment + * + * @return string + * the requested config file + */ +function apachesolr_config_file($name, array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $output = ''; + try { + $solr = apachesolr_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('file' => $name)); + $raw_file = $response->data; + $output = '
' . check_plain($raw_file) . '
'; + drupal_set_title(check_plain($name)); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + return $output; +} + +/** + * Form builder for the Apachesolr Indexer actions form. + * + * @param array $form + * @param array $form_state + * @param string $env_id + * The machine name of the environment. + * @see apachesolr_index_action_form_delete_submit(). + * + * @return array $form + */ +function apachesolr_index_action_form(array $form, array $form_state, $env_id) { + $form = array(); + $form['action'] = array( + '#type' => 'fieldset', + '#title' => t('Actions'), + '#collapsible' => TRUE, + ); + + $form['action']['env_id'] = array( + '#type' => 'value', + '#value' => $env_id, + ); + + $form['action']['cron'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Index queued content (!amount)', array('!amount' => variable_get('apachesolr_cron_limit', 50))), + '#submit' => array('apachesolr_index_action_form_cron_submit'), + ); + $form['action']['cron_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Indexes just as many items as 1 cron run would do.'), + ); + + $form['action']['remaining'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Index all queued content'), + '#submit' => array('apachesolr_index_action_form_remaining_submit'), + ); + $form['action']['remaining_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Could take time and could put an increased load on your server.'), + ); + + $form['action']['reset'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'submit', + '#value' => t('Queue all content for reindexing'), + '#submit' => array('apachesolr_index_action_form_reset_submit'), + ); + $form['action']['delete'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Delete the Search & Solr index'), + '#submit' => array('apachesolr_index_action_form_delete_submit'), + ); + $form['action']['delete_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Useful with a corrupt index or a new schema.xml.'), + ); + + return $form; +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_remaining_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/remaining', array('query' => $destination)); +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_delete_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/delete', array('query' => $destination)); +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_reset_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/reset', array('query' => $destination)); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_cron_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0])) { + $env_id = $form_state['build_info']['args'][0]; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + apachesolr_cron($env_id); + drupal_set_message(t('Apachesolr cron succesfully executed')); +} + +/** + * Form builder for to reindex the remaining items left in the queue. + * + * @see apachesolr_index_action_form_delete_confirm_submit(). + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return mixed + */ +function apachesolr_index_action_form_remaining_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want index all remaining content?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + NULL, + t('Index all remaining') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_remaining_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + apachesolr_index_batch_index_remaining($env_id); +} + +/** + * Form builder for the index re-enqueue form. + * + * @see apachesolr_index_action_form_reset_confirm_submit(). + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return mixed + */ +function apachesolr_index_action_form_reset_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want to queue content for reindexing?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + t('All content on the site will be queued for indexing. The documents currently in the Solr index will remain searchable.'), + t('Queue all content') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_reset_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + apachesolr_index_mark_for_reindex($env_id); + drupal_set_message(t('All the content on your site is queued for indexing. You can wait for it to be indexed during cron runs, or you can manually reindex it.')); +} + +/** + * Form builder for the index delete/clear form. + * + * @see apachesolr_index_action_form_delete_confirm_submit(). + + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return array output of confirm_form() + */ +function apachesolr_index_action_form_delete_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want to clear your index?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + t('This will remove all data from your index and all search results will be incomplete until your site is reindexed.'), + t('Delete index') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_delete_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + // Rebuild our tracking table. + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + apachesolr_index_delete_index($env_id); + drupal_set_message(t('The index has been deleted.')); +} + +/** + * Submit a batch job to index the remaining, non-indexed content. + * + * @param string $env_id + * The environment ID where it needs to index the remaining items for + */ +function apachesolr_index_batch_index_remaining($env_id, $total_limit = null) { + $batch = array( + 'operations' => array( + array( + 'apachesolr_index_batch_index_entities', + array( + $env_id, + $total_limit, + ), + ), + ), + 'finished' => 'apachesolr_index_batch_index_finished', + 'title' => t('Indexing'), + 'init_message' => t('Preparing to submit content to Solr for indexing...'), + 'progress_message' => t('Submitting content to Solr...'), + 'error_message' => t('Solr indexing has encountered an error.'), + 'file' => drupal_get_path('module', 'apachesolr') . '/apachesolr.admin.inc', + ); + batch_set($batch); +} + + +/** + * Batch Operation Callback + * + * @param string $env_id + * The machine name of the environment. + * @param $total_limit + * The total number of items to index across all batches + * @param array $context + * + * @return false + * return false when an exception was caught + * + * @throws Exception + * When solr gives an error, throw an exception that solr is not available + */ +function apachesolr_index_batch_index_entities($env_id, $total_limit = NULL, &$context) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + if (empty($context['sandbox'])) { + try { + // Get the $solr object + $solr = apachesolr_get_solr($env_id); + // If there is no server available, don't continue. + if (!$solr->ping()) { + throw new Exception(t('No Solr instance available during indexing.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR); + return FALSE; + } + + $status = apachesolr_index_status($env_id); + $context['sandbox']['progress'] = 0; + $context['sandbox']['submitted'] = 0; + + // How many items do we want to index? All or a limited set of items + if (empty($total_limit)) { + $context['sandbox']['max'] = $status['remaining']; + } + else { + $context['sandbox']['max'] = $total_limit; + } + } + + // 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); + + // Reduce the limit for our final batch if we would be processing more than had been requested + if ($limit + $context['sandbox']['progress'] > $context['sandbox']['max']) { + $limit = $context['sandbox']['max'] - $context['sandbox']['progress']; + } + + if ($context['sandbox']['max'] >= $context['sandbox']['progress'] + $limit) { + $context['sandbox']['progress'] += $limit; + } + else { + $context['sandbox']['progress'] = $context['sandbox']['max']; + } + $context['sandbox']['submitted'] += apachesolr_index_entities($env_id, $limit); + + $arguments = array( + '@current' => $context['sandbox']['progress'], + '@total' => $context['sandbox']['max'], + '@submitted' => $context['sandbox']['submitted'], + ); + $context['message'] = t('Inspected @current of @total entities. Submitted @submitted documents to Solr', $arguments); + + // Inform the batch engine that we are not finished, and provide an + // estimation of the completion level we reached. + $context['finished'] = empty($context['sandbox']['max']) ? 1 : $context['sandbox']['progress'] / $context['sandbox']['max']; + + // Put the total into the results section when we're finished so we can + // show it to the admin. + if ($context['finished']) { + $context['results']['count'] = $context['sandbox']['progress']; + $context['results']['submitted'] = $context['sandbox']['submitted']; + } +} + +/** + * Batch 'finished' callback + * + * @param bool $success + * Whether the batch ended with success or not + * @param array $results + * @param array $operations + */ +function apachesolr_index_batch_index_finished($success, array $results, array $operations) { + $message = ''; + // $results['count'] will not be set if Solr is unavailable. + if (isset($results['count'])) { + $message .= format_plural($results['count'], '1 item processed successfully. ', '@count items successfully processed. '); + } + if (isset($results['submitted'])) { + $message .= format_plural($results['submitted'], '1 document successfully sent to Solr.', '@count documents successfully sent to Solr.'); + } + if ($success) { + $type = 'status'; + } + else { + // An error occurred. $operations contains the unprocessed operations. + $error_operation = reset($operations); + $message .= ' ' . t('An error occurred while processing @num with arguments: @args', array('@num' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))); + $type = 'error'; + } + drupal_set_message($message, $type); +} + +/** + * Form builder for the bundle configuration form. + * + * @see apachesolr_index_config_form_submit(). + * + * @param array $form + * @param array $form_state + * @param string $env_id + * The machine name of the environment. + * + * @return array $form + */ +function apachesolr_index_config_form(array $form, array $form_state, $env_id) { + $form['config'] = array( + '#type' => 'fieldset', + '#title' => t('Configuration'), + '#collapsible' => TRUE, + ); + + $form['config']['bundles'] = array( + '#type' => 'markup', + '#markup' => t('Select the entity types and bundles that should be indexed.'), + ); + + // For future extensibility, when we have multiple cores. + $form['config']['env_id'] = array( + '#type' => 'value', + '#value' => $env_id, + ); + + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!empty($entity_info['apachesolr']['indexable'])) { + $options = array(); + foreach ($entity_info['bundles'] as $key => $info) { + $options[$key] = $info['label']; + } + + $form['config']['entities']['#tree'] = TRUE; + $form['config']['entities'][$entity_type] = array( + '#type' => 'checkboxes', + '#title' => check_plain($entity_info['label']), + '#options' => $options, + '#default_value' => apachesolr_get_index_bundles($env_id, $entity_type), + ); + } + } + + $form['config']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + return $form; +} + +/** + * Submit handler for the bundle configuration form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_config_form_submit(array $form, array &$form_state) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $form_values = $form_state['values']; + $env_id = $form_values['env_id']; + + foreach ($form_values['entities'] as $entity_type => $bundles) { + $existing_bundles = apachesolr_get_index_bundles($env_id, $entity_type); + $all_bundles = array_keys($bundles); + $new_bundles = array_values(array_filter($bundles)); + apachesolr_index_set_bundles($env_id, $entity_type, $new_bundles); + + // Remove all excluded bundles - this happens on form submit + // even if there is no change so the admin can remove + // bundles if there was an error. + $excluded_bundles = array_diff($all_bundles, $new_bundles); + if (apachesolr_index_delete_bundles($env_id, $entity_type, $excluded_bundles)) { + $callback = apachesolr_entity_get_callback($entity_type, 'bundles changed callback'); + if (!empty($callback)) { + call_user_func($callback, $env_id, $existing_bundles, $new_bundles); + } + } + else { + drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error'); + } + } + + // Clear the entity cache, since we will be changing its data. + entity_info_cache_clear(); + cache_clear_all('apachesolr:environments', 'cache_apachesolr'); + drupal_set_message(t('Your settings have been saved.')); +} + +/** + * Page callback for node/%node/devel/apachesolr. + * + * @param object $node + * @return string debugging information + */ +function apachesolr_devel($node) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $item = new stdClass(); + $item->entity_type = 'node'; + $item->entity_id = $node->nid; + $output = ''; + foreach (apachesolr_load_all_environments() as $env_id => $environment) { + $documents = apachesolr_index_entity_to_documents($item, $env_id); + $output .= '

' . t('Environment %name (%env_id)', array('%name' => $environment['name'], '%env_id' => $env_id)). '

'; + foreach ($documents as $document) { + $debug_data = array(); + foreach ($document as $key => $value) { + $debug_data[$key] = $value; + } + $output .= kdevel_print_object($debug_data); + } + } + return $output; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.api.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,426 @@ + 'apachesolr_fields_list_indexing_callback', + * 'index_type' => 'string', + * 'map callback' => 'apachesolr_fields_list_display_callback', + * 'facets' => TRUE, + * ), + * + * In your implementation you can add additional field types such as: + * $mappings['number_integer']['number'] = array(...); + * + * You can also add mapping for a specific field. This will take precedence + * over any mapping for a general field type. A field-specific mapping would + * looks like: + * $mappings['per-field']['field_model_name'] = array(...); + * + * Much more information can be found below in the example implementation or in + * facetapi.api.php. If you feel restricted with the options as set below + * there is nothing that stops you from implementing facetapi directly. However + * it is recommended to not directly talk to solr fields since this could break + * in the future. + * + * @return array $mappings + * An associative array of mappings as defined by modules that implement + * hook_apachesolr_field_mappings(). + */ +function hook_apachesolr_field_mappings() { + $mappings = array( + // Example for a field API type. See extensive documentation below + 'number_float' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'index_type' => 'tfloat', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + // Example for a field API field + 'per-field' => array( + // machine name of the field in Field API + 'field_price' => array( + // REQUIRED FIELDS // + // Function callback to return the value that will be put in to + // the solr index + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + + // NON REQUIRED FIELDS // + // See apachesolr_index_key() for the correct type. Defaults string + 'index_type' => 'string', + // How to display the values when they return as a facet + 'map callback' => 'apachesolr_fields_list_facet_map_callback', + // Does your facet have a dynamic name? Add function call here and will + // have the name of the return value + 'name callback' => FALSE, + // If a custom field needs to be searchable but does not need to be faceted you + // can change the 'facets' parameter to FALSE. + 'facets' => FALSE, + // Do you want to allow items without value + 'facet missing allowed' => FALSE, + // (optional) Whether or not the facet supports the + // "minimum facet count" setting. Defaults to TRUE. + 'facet mincount allowed' => FALSE, + // Field API allows any field to be multi-valued. + // If we set this to false we are able to sort + 'dependency plugins' => array('bundle', 'role'), + // Does your solr index has a hierarchy? + // See facetapi_get_taxonomy_hierarchy for details or + // view the mapping of taxonomy_term_reference + 'hierarchy callback' => FALSE, + // There are different query types to return information from Solr + // term : Regular strings + // date : Everything regarding dates + // numeric_range : Useful when you have widgets that depend + // on statistics coming from Solr + 'query types' => array('term', 'numeric_range'), + // Backwards compatible with previous facetapi versions. + // Pick the main query type + 'query type' => 'term', + // What dependencies do you have (see facetapi) + 'multiple' => TRUE, + ), + ), + ); + return $mappings; +} + +/** + * Alter hook for apachesolr_field_mappings(). + * + * Add or alter index mappings for Field API types. The default mappings array + * handles just list fields and taxonomy term reference fields, in the same way + * as documented in hook_apachesolr_field_mappings. + * + * @param array $mappings + * An associative array of mappings as defined by modules that implement + * hook_apachesolr_field_mappings(). + * @param string $entity_type + * The entity type for which you want to alter the field mappings + */ +function hook_apachesolr_field_mappings_alter(array &$mappings, $entity_type) { + // Enable indexing for text fields + $mappings['text'] = array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'map callback' => '', + 'index_type' => 'string', + 'facets' => TRUE, + 'facet missing allowed' => TRUE, + 'dependency plugins' => array('bundle', 'role'), + 'hierarchy callback' => FALSE, + 'name callback' => '', + 'facet mincount allowed' => FALSE, + 'multiple' => FALSE, + ); + + // Add our per field mapping here so we can sort on the + // price by making it single. Solr cannot sort on multivalued fields + // field_price is our identifier of a custom field, and it was decided to + // index in the same way as a number_float field. + $mappings['per-field']['field_price'] = $mappings['number_float']; + $mappings['per-field']['field_price']['multiple'] = FALSE; +} + +/** + * Prepare the query by adding parameters, sorts, etc. + * + * This hook is invoked before the query is cached. The cached query is used + * after the search such as for building facet and sort blocks, so parameters + * added during this hook may be visible to end users. + * + * This is otherwise the same as HOOK_apachesolr_query_alter(), but runs before + * it. + * + * @param DrupalSolrQueryInterface $query + * An object implementing DrupalSolrQueryInterface. No need for &. + */ +function hook_apachesolr_query_prepare(DrupalSolrQueryInterface $query) { + // Add a sort on the node ID. + $query->setAvailableSort('entity_id', array( + 'title' => t('Node ID'), + 'default' => 'asc', + )); +} + +/** + * Assigns a readable name to your custom solr field + * + * @param array $map + */ +function hook_apachesolr_field_name_map_alter(array &$map) { + $map['xs_node'] = t('The full node object'); +} + +/** + * Alter the query after it's prepared and cached. + * + * Any module performing a search should call + * drupal_alter('apachesolr_query', $query). That function then invokes this + * hook. It allows modules to modify the query object and its parameters. + * + * A module implementing HOOK_apachesolr_query_alter() may set + * $query->abort_search to TRUE to flag the query to be aborted. + * + * @param DrupalSolrQueryInterface $query + * An object implementing DrupalSolrQueryInterface. No need for &. + */ +function hook_apachesolr_query_alter(DrupalSolrQueryInterface $query) { + // I only want to see articles by the admin. + // + // NOTE: this "is_uid" filter does NOT refer to the English word "is" + // It is a combination of flags representing Integer-Single, which is + // abbreviated with the letters i and s. + // + // @see the definitions in schema.xml or schema-solr3.xml + $query->addFilter("is_uid", 1); + + // Only search titles. + $query->replaceParam('qf', 'label'); +} + +/** + * Allows a module to modify the delete query. + * + * @param string $query + * Defaults to *:* + * This is not an instance of DrupalSolrQueryInterface, it is the raw query that is being sent to Solr + */ +function hook_apachesolr_delete_by_query_alter($query) { + // use the site hash so that you only delete this site's content + if ($query == '*:*') { + $query = 'hash:' . apachesolr_site_hash(); + } + else { + $query .= ' AND hash:' . apachesolr_site_hash(); + } +} + +/** + * This is the place to look for the replacement to hook_apachesolr_node_exclude + * You should define a replacement for the status callback and return + * FALSE for entities which you do not want to appear in the index and TRUE for + * those that you want to include + */ + +/** + * This is invoked for each entity that is being inspected to be added to the + * index. if any module returns TRUE, the entity is skipped for indexing. + * + * @param string $entity_id + * @param string $entity_type + * @param object $row + * A complete set of data from the indexing table. + * @param string $env_id + * The machine name of the environment. + * @return boolean + */ +function hook_apachesolr_exclude($entity_id, $entity_type, $row, $env_id) { + // Never index media entities to core_1 + if ($entity_type == 'media' && $env_id == 'core_1') { + return TRUE; + } + return FALSE; +} + +/** + * This is invoked for each entity from the type of ENTITY_TYPE that is being + * inspected to be added to the index. if any module returns TRUE,  + * the entity is skipped for indexing. + * + * @param string $entity_id + * @param object $row + * A complete set of data from the indexing table. + * @param string $env_id + * The machine name of the environment. + * @return boolean + */ +function hook_apachesolr_ENTITY_TYPE_exclude($entity_id, $row, $env_id) { + // Never index ENTITY_TYPE to core_1 + if ($env_id == 'core_1') { + return TRUE; + } + return FALSE; +} + +/** + * Add information to index other entities. + * There are some modules in http://drupal.org that can give a good example of + * custom entity indexing such as apachesolr_user, apachesolr_term + * + * @param array $entity_info + */ +function hook_apachesolr_entity_info_alter(array &$entity_info) { + // REQUIRED VALUES + // myentity should be replaced with user/node/custom entity + $entity_info['node'] = array(); + // Set this entity as indexable + $entity_info['node']['indexable'] = TRUE; + // Validate each entity if it can be indexed or not. Multiple callbacks are + // allowed. If one of them returns false it won't be indexed + $entity_info['node']['status callback'][] = 'apachesolr_index_node_status_callback'; + // Build up a custom document. + $entity_info['node']['document callback'][] = 'apachesolr_index_node_solr_document'; + // What to do when a reindex is issued. Most probably this will reset all the + // items in the index_table + $entity_info['node']['reindex callback'] = 'apachesolr_index_node_solr_reindex'; + + // OPTIONAL VALUES + // Index in a separate table? Useful for huge datasets. + $entity_info['node']['index_table'] = 'apachesolr_index_entities_node'; + // Execute custom callback on each cron run. + // See apachesolr_index_node_check_table + $entity_info['node']['cron_check'] = 'apachesolr_index_node_check_table'; + // Specific output processing for the results + $entity_info['node']['apachesolr']['result callback'] = 'apachesolr_search_node_result'; + + // BUNDLE SPECIFIC OVERRIDES + // The following can be overridden on a per-bundle basis. + // The bundle-specific settings will take precedence over the entity settings. + $entity_info['node']['bundles']['page']['apachesolr']['result callback'] = 'apachesolr_search_node_result'; + $entity_info['node']['bundles']['page']['apachesolr']['status callback'][] = 'apachesolr_index_node_status_callback'; + $entity_info['node']['bundles']['page']['apachesolr']['document callback'][] = 'apachesolr_index_node_solr_document'; +} + + +/** + * The is invoked by apachesolr_search.module for each document returned in a + * search. This has been introduced in 6.x-beta7 as a replacement for the call + * to HOOK_nodeapi(). + * + * @param ApacheSolrDocument $document + * The ApacheSolrDocument instance. + * @param array $extra + * @param DrupalSolrQueryInterface $query + */ +function hook_apachesolr_search_result_alter(ApacheSolrDocument $document, array &$extra, DrupalSolrQueryInterface $query) { +} + +/** + * This is invoked by apachesolr_search.module for the whole resultset returned + * in a search. + * + * @param array $results + * The returned search results. + * @param DrupalSolrQueryInterface $query + * The query for which we want to process the results from + */ +function hook_apachesolr_process_results(array &$results, DrupalSolrQueryInterface $query) { + foreach ($results as $id => $result) { + $results[$id]['title'] = t('[Result] !title', array('!title' => $result['title'])); + } +} + +/** + * Respond to search environment deletion. + * + * This hook is invoked from apachesolr_environment_delete() after the + * environment is removed from the database. + * + * @param array $environment + * The environment object that is being deleted. + */ +function hook_apachesolr_environment_delete(array $environment) { +} + +/** + * + * Modify the build array for any search output build by Apache Solr + * This includes core and custom pages and makes it very easy to modify both + * of them at once + * + * @param array $build + * @param array $search_page + */ +function hook_apachesolr_search_page_alter(array &$build, array $search_page) { + // Adds a text to the top of the page + $info = array('#markup' => t('Add information to every search page')); + array_unshift($build, $info); +} + +/** + * Modify the search types as found in the search pages administration + * + * @param array $search_types + */ +function hook_apachesolr_search_types_alter(&$search_types) { + $search_types['ss_language'] = array( + 'name' => apachesolr_field_name_map('ss_language'), + 'default menu' => 'search/language/%', + 'title callback' => 'custom_title_callback', + ); +} + +/** + * Build the documents before sending them to Solr. + * The function is the follow-up for apachesolr_update_index + * + * @param ApacheSolrDocument $document + * @param object $entity + * @param string $entity_type + * @param string $env_id + * The machine name of the environment. + */ +function hook_apachesolr_index_document_build(ApacheSolrDocument $document, $entity, $entity_type, $env_id) { + +} + +/** + * Build the documents before sending them to Solr. + * + * Supports all types of + * hook_apachesolr_index_document_build_' . $entity_type($documents[$id], $entity, $env_id); + * + * The function is the follow-up for apachesolr_update_index but then for + * specific entity types + * + * @param ApacheSolrDocument $document + * @param object $entity + * @param string $env_id + * The machine name of the environment. + */ +function hook_apachesolr_index_document_build_ENTITY_TYPE(ApacheSolrDocument $document, $entity, $env_id) { + // Index field_main_image as a separate field + if ($entity->type == 'profile') { + $user = user_load(array('uid' => $entity->uid)); + // Hard coded field, not recommended for inexperienced users. + $document->setMultiValue('sm_field_main_image', $user->picture); + } +} + +/** + * Alter the prepared documents from one entity before sending them to Solr. + * + * @param $documents + * Array of ApacheSolrDocument objects. + * @param object $entity + * @param string $entity_type + * @param string $env_id + * The machine name of the environment. + */ +function hook_apachesolr_index_documents_alter(array &$documents, $entity, $entity_type, $env_id) { + // Do whatever altering you need here +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,36 @@ + +table.admin-apachesolr td.icon { + background: no-repeat center; + width: 16px; +} +table.admin-apachesolr td.operation { + width: 16px; +} +table.admin-apachesolr tr.ok { + background-color : #E5FFE2; +} +table.admin-apachesolr tr.error { + color: #8C2E0B; + background-color : #FEF5F1; +} + +table.admin-apachesolr td.status-icon { + width: 16px; + padding-right: 0; +} +table.admin-apachesolr td.status-icon div { + background-repeat: no-repeat; + height: 16px; + width: 16px; +} +table.admin-apachesolr tr { + border-bottom: 1px solid #ccc; +} + +table.admin-apachesolr tr td.default-environment { + font-weight: bold; +} + +table.admin-apachesolr tr.error td.status-icon div { + background-image: url(/misc/message-16-error.png); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.index.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.index.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1469 @@ + $info) { + // With each pass through the callback, retrieve the next group of nids. + $rows = apachesolr_index_get_entities_to_index($env_id, $entity_type, $limit); + $documents = array(); + foreach ($rows as $row) { + $row_documents = apachesolr_index_entities_document($row, $entity_type, $env_id); + $documents = array_merge($documents, $row_documents); + } + + $indexed = apachesolr_index_send_to_solr($env_id, $documents); + if ($indexed !== FALSE) { + $documents_submitted += count($documents); + $index_position = apachesolr_get_last_index_position($env_id, $entity_type); + $max_changed = $index_position['last_changed']; + $max_entity_id = $index_position['last_entity_id']; + foreach ($rows as $row) { + if (!empty($row->status)) { + if ($row->changed > $max_changed) { + $max_changed = $row->changed; + } + if ($row->entity_id > $max_entity_id) { + $max_entity_id = $row->entity_id; + } + } + } + apachesolr_set_last_index_position($env_id, $entity_type, $max_changed, $max_entity_id); + apachesolr_set_last_index_updated($env_id, REQUEST_TIME); + } + } + return $documents_submitted; +} + +/** + * Convert a certain entity from the apachesolr index table to a set of documents. 1 entity + * can be converted in multiple documents if the apachesolr_index_entity_to_documents decides to do so. + * + * @param array $row + * A row from the indexing table + * @param string $entity_type + * The type of the entity + * @param string $env_id + * The machine name of the environment. + * + * @return array of ApacheSolrDocument(s) + */ +function apachesolr_index_entities_document($row, $entity_type, $env_id) { + $documents = array(); + if (!empty($row->status)) { + // Let any module exclude this entity from the index. + $build_document = TRUE; + foreach (module_implements('apachesolr_exclude') as $module) { + $exclude = module_invoke($module, 'apachesolr_exclude', $row->entity_id, $entity_type, $row, $env_id); + // If the hook returns TRUE we should exclude the entity + if (!empty($exclude)) { + $build_document = FALSE; + } + } + foreach (module_implements('apachesolr_' . $entity_type . '_exclude') as $module) { + $exclude = module_invoke($module, 'apachesolr_' . $entity_type . '_exclude', $row->entity_id, $row, $env_id); + // If the hook returns TRUE we should exclude the entity + if (!empty($exclude)) { + $build_document = FALSE; + } + } + if ($build_document) { + $documents = array_merge($documents, apachesolr_index_entity_to_documents($row, $env_id)); + } + } + else { + // Delete the entity from our index if the status callback returned 0 + apachesolr_remove_entity($env_id, $row->entity_type, $row->entity_id); + } + // Clear entity cache for this specific entity + entity_get_controller($row->entity_type)->resetCache(array($row->entity_id)); + return $documents; +} +/** + * Returns the total number of documents that are able to be indexed and the + * number of documents left to be indexed. + * + * This is a helper function for modules that implement hook_search_status(). + * + * @param string $env_id + * The machine name of the environment. + * + * @return array + * An associative array with the key-value pairs: + * - remaining: The number of items left to index. + * - total: The total number of items to index. + * + * @see hook_search_status() + */ +function apachesolr_index_status($env_id) { + $remaining = 0; + $total = 0; + + foreach (entity_get_info() as $entity_type => $info) { + $bundles = apachesolr_get_index_bundles($env_id, $entity_type); + if (empty($bundles)) { + continue; + } + + $table = apachesolr_get_indexer_table($entity_type); + $query = db_select($table, 'asn')->condition('asn.status', 1)->condition('asn.bundle', $bundles); + $total += $query->countQuery()->execute()->fetchField(); + + // Get $last_entity_id and $last_changed. + $last_index_position = apachesolr_get_last_index_position($env_id, $entity_type); + $last_entity_id = $last_index_position['last_entity_id']; + $last_changed = $last_index_position['last_changed']; + + // Find the remaining entities to index for this entity type. + $query = db_select($table, 'aie') + ->condition('aie.bundle', $bundles) + ->condition('aie.status', 1) + ->condition(db_or() + ->condition('aie.changed', $last_changed, '>') + ->condition(db_and() + ->condition('aie.changed', $last_changed, '<=') + ->condition('aie.entity_id', $last_entity_id, '>'))) + ->addTag('apachesolr_index_' . $entity_type); + + + if ($table == 'apachesolr_index_entities') { + // Other, entity-specific tables don't need this condition. + $query->condition('aie.entity_type', $entity_type); + } + $remaining += $query->countQuery()->execute()->fetchField(); + } + return array('remaining' => $remaining, 'total' => $total); +} + +/** + * Worker callback for apachesolr_index_entities(). + * + * Loads and proccesses the entity queued for indexing and converts into one or + * more documents that are sent to the Apache Solr server for indexing. + * + * The entity is loaded as the user specified in the "apachesolr_index_user" + * system variable in order to prevent sentive data from being indexed and + * displayed to underprivileged users in search results. The index user defaults + * to a user ID of "0", which is the anonymous user. + * + * After the entity is loaded, it is converted to an array via the callback + * specified in the entity type's info array. The array that the entity is + * converted to is the model of the document sent to the Apache Solr server for + * indexing. This function allows develoeprs to modify the document by + * implementing the following hooks: + * - apachesolr_index_document_build() + * - apachesolr_index_document_build_ENTITY_TYPE() + * - apachesolr_index_documents_alter() + * + * @param stdClass $item + * The data returned by the queue table containing: + * - entity_id: An integer containing the unique identifier of the entity, for + * example a node ID or comment ID. + * - entity_type: The unique identifier for the entity, i.e. "node", "file". + * - bundle: The machine-readable name of the bundle the passed entity is + * associated with. + * - status: The "published" status of the entity. The status will also be set + * to "0" when entity is deleted but the Apache Solr server is unavailable. + * - changed: A timestamp flagging when the entity was last modified. + * @param string $env_id + * The machine name of the environment. + * + * @return array + * An associative array of documents that are sent to the Apache Solr server + * for indexing. + * + * @see apachesolr_index_nodes() for the old-skool version. + */ +function apachesolr_index_entity_to_documents($item, $env_id) { + global $user; + drupal_save_session(FALSE); + $saved_user = $user; + // build the content for the index as an anonymous user to avoid exposing restricted fields and such. + // By setting a variable, indexing can take place as a different user + $uid = variable_get('apachesolr_index_user', 0); + if ($uid == 0) { + $user = drupal_anonymous_user(); + } + else { + $user = user_load($uid); + } + // Pull out all of our pertinent data. + $entity_type = $item->entity_type; + + // Entity cache will be reset at the end of the indexing algorithm, to use the cache properly whenever + // the code does another entity_load + $entity = entity_load($entity_type, array($item->entity_id)); + $entity = $entity ? reset($entity) : FALSE; + + if (empty($entity)) { + // If the object failed to load, just stop. + return FALSE; + } + + list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + + // Create a new document, and do the bare minimum on it. + $document = _apachesolr_index_process_entity_get_document($entity, $entity_type); + + //Get the callback array to add stuff to the document + $callbacks = apachesolr_entity_get_callback($entity_type, 'document callback', $bundle); + $documents = array(); + foreach ($callbacks as $callback) { + // Call a type-specific callback to add stuff to the document. + $documents = array_merge($documents, $callback($document, $entity, $entity_type, $env_id)); + } + + //do this for all possible documents that were returned by the callbacks + foreach ($documents as $document) { + // Call an all-entity hook to add stuff to the document. + module_invoke_all('apachesolr_index_document_build', $document, $entity, $entity_type, $env_id); + + // Call a type-specific hook to add stuff to the document. + module_invoke_all('apachesolr_index_document_build_' . $entity_type, $document, $entity, $env_id); + + // Final processing to ensure that the document is properly structured. + // All records must have a label field, which is used for user-friendly labeling. + if (empty($document->label)) { + $document->label = ''; + } + + // All records must have a "content" field, which is used for fulltext indexing. + // If we don't have one, enter an empty value. This does mean that the entity + // will not be fulltext searchable. + if (empty($document->content)) { + $document->content = ''; + } + + // All records must have a "teaser" field, which is used for abbreviated + // displays when no highlighted text is available. + if (empty($document->teaser)) { + $document->teaser = truncate_utf8($document->content, 300, TRUE); + } + + // Add additional indexing based on the body of each record. + apachesolr_index_add_tags_to_document($document, $document->content); + } + + // Now allow modules to alter each other's additions for maximum flexibility. + + // Hook to allow modifications of the retrieved results + foreach (module_implements('apachesolr_index_documents_alter') as $module) { + $function = $module . '_apachesolr_index_documents_alter'; + $function($documents, $entity, $entity_type, $env_id); + } + + // Restore the user. + $user = $saved_user; + drupal_save_session(TRUE); + + return $documents; +} + +/** + * Index an array of documents to solr. + * + * @param $env_id + * @param array $documents + * + * @return bool|int number indexed, or FALSE on failure. + * @throws Exception + */ +function apachesolr_index_send_to_solr($env_id, array $documents) { + try { + // Get the $solr object + $solr = apachesolr_get_solr($env_id); + // If there is no server available, don't continue. + if (!$solr->ping(variable_get('apachesolr_ping_timeout', 4))) { + throw new Exception(t('No Solr instance available during indexing.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } + // Do not index when we do not have any documents to send + // Send TRUE because this is not an error + if (empty($documents)) { + return TRUE; + } + // Send the document off to Solr. + watchdog('Apache Solr', 'Adding @count documents.', array('@count' => count($documents))); + try { + $docs_chunk = array_chunk($documents, 20); + foreach ($docs_chunk as $docs) { + $solr->addDocuments($docs); + } + watchdog('Apache Solr', 'Indexing succeeded on @count documents', array( + '@count' => count($documents), + ), WATCHDOG_INFO); + return count($documents); + } + catch (Exception $e) { + if (!empty($docs)) { + foreach ($docs as $doc) { + $eids[] = $doc->entity_type . '/' . $doc->entity_id; + } + } + watchdog('Apache Solr', 'Indexing failed on one of the following entity ids: @eids
!message', array( + '@eids' => implode(', ', $eids), + '!message' => nl2br(strip_tags($e->getMessage())), + ), WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Extract HTML tag contents from $text and add to boost fields. + * + * @param ApacheSolrDocument $document + * @param string $text + * must be stripped of control characters before hand. + * + */ +function apachesolr_index_add_tags_to_document(ApacheSolrDocument $document, $text) { + $tags_to_index = variable_get('apachesolr_tags_to_index', array( + 'h1' => 'tags_h1', + 'h2' => 'tags_h2_h3', + 'h3' => 'tags_h2_h3', + 'h4' => 'tags_h4_h5_h6', + 'h5' => 'tags_h4_h5_h6', + 'h6' => 'tags_h4_h5_h6', + 'u' => 'tags_inline', + 'b' => 'tags_inline', + 'i' => 'tags_inline', + 'strong' => 'tags_inline', + 'em' => 'tags_inline', + 'a' => 'tags_a' + )); + + // Strip off all ignored tags. + $text = strip_tags($text, '<' . implode('><', array_keys($tags_to_index)) . '>'); + + preg_match_all('@<(' . implode('|', array_keys($tags_to_index)) . ')[^>]*>(.*)@Ui', $text, $matches); + foreach ($matches[1] as $key => $tag) { + $tag = drupal_strtolower($tag); + // We don't want to index links auto-generated by the url filter. + if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) { + if (!isset($document->{$tags_to_index[$tag]})) { + $document->{$tags_to_index[$tag]} = ''; + } + $document->{$tags_to_index[$tag]} .= ' ' . apachesolr_clean_text($matches[2][$key]); + } + } +} + +/** + * Returns a generic Solr document object for this entity. + * + * This function will do the basic processing for the document that is common + * to all entities, but virtually all entities will need their own additional + * processing. + * + * @param object $entity + * The entity for which we want a document. + * @param string $entity_type + * The type of entity we're processing. + * @return ApacheSolrDocument + */ +function _apachesolr_index_process_entity_get_document($entity, $entity_type) { + list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + + $document = new ApacheSolrDocument(); + + // Define our url options in advance. This differs depending on the + // language + $languages = language_list(); + $url_options = array('absolute' => TRUE); + if (isset($entity->language) && isset($languages[$entity->language])) { + $url_options = $url_options + array('language' => $languages[$entity->language]); + } + + $document->id = apachesolr_document_id($entity_id, $entity_type); + $document->site = url(NULL, $url_options); + $document->hash = apachesolr_site_hash(); + + $document->entity_id = $entity_id; + $document->entity_type = $entity_type; + $document->bundle = $bundle; + $document->bundle_name = entity_bundle_label($entity_type, $bundle); + + if (empty($entity->language)) { + // 'und' is the language-neutral code in Drupal 7. + $document->language = LANGUAGE_NONE; + } + else { + $document->language = $entity->language; + } + + $path = entity_uri($entity_type, $entity); + // A path is not a requirement of an entity + if (!empty($path)) { + $document->path = $path['path']; + $document->url = url($path['path'], $path['options'] + $url_options); + // Path aliases can have important information about the content. + // Add them to the index as well. + if (function_exists('drupal_get_path_alias')) { + // Add any path alias to the index, looking first for language specific + // aliases but using language neutral aliases otherwise. + $output = drupal_get_path_alias($document->path, $document->language); + if ($output && $output != $document->path) { + $document->path_alias = $output; + } + } + } + return $document; +} + +/** + * Returns an array of rows from a query based on an indexing environment. + * @todo Remove the read only because it is not environment specific + * + * @param $env_id + * @param $entity_type + * @param $limit + * + * @return array list of row to index + */ +function apachesolr_index_get_entities_to_index($env_id, $entity_type, $limit) { + $rows = array(); + if (variable_get('apachesolr_read_only', 0)) { + return $rows; + } + $bundles = apachesolr_get_index_bundles($env_id, $entity_type); + if (empty($bundles)) { + return $rows; + } + + $table = apachesolr_get_indexer_table($entity_type); + // Get $last_entity_id and $last_changed. + $last_index_position = apachesolr_get_last_index_position($env_id, $entity_type); + $last_entity_id = $last_index_position['last_entity_id']; + $last_changed = $last_index_position['last_changed']; + + // Find the next batch of entities to index for this entity type. Note that + // for ordering we're grabbing the oldest first and then ordering by ID so + // that we get a definitive order. + // Also note that we fetch ALL fields from the indexer table + $query = db_select($table, 'aie') + ->fields('aie') + ->condition('aie.bundle', $bundles) + ->condition(db_or() + ->condition('aie.changed', $last_changed, '>') + ->condition(db_and() + ->condition('aie.changed', $last_changed, '<=') + ->condition('aie.entity_id', $last_entity_id, '>'))) + ->orderBy('aie.changed', 'ASC') + ->orderBy('aie.entity_id', 'ASC') + ->addTag('apachesolr_index_' . $entity_type); + + if ($table == 'apachesolr_index_entities') { + // Other, entity-specific tables don't need this condition. + $query->condition('aie.entity_type', $entity_type); + } + $query->range(0, $limit); + $records = $query->execute(); + + $status_callbacks = apachesolr_entity_get_callback($entity_type, 'status callback'); + foreach ($records as $record) { + // Check status and status callbacks before sending to the index + if (is_array($status_callbacks)) { + foreach($status_callbacks as $status_callback) { + if (is_callable($status_callback)) { + // by placing $status in front we prevent calling any other callback + // after one status callback returned false + $record->status = $record->status && $status_callback($record->entity_id, $record->entity_type); + } + } + } + $rows[] = $record; + } + return $rows; +} + +/** + * Delete the whole index for an environment. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * (optional) specify to remove just this entity_type from the index. + * @param string $bundle + * (optional) also specify a bundle to remove just the bundle from + * the index. + */ +function apachesolr_index_delete_index($env_id, $entity_type = NULL, $bundle = NULL) { + // Instantiate a new Solr object. + try { + $solr = apachesolr_get_solr($env_id); + $query = '*:*'; + + if (!empty($entity_type) && !empty($bundle)) { + $query = "(bundle:$bundle AND entity_type:$entity_type) OR sm_parent_entity_bundle:{$entity_type}-{$bundle}"; + } + elseif (!empty($bundle)) { + $query = "(bundle:$bundle)"; + } + + // Allow other modules to modify the delete query. + // For example, use the site hash so that you only delete this site's + // content: $query = 'hash:' . apachesolr_site_hash() + drupal_alter('apachesolr_delete_by_query', $query); + $solr->deleteByQuery($query); + $solr->commit(); + + if (!empty($entity_type)) { + $rebuild_callback = apachesolr_entity_get_callback($entity_type, 'reindex callback'); + if (is_callable($rebuild_callback)) { + $rebuild_callback($env_id, $bundle); + } + } + else { + apachesolr_index_mark_for_reindex($env_id); + } + + apachesolr_set_last_index_updated($env_id, REQUEST_TIME); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +/** + * Delete from the index documents with the entity type and any of the excluded bundles. + * + * Also deletes all documents that have the entity type and bundle as a parent. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * @param array $excluded_bundles + * + * @return true on success, false on failure. + */ +function apachesolr_index_delete_bundles($env_id, $entity_type, array $excluded_bundles) { + // Remove newly omitted bundles. + try { + $solr = apachesolr_get_solr($env_id); + foreach ($excluded_bundles as $bundle) { + $query = "(bundle:$bundle AND entity_type:$entity_type) OR sm_parent_entity_bundle:{$entity_type}-{$bundle}"; + + // Allow other modules to modify the delete query. + // For example, use the site hash so that you only delete this site's + // content: $query = 'hash:' . apachesolr_site_hash() + drupal_alter('apachesolr_delete_by_query', $query); + $solr->deleteByQuery($query); + } + if ($excluded_bundles) { + $solr->commit(); + } + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Delete an entity from the index. + * + * Also deletes all documents that have the deleted document as a parent. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * @param string $entity_id + * + * @return true on success, false on failure. + */ +function apachesolr_index_delete_entity_from_index($env_id, $entity_type, $entity_id) { + static $failed = FALSE; + if ($failed) { + return FALSE; + } + try { + $solr = apachesolr_get_solr($env_id); + $document_id = apachesolr_document_id($entity_id, $entity_type); + $query = "id:\"$document_id\" OR sm_parent_document_id:\"$document_id\""; + $solr->deleteByQuery($query); + apachesolr_set_last_index_updated($env_id, REQUEST_TIME); + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + // Don't keep trying queries if they are failing. + $failed = TRUE; + return FALSE; + } +} + +/** + * Mark a certain entity type for a specific environment for reindexing. + * + * @param $env_id + * @param null $entity_type + */ +function apachesolr_index_mark_for_reindex($env_id, $entity_type = NULL) { + foreach (entity_get_info() as $type => $entity_info) { + if (($type == $entity_type) || ($entity_type == NULL)) { + if (isset($entity_info['apachesolr']) && ($entity_info['apachesolr']['indexable'])) { + $reindex_callback = apachesolr_entity_get_callback($type, 'reindex callback'); + if (!empty($reindex_callback)) { + call_user_func($reindex_callback, $env_id); + } + } + } + } + apachesolr_clear_last_index_position($env_id, $entity_type); + cache_clear_all('*', 'cache_apachesolr', TRUE); +} + +/** + * Sets what bundles on the specified entity type should be indexed. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * The entity type to index. + * @param array $bundles + * The machine names of the bundles to index. + * + * @throws Exception + */ +function apachesolr_index_set_bundles($env_id, $entity_type, array $bundles) { + $transaction = db_transaction(); + try { + db_delete('apachesolr_index_bundles') + ->condition('env_id', $env_id) + ->condition('entity_type', $entity_type) + ->execute(); + + if ($bundles) { + $insert = db_insert('apachesolr_index_bundles') + ->fields(array('env_id', 'entity_type', 'bundle')); + + foreach ($bundles as $bundle) { + $insert->values(array( + 'env_id' => $env_id, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + )); + } + $insert->execute(); + } + } + catch (Exception $e) { + $transaction->rollback(); + // Re-throw the exception so we are aware of the failure. + throw $e; + } +} + +// This really should be in core, but it isn't yet. When it gets added to core, +// we can remove this version. +// @see http://drupal.org/node/969180 +if (!function_exists('entity_bundle_label')) { + +/** + * Returns the label of a bundle. + * + * @param string $entity_type + * The entity type; e.g. 'node' or 'user'. + * @param string $bundle_name + * The bundle for which we want the label from + * + * @return + * A string with the human-readable name of the bundle, or FALSE if not specified. + */ +function entity_bundle_label($entity_type, $bundle_name) { + $labels = &drupal_static(__FUNCTION__, array()); + + if (empty($labels)) { + foreach (entity_get_info() as $type => $info) { + foreach ($info['bundles'] as $bundle => $bundle_info) { + $labels[$type][$bundle] = !empty($bundle_info['label']) ? $bundle_info['label'] : FALSE; + } + } + } + + return $labels[$entity_type][$bundle_name]; +} + +} + +/** + * Builds the node-specific information for a Solr document. + * + * @param ApacheSolrDocument $document + * The Solr document we are building up. + * @param object $node + * The entity we are indexing. + * @param string $entity_type + * The type of entity we're dealing with. + * @param string $env_id + * The type of entity we're dealing with. + * + * @return array A set of ApacheSolrDocument documents + */ +function apachesolr_index_node_solr_document(ApacheSolrDocument $document, $node, $entity_type, $env_id) { + // None of these get added unless they are explicitly in our schema.xml + $document->label = apachesolr_clean_text($node->title); + + // Build the node body. + $build = node_view($node, 'search_index', !empty($node->language) ? $node->language : LANGUAGE_NONE); + // Remove useless html crap out of the render. + unset($build['#theme']); + $text = drupal_render($build); + $document->content = apachesolr_clean_text($text); + + // Adding the teaser + if (isset($node->teaser)) { + $document->teaser = apachesolr_clean_text($node->teaser); + } + else { + $document->teaser = truncate_utf8($document->content, 300, TRUE); + } + + // Path aliases can have important information about the content. + // Add them to the index as well. + if (function_exists('drupal_get_path_alias')) { + // Add any path alias to the index, looking first for language specific + // aliases but using language neutral aliases otherwise. + $language = empty($node->language) ? NULL : $node->language; + $path = 'node/' . $node->nid; + $output = drupal_get_path_alias($path, $language); + if ($output && $output != $path) { + $document->path_alias = $output; + } + } + + // Author information + $document->ss_name = $node->name; + // We want the name to be searchable for keywords. + $document->tos_name = $node->name; + + // Index formatted username so it can be searched and sorted on. + $account = (object) array('uid' => $node->uid, 'name' => $node->name); + $username = format_username($account); + $document->ss_name_formatted = $username; + $document->tos_name_formatted = $username; + $document->is_uid = $node->uid; + $document->bs_status = $node->status; + $document->bs_sticky = $node->sticky; + $document->bs_promote = $node->promote; + $document->is_tnid = $node->tnid; + $document->bs_translate = $node->translate; + + // Language specific checks + if (empty($node->language)) { + // 'und' is the language-neutral code in Drupal 7. + $document->ss_language = LANGUAGE_NONE; + } + else { + $document->ss_language = $node->language; + } + + // Timestamp of the node + $document->ds_created = apachesolr_date_iso($node->created); + $document->ds_changed = apachesolr_date_iso($node->changed); + + // Comment counts + time + if (isset($node->last_comment_timestamp) && !empty($node->comment_count)) { + $document->ds_last_comment_timestamp = apachesolr_date_iso($node->last_comment_timestamp); + $document->ds_last_comment_or_change = apachesolr_date_iso(max($node->last_comment_timestamp, $node->changed)); + $document->is_comment_count = $node->comment_count; + } + else { + $document->ds_last_comment_or_change = apachesolr_date_iso($node->changed); + } + + // Fetch extra data normally not visible, including comments. + // We do this manually (with module_implements instead of node_invoke_nodeapi) + // because we want a keyed array to come back. Only in this way can we decide + // whether to index comments or not. + $extra = array(); + $excludes = variable_get('apachesolr_exclude_nodeapi_types', array()); + $exclude_nodeapi = isset($excludes[$node->type]) ? $excludes[$node->type] : array(); + + foreach (module_implements('node_update_index') as $module) { + // Invoke nodeapi if this module has not been excluded, for example, + // exclude 'comment' for a type to skip indexing its comments. + if (empty($exclude_nodeapi[$module])) { + $function = $module . '_node_update_index'; + if ($output = $function($node)) { + $extra[$module] = $output; + } + } + } + + // Adding the text of the comments + if (isset($extra['comment'])) { + $comments = $extra['comment']; + // Remove comments from the extra fields + unset($extra['comment']); + $document->ts_comments = apachesolr_clean_text($comments); + // @todo: do we want to reproduce apachesolr_add_tags_to_document() for comments? + } + // If there are other extra fields, add them to the document + if (!empty($extra)) { + // Use an omit-norms text field since this is generally going to be short; not + // really a full-text field. + $document->tos_content_extra = apachesolr_clean_text(implode(' ', $extra)); + } + + // Generic use case for future reference. Callbacks can + // allow you to send back multiple documents + $documents = array(); + $documents[] = $document; + return $documents; +} + +/** + * Function that will be executed if the node bundles were updated. + * Currently it does nothing, but it could potentially do something later on. + * + * @param $env_id + * @param $existing_bundles + * @param $new_bundles + */ +function apachesolr_index_node_bundles_changed($env_id, $existing_bundles, $new_bundles) { + // Nothing to do for now. +} + +/** + * Reindexing callback for ApacheSolr, for nodes. + * + * @param string $env_id + * The machine name of the environment. + * @param string|null $bundle + * (optional) The bundle type to reindex. If not used + * all bundles will be re-indexed. + * + * @return null + * returns NULL if the specified bundle is not in the indexable bundles list + * + * @throws Exception + */ +function apachesolr_index_node_solr_reindex($env_id, $bundle = NULL) { + $indexer_table = apachesolr_get_indexer_table('node'); + $transaction = db_transaction(); + try { + $indexable_bundles = apachesolr_get_index_bundles($env_id, 'node'); + + if ($bundle && !empty($indexable_bundles) && !in_array($bundle, $indexable_bundles)) { + // The bundle specified is not in the indexable bundles list. + return NULL; + } + + // Leave status 0 rows - those need to be + // removed from the index later. + $delete = db_delete($indexer_table); + $delete->condition('status', 1); + + if (!empty($bundle)) { + $delete->condition('bundle', $bundle); + } + elseif (!empty($indexable_bundles)) { + $delete->condition('bundle', $indexable_bundles, 'IN'); + } + + $delete->execute(); + + $select = db_select('node', 'n'); + $select->condition('status', 1); + $select->addExpression("'node'", 'entity_type'); + $select->addField('n', 'nid', 'entity_id'); + $select->addField('n', 'type', 'bundle'); + $select->addField('n', 'status', 'status'); + $select->addExpression(REQUEST_TIME, 'changed'); + + if ($bundle) { + // Mark all nodes of the specified content type for reindexing. + $select->condition('n.type', $bundle); + } + elseif (!empty($indexable_bundles)) { + // Restrict reindex to content types in the indexable bundles list. + $select->condition('n.type', $indexable_bundles, 'IN'); + } + + $insert = db_insert($indexer_table) + ->fields(array('entity_id', 'bundle', 'status', 'entity_type', 'changed')) + ->from($select) + ->execute(); + } + catch (Exception $e) { + $transaction->rollback(); + throw $e; + } +} + +/** + * Status callback for ApacheSolr, for nodes. + * after indexing a certain amount of nodes + * + * @param $entity_id + * @param $entity_type + * + * @return int + * The status of the node + */ +function apachesolr_index_node_status_callback($entity_id, $entity_type) { + // Make sure we have a boolean value. + // Anything different from 1 becomes zero + $entity = entity_load($entity_type, array($entity_id)); + $entity = $entity ? reset($entity) : FALSE; + + if (empty($entity)) { + // If the object failed to load, just stop. + return FALSE; + } + $status = ($entity->status == 1 ? 1 : 0); + return $status; +} + +/** + * Callback that converts term_reference field into an array + * + * @param object $node + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + * fields that will be indexed for this term reference + */ +function apachesolr_term_reference_indexing_callback($node, $field_name, $index_key, array $field_info) { + // Keep ancestors cached + $ancestors = &drupal_static(__FUNCTION__, array()); + + $fields = array(); + $vocab_names = array(); + if (!empty($node->{$field_name}) && function_exists('taxonomy_get_parents_all')) { + $field = $node->$field_name; + list($lang, $items) = each($field); + foreach ($items as $item) { + // Triple indexing of tids lets us do efficient searches (on tid) + // and do accurate per field or per-vocabulary faceting. + + // By including the ancestors to a term in the index we make + // sure that searches for general categories match specific + // categories, e.g. Fruit -> apple, a search for fruit will find + // content categorized with apple. + if (!isset($ancestors[$item['tid']])) { + $ancestors[$item['tid']] = taxonomy_get_parents_all($item['tid']); + } + foreach ($ancestors[$item['tid']] as $ancestor) { + // Index parent term against the field. Note that this happens + // regardless of whether the facet is set to show as a hierarchy or not. + // We would need a separate field if we were to index terms without any + // hierarchy at all. + // If the term is singular, then we cannot add another value to the + // document as the field is single + if ($field_info['multiple'] == true) { + $fields[] = array( + 'key' => $index_key, + 'value' => $ancestor->tid, + ); + } + $fields[] = array( + 'key' => 'tid', + 'value' => $ancestor->tid, + ); + $fields[] = array( + 'key' => 'im_vid_' . $ancestor->vid, + 'value' => $ancestor->tid, + ); + $name = apachesolr_clean_text($ancestor->name); + $vocab_names[$ancestor->vid][] = $name; + // We index each name as a string for cross-site faceting + // using the vocab name rather than vid in field construction . + $fields[] = array( + 'key' => 'sm_vid_' . apachesolr_vocab_name($ancestor->vid), + 'value' => $name, + ); + } + } + // Index the term names into a text field for MLT queries and keyword searching. + foreach ($vocab_names as $vid => $names) { + $fields[] = array( + 'key' => 'tm_vid_' . $vid . '_names', + 'value' => implode(' ', $names), + ); + } + } + return $fields; +} + +/** + * Helper function - return a safe (PHP identifier) vocabulary name. + * + * @param integer $vid + * @return string + */ +function apachesolr_vocab_name($vid) { + $names = &drupal_static(__FUNCTION__, array()); + + if (!isset($names[$vid])) { + $vocab_name = db_query('SELECT v.name FROM {taxonomy_vocabulary} v WHERE v.vid = :vid', array(':vid' => $vid))->fetchField(); + $names[$vid] = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $vocab_name); + // Fallback for names ending up all as '_'. + $check = rtrim($names[$vid], '_'); + if (!$check) { + $names[$vid] = '_' . $vid . '_'; + } + } + return $names[$vid]; +} + +/** + * Callback that converts list module field into an array + * For every multivalued value we also add a single value to be able to + * use the stats + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function apachesolr_fields_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + $numeric = TRUE; + if (!empty($entity->{$field_name})) { + $field = $entity->$field_name; + list($lang, $values) = each($field); + switch ($field_info['index_type']) { + case 'integer': + case 'half-int': + case 'sint': + case 'tint': + case 'thalf-int': + case 'boolean': + $function = 'intval'; + break; + case 'float': + case 'double': + case 'sfloat': + case 'sdouble': + case 'tfloat': + case 'tdouble': + $function = 'apachesolr_floatval'; + break; + default: + $numeric = FALSE; + $function = 'apachesolr_clean_text'; + } + for ($i = 0; $i < count($values); $i++) { + $fields[] = array( + 'key' => $index_key, + 'value' => $function($values[$i]['value']), + ); + } + // Also store the first value of the field in a singular index for multi value fields + if ($field_info['multiple'] && $numeric && !empty($values[0])) { + $singular_field_info = $field_info; + $singular_field_info['multiple'] = FALSE; + $single_key = apachesolr_index_key($singular_field_info); + $fields[] = array( + 'key' => $single_key, + 'value' => $function($values[0]['value']), + ); + } + } + return $fields; +} + +/** + * This function is used during indexing to normalize the DATE and DATETIME + * fields into the appropriate format for Apache Solr. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function apachesolr_date_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + $field = $entity->$field_name; + list($lang, $values) = each($field); + // Construct a Solr-ready date string in UTC time zone based on the field's date string and time zone. + $tz = new DateTimeZone(isset($field['timezone']) ? $field['timezone'] : 'UTC'); + + // $fields may end up having two values; one for the start date + // and one for the end date. + foreach ($values as $value) { + if ($date = date_create($value['value'], $tz)) { + $index_value = apachesolr_date_iso($date->format('U')); + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + + if (isset($value['value2'])) { + if ($date = date_create($value['value2'], $tz)) { + $index_value = apachesolr_date_iso($date->format('U')); + $fields[] = array( + // The value2 element is the end date. Therefore it gets indexed + // into its own Solr field. + 'key' => $index_key . '_end', + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * This function is used during indexing to normalize the DATESTAMP fields + * into the appropriate format for Apache Solr. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function apachesolr_datestamp_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + // $fields may end up having two values; one for the start date + // and one for the end date. + $field = $entity->$field_name; + list($lang, $values) = each($field); + + foreach ($values as $value) { + if (isset($value['value']) && $value['value'] != 0) { + $index_value = apachesolr_date_iso($value['value']); + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + if (isset($value['value2']) && $value['value'] != 0) { + $index_value = apachesolr_date_iso($value['value2']); + $fields[] = array( + // The value2 element is the end date. Therefore it gets indexed + // into its own Solr field. + 'key' => $index_key . '_end', + 'value' => $index_value, + ); + } + } + } + return $fields; +} + +function apachesolr_floatval($value) { + return sprintf('%0.20f', $value); +} + +/** + * Indexing callback for the node_reference module + * by the references module + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function apachesolr_nodereference_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + $index_key = apachesolr_index_key($field_info); + foreach ($entity->$field_name as $field_references) { + foreach ($field_references as $reference) { + if ($index_value = (!empty($reference['nid'])) ? $reference['nid'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * Indexing callback for the user_reference module + * by the references module + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function apachesolr_userreference_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->$field_name)) { + $index_key = apachesolr_index_key($field_info); + foreach ($entity->$field_name as $field_references) { + foreach ($field_references as $reference) { + if ($index_value = (isset($reference['uid']) && strlen($reference['uid'])) ? $reference['uid'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * Indexing callback for entityreference fields. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + * + */ +function apachesolr_entityreference_indexing_callback($entity, $field_name, $index_key, $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + + // Gets entity type and index key. We need to prefix the ID with the entity + // type so we know what entity we are dealing with in the mapping callback. + $entity_type = $field_info['field']['settings']['target_type']; + $index_key = apachesolr_index_key($field_info); + + // Iterates over all references and adds them to the fields. + foreach ($entity->$field_name as $entity_references) { + foreach ($entity_references as $reference) { + if ($id = (!empty($reference['target_id'])) ? $reference['target_id'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $entity_type . ':' . $id, + ); + } + } + } + } + return $fields; +} + +/** + * Extract HTML tag contents from $text and add to boost fields. + * + * $text must be stripped of control characters before hand. + * + * @param ApacheSolrDocument $document + * @param type $text + */ +function apachesolr_add_tags_to_document(ApacheSolrDocument $document, $text) { + $tags_to_index = variable_get('apachesolr_tags_to_index', array( + 'h1' => 'tags_h1', + 'h2' => 'tags_h2_h3', + 'h3' => 'tags_h2_h3', + 'h4' => 'tags_h4_h5_h6', + 'h5' => 'tags_h4_h5_h6', + 'h6' => 'tags_h4_h5_h6', + 'u' => 'tags_inline', + 'b' => 'tags_inline', + 'i' => 'tags_inline', + 'strong' => 'tags_inline', + 'em' => 'tags_inline', + 'a' => 'tags_a' + )); + + // Strip off all ignored tags. + $text = strip_tags($text, '<' . implode('><', array_keys($tags_to_index)) . '>'); + + preg_match_all('@<(' . implode('|', array_keys($tags_to_index)) . ')[^>]*>(.*)@Ui', $text, $matches); + foreach ($matches[1] as $key => $tag) { + $tag = strtolower($tag); + // We don't want to index links auto-generated by the url filter. + if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) { + if (!isset($document->{$tags_to_index[$tag]})) { + $document->{$tags_to_index[$tag]} = ''; + } + $document->{$tags_to_index[$tag]} .= ' ' . apachesolr_clean_text($matches[2][$key]); + } + } +} + +/** + * hook_cron() helper to try to make the index table consistent with their + * respective entity table. + */ +function apachesolr_index_node_check_table() { + // Check for unpublished content that wasn't deleted from the index. + $table = apachesolr_get_indexer_table('node'); + // We do not check more nodes than double the cron limit per time + // Update or delete at most this many in each Solr query. + $limit = variable_get('apachesolr_cron_mass_limit', 500); + $query = db_select($table, 'aien') + ->fields('n', array('nid', 'status')) + ->where('aien.status <> n.status') + ->range(0, ($limit * 2)) + ->addTag('apachesolr_index_node'); + $query->innerJoin('node', 'n', 'n.nid = aien.entity_id'); + $nodes = $query->execute()->fetchAllAssoc('nid'); + + $node_lists = array_chunk($nodes, $limit, TRUE); + foreach ($node_lists as $nodes) { + watchdog('Apache Solr', 'On cron running apachesolr_nodeapi_mass_update() on nids @nids', array('@nids' => implode(',', array_keys($nodes))), WATCHDOG_NOTICE); + if (!apachesolr_index_nodeapi_mass_update($nodes, $table)) { + // Solr query failed - so stop trying. + break; + } + } + + // Check for deleted content that wasn't deleted from the index. + $query = db_select($table, 'aien') + ->isNull('n.nid') + ->range(0, ($limit*2)); + $query->addExpression('aien.entity_id', 'nid'); + $query->leftJoin('node', 'n', 'n.nid = aien.entity_id'); + $nodes = $query->execute()->fetchAllAssoc('nid'); + $node_lists = array_chunk($nodes, $limit, TRUE); + + foreach ($node_lists as $nodes) { + watchdog('Apache Solr', 'On cron running apachesolr_nodeapi_mass_delete() on nids @nids', array('@nids' => implode(',', array_keys($nodes))), WATCHDOG_NOTICE); + if (!apachesolr_index_nodeapi_mass_delete($nodes, $table)) { + // Solr query failed - so stop trying. + break; + } + } +} + +/** + * Mass Update nodes from the solr indexer table + * + * @param array $nodes + * @param string $table + * @return boolean + * true if we mass updated, false if failed + */ +function apachesolr_index_nodeapi_mass_update(array $nodes, $table = NULL) { + if (empty($nodes)) { + return TRUE; + } + if (empty($table)) { + $table = apachesolr_get_indexer_table('node'); + } + + if (apachesolr_environment_variable_get(apachesolr_default_environment(), 'apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) { + return TRUE; + } + + $published_ids = array(); + $unpublished_ids = array(); + foreach ($nodes as $node) { + if ($node->status) { + $published_ids[$node->nid] = apachesolr_document_id($node->nid); + } + else { + $unpublished_ids[$node->nid] = apachesolr_document_id($node->nid); + } + } + try { + $env_id = apachesolr_default_environment(); + $solr = apachesolr_get_solr($env_id); + $solr->deleteByMultipleIds($unpublished_ids); + apachesolr_set_last_index_updated($env_id, REQUEST_TIME); + + // There was no exception, so update the table. + if ($published_ids) { + db_update($table) + ->fields(array('changed' => REQUEST_TIME, 'status' => 1)) + ->condition('entity_id', array_keys($published_ids), 'IN') + ->execute(); + } + if ($unpublished_ids) { + db_update($table) + ->fields(array('changed' => REQUEST_TIME, 'status' => 0)) + ->condition('entity_id', array_keys($unpublished_ids), 'IN') + ->execute(); + } + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Mass delete nodes from the solr indexer tables. + * + * @param array $nodes + * @param string $table + * @return boolean + * true if we mass updated, false if failed + */ +function apachesolr_index_nodeapi_mass_delete(array $nodes, $table = NULL) { + if (empty($nodes)) { + return TRUE; + } + if (empty($table)) { + $table = apachesolr_get_indexer_table('node'); + } + + if (apachesolr_environment_variable_get(apachesolr_default_environment(), 'apachesolr_read_only', APACHESOLR_READ_WRITE) == APACHESOLR_READ_ONLY) { + return TRUE; + } + + $ids = array(); + $nids = array(); + foreach ($nodes as $node) { + $ids[] = apachesolr_document_id($node->nid); + $nids[] = $node->nid; + } + try { + $env_id = apachesolr_default_environment(); + $solr = apachesolr_get_solr($env_id); + $solr->deleteByMultipleIds($ids); + apachesolr_set_last_index_updated($env_id, REQUEST_TIME); + // There was no exception, so update the table. + db_delete($table) + ->condition('entity_id', $nids, 'IN') + ->execute(); + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,32 @@ +name = Apache Solr framework +description = Framework for searching with Solr +package = Search Toolkit +core = 7.x +configure = admin/config/search/apachesolr/settings + +files[] = apachesolr.install +files[] = apachesolr.module +files[] = apachesolr.admin.inc +files[] = apachesolr.index.inc +files[] = apachesolr.interface.inc +files[] = Drupal_Apache_Solr_Service.php +files[] = Apache_Solr_Document.php +files[] = Solr_Base_Query.php +files[] = plugins/facetapi/adapter.inc +files[] = plugins/facetapi/query_type_date.inc +files[] = plugins/facetapi/query_type_term.inc +files[] = plugins/facetapi/query_type_numeric_range.inc +files[] = plugins/facetapi/query_type_geo.inc +files[] = tests/Dummy_Solr.php +files[] = tests/apachesolr_base.test +files[] = tests/solr_index_and_search.test +files[] = tests/solr_base_query.test +files[] = tests/solr_base_subquery.test +files[] = tests/solr_document.test + +; Information added by drupal.org packaging script on 2013-03-15 +version = "7.x-1.1+34-dev" +core = "7.x" +project = "apachesolr" +datestamp = "1363307665" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,899 @@ + $t('Apache Solr'), + 'value' => $t('Missing environment configuration'), + 'description' => $t('Missing or invalid Solr environment record for the default environment ID %id.', array('%id' => $id)), + 'severity' => REQUIREMENT_ERROR, + ); + } + else { + $has_settings = TRUE; + } + + if ($has_settings) { + $ping = FALSE; + try { + $solr = apachesolr_get_solr($id); + $ping = @$solr->ping(variable_get('apachesolr_ping_timeout', 4)); + // If there is no $solr object, there is no instance available, so don't continue. + if (!$ping) { + throw new Exception(t('No Solr instance available when checking requirements.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + $value = $ping ? $t('Your site has contacted the Apache Solr server.') : $t('Your site was unable to contact the Apache Solr server.'); + $severity = $ping ? REQUIREMENT_OK : REQUIREMENT_ERROR; + $requirements['apachesolr'] = array( + 'title' => $t('Apache Solr'), + 'value' => $value, + 'description' => $t('Default environment url:
%url', array('%url' => $environment['url'])), + 'severity' => $severity, + ); + } + + return $requirements; +} + +/** + * Implements hook_install(). + */ +function apachesolr_install() { + module_load_include('inc', 'apachesolr', 'apachesolr_search.admin'); + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + // Create one MLT block. + apachesolr_search_mlt_save_block(array('name' => st('More like this'))); + 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']); + $env_id = apachesolr_default_environment(); + apachesolr_index_set_bundles($env_id, 'node', $bundles); + + drupal_set_message(st('Apache Solr is enabled. Visit the settings page.', array('@settings_link' => url('admin/config/search/apachesolr')))); +} + +/** + * Implements hook_enable(). + */ +function apachesolr_enable() { + // Completely build the index table. + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $env_id = apachesolr_default_environment(); + apachesolr_index_mark_for_reindex($env_id); +} + +/** + * Implements hook_schema(). + */ +function apachesolr_schema() { + + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for apachesolr to store Luke data and indexing information.'; + $schema['cache_apachesolr'] = $table; + + $schema['apachesolr_environment'] = array( + 'description' => 'The Solr search environment table.', + // Enable CTools exportables based on this table. + 'export' => array( + // Environment machine name. + 'key' => 'env_id', + // Description of key. + 'key name' => 'Environment machine name', + // Apache Solr doesn't allow disabling environments. + 'can disable' => FALSE, + // Variable name to use in exported code. + 'identifier' => 'environment', + // Thin wrapper for the environment save callback. + 'save callback' => 'apachesolr_ctools_environment_save', + // Thin wrapper for the environment delete callback. + 'delete callback' => 'apachesolr_ctools_environment_delete', + // Includes the environment variables in 'conf' as well as the fields in this table. + 'export callback' => 'apachesolr_ctools_environment_export', + // Use the same hook as the API name below. + 'default hook' => 'apachesolr_environments', + // CTools API implementation. + 'api' => array( + 'owner' => 'apachesolr', + 'api' => 'apachesolr_environments', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + 'fields' => array( + 'env_id' => array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'Human-readable name for the server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'url' => array( + 'description' => 'Full url for the server', + 'type' => 'varchar', + 'length' => 1000, + 'not null' => TRUE, + ), + 'service_class' => array( + 'description' => 'Optional class name to use for connection', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + ), + 'primary key' => array('env_id'), + ); + $schema['apachesolr_environment_variable'] = array( + 'description' => 'Variable values for each Solr search environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'The name of the variable.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value of the variable.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array('env_id', 'name'), + ); + + // Technically the entity system does not require an integer ID. + // However, documentation mentions : + // id: The name of the property that contains the primary id of the + // entity. Every entity object passed to the Field API must have this + // property and its value must be numeric. + + //Predefine an amount of types that get their own table + $types = array( + 'other' => 'apachesolr_index_entities', + 'node' => 'apachesolr_index_entities_node', + ); + foreach ($types as $type => $table) { + $schema[$table] = array( + 'description' => 'Stores a record of when an entity changed to determine if it needs indexing by Solr.', + 'fields' => array( + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'entity_id' => array( + 'description' => 'The primary identifier for an entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to which this entity belongs.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the entity should be in the index.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when an entity was changed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'bundle_changed' => array('bundle', 'changed'), + ), + 'primary key' => array('entity_id'), + ); + if ($type == 'other') { + // Need the entity type also in the pkey for multiple entities in one table. + $schema[$table]['primary key'][] = 'entity_type'; + } + } + + $schema['apachesolr_index_bundles'] = array( + 'description' => 'Records what bundles we should be indexing for a given environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'The name of the environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to index.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + ), + 'primary key' => array('env_id', 'entity_type', 'bundle'), + ); + return $schema; +} + +/** + * Implements hook_uninstall(). + */ +function apachesolr_uninstall() { + // Remove variables. + variable_del('apachesolr_default_environment'); + variable_del('apachesolr_rows'); + variable_del('apachesolr_site_hash'); + variable_del('apachesolr_index_last'); + variable_del('apachesolr_search_mlt_blocks'); + variable_del('apachesolr_cron_limit'); + variable_del('apachesolr_exclude_nodeapi_types'); + variable_del('apachesolr_failure'); + variable_del('apachesolr_index_updated'); + variable_del('apachesolr_read_only'); + variable_del('apachesolr_set_nodeapi_messages'); + variable_del('apachesolr_last_optimize'); + variable_del('apachesolr_update_from_6303'); + // Remove blocks. + db_delete('block')->condition('module', 'apachesolr')->execute(); +} + +/** + * Add a table to track Solr servers. + */ +function apachesolr_update_7000() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + $schema['apachesolr_server'] = array( + 'description' => 'The Solr server table.', + 'fields' => array( + 'server_id' => array( + 'description' => 'Unique identifier for the server', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'Human-readable name for the server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'scheme' => array( + 'description' => 'Preferred scheme for the registered server', + 'type' => 'varchar', + 'length' => 10, + 'not null' => TRUE, + 'default' => 'http' + ), + 'host' => array( + 'description' => 'Host name for the registered server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'port' => array( + 'description' => 'Port number for the registered server', + 'type' => 'int', + 'not null' => TRUE, + ), + 'path' => array( + 'description' => 'Path to the registered server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'service_class' => array( + 'description' => 'Optional class name to use for connection', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + ), + 'primary key' => array('server_id'), + ); + db_create_table('apachesolr_server', $schema['apachesolr_server']); + // Insert into the table the current single server record. + $host = variable_get('apachesolr_host', 'localhost'); + $port = variable_get('apachesolr_port', '8983'); + $path = variable_get('apachesolr_path', '/solr'); + db_insert('apachesolr_server')->fields(array('server_id' => 'solr', 'name' => 'Apache Solr server', 'host' => $host, 'port' => $port, 'path' => $path))->execute(); + variable_set('apachesolr_default_server', 'solr'); + variable_del('apachesolr_host'); + variable_del('apachesolr_port'); + variable_del('apachesolr_path'); + $value = variable_get('apachesolr_service_class', NULL); + if (is_array($value)) { + list($module, $filepath, $class) = $value; + variable_set('apachesolr_service_class', $class); + } + variable_del('apachesolr_logging'); +} + + +/** + * Re-jigger the schema to use fewer, shorter keys. + */ +function apachesolr_update_7001() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + if (db_field_exists('apachesolr_server', 'asid')) { + // You installed the beta1 and need to be fixed up. + db_drop_field('apachesolr_server', 'asid'); + db_drop_unique_key('apachesolr_server', 'server_id'); + db_add_primary_key('apachesolr_server', array('server_id')); + db_drop_unique_key('apachesolr_server', 'host_post_path'); + db_change_field('apachesolr_server', 'port', 'port', + array( + 'description' => 'Port number for the registered server', + 'type' => 'int', + 'not null' => TRUE, + ) + ); + } +} + +/** + * Create the per-server variable table. + */ +function apachesolr_update_7002() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + $schema['apachesolr_server_variable'] = array( + 'description' => 'Variable values for each Solr server.', + 'fields' => array( + 'server_id' => array( + 'description' => 'Unique identifier for the server', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'The name of the variable.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value of the variable.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array('server_id', 'name'), + ); + db_create_table('apachesolr_server_variable', $schema['apachesolr_server_variable']); + $server_id = variable_get('apachesolr_default_server', 'solr'); + // Variables to be migrated: + $conf['apachesolr_enabled_facets'] = variable_get('apachesolr_enabled_facets', NULL); + $conf['apachesolr_search_query_fields'] = variable_get('apachesolr_search_query_fields', NULL); + $conf['apachesolr_search_type_boosts'] = variable_get('apachesolr_search_type_boosts', NULL); + $conf['apachesolr_search_comment_boost'] = variable_get('apachesolr_search_comment_boost', NULL); + $conf['apachesolr_search_changed_boost'] = variable_get('apachesolr_search_changed_boost', NULL); + $conf['apachesolr_search_sticky_boost'] = variable_get('apachesolr_search_sticky_boost', NULL); + $conf['apachesolr_search_promote_boost'] = variable_get('apachesolr_search_promote_boost', NULL); + $conf['apachesolr_search_excluded_types'] = variable_get('apachesolr_search_excluded_types', NULL); + foreach ($conf as $name => $value) { + if ($value !== NULL) { + db_merge('apachesolr_server_variable') + ->key(array('server_id' => $server_id, 'name' => $name)) + ->fields(array('value' => serialize($value))) + ->execute(); + } + variable_del($name); + } +} + +/** + * Move excluded comment types into a new variable. + */ +function apachesolr_update_7003() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + // Same as apachesolr_update_6006() + $exclude_comment_types = variable_get('apachesolr_exclude_comments_types', NULL); + if (is_array($exclude_comment_types)) { + $exclude = array(); + foreach ($exclude_comment_types as $type) { + $exclude[$type]['comment'] = TRUE; + } + variable_set('apachesolr_exclude_nodeapi_types', $exclude); + } + variable_del('apachesolr_exclude_comments_types'); +} + +/** + * Update apachesolr_failure variable. + */ +function apachesolr_update_7004() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + $failure = variable_get('apachesolr_failure', NULL); + switch ($failure) { + case 'show_error': + variable_set('apachesolr_failure', 'apachesolr:show_error'); + break; + case 'show_drupal_results': + variable_set('apachesolr_failure', 'node'); + break; + case 'show_no_results': + variable_set('apachesolr_failure', 'apachesolr:show_no_results'); + break; + } +} + +/** + * Re-jigger the schema to use just a url column. + */ +function apachesolr_update_7005() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + if (db_field_exists('apachesolr_server', 'port')) { + // You installed the beta3 and need to be fixed up. + $servers = db_query('SELECT * FROM {apachesolr_server}')->fetchAllAssoc('server_id', PDO::FETCH_ASSOC); + db_drop_field('apachesolr_server', 'scheme'); + db_drop_field('apachesolr_server', 'port'); + db_drop_field('apachesolr_server', 'path'); + db_change_field('apachesolr_server', 'host', 'url', + array( + 'description' => 'Full url for the server', + 'type' => 'varchar', + 'length' => 1000, + 'not null' => TRUE, + ) + ); + foreach ($servers as $id => $server) { + $port = $server['port'] ? ':' . $server['port'] : ''; + $url = $server['scheme'] . '://' . $server['host'] . $port . $server['path']; + db_update('apachesolr_server') + ->fields(array('url' => $url)) + ->condition('server_id', $id) + ->execute(); + } + } +} + +/** + * Remove facet-related variable deprecated by the Facet API integration. + */ +function apachesolr_update_7006() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + variable_del('apachesolr_facetstyle'); + variable_del('apachesolr_facet_show_children'); + variable_del('apachesolr_facet_query_limits'); + variable_del('apachesolr_facet_query_limit_default'); +} + +/** + * Rename tables to make them more generic. + */ +function apachesolr_update_7007() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_primary_key('apachesolr_server'); + db_drop_primary_key('apachesolr_server_variable'); + db_rename_table('apachesolr_server', 'apachesolr_environment'); + db_rename_table('apachesolr_server_variable', 'apachesolr_environment_variable'); + db_change_field('apachesolr_environment', 'server_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_change_field('apachesolr_environment_variable', 'server_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_add_primary_key('apachesolr_environment', array('env_id')); + db_add_primary_key('apachesolr_environment_variable', array('env_id', 'name')); + $id = variable_get('apachesolr_default_server', NULL); + if (isset($id)) { + variable_set('apachesolr_default_environment', $id); + } + variable_del('apachesolr_default_server'); +} + +/** + * Remove more facet-related variable deprecated by the Facet API integration. + */ +function apachesolr_update_7008() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + variable_del('apachesolr_facet_missing'); + variable_del('apachesolr_facet_query_initial_limits'); + variable_del('apachesolr_facet_query_sorts'); + variable_del('apachesolr_facet_sort_active'); + variable_del('apachesolr_operator'); +} + +/** + * Update Facet API block deltas to account for removal of numeric ID from field names. + */ +function apachesolr_update_7009() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + // Only run when facetapi is available and/or installed + if (module_exists('facetapi')) { + module_load_include('inc', 'facetapi', 'facetapi.block'); + // Get all searchers + $searchers = facetapi_get_searcher_info(); + $realms = facetapi_get_realm_info(); + foreach ($searchers as $searcher_id => $searcher) { + foreach ($realms as $realm_id => $realm) { + foreach (field_info_fields() as $field_name => $field) { + // Generate the old delta + $facet_name_old = $field['id'] . '_' . $field['field_name']; + $delta_old = facetapi_build_delta($searcher['name'], $realm['name'], $facet_name_old); + $delta_old = substr(drupal_hash_base64($delta_old), 0, 32); + // Generate the new delta + $facet_name = $field['field_name']; + $delta = facetapi_build_delta($searcher['name'], $realm['name'], $facet_name); + $delta = substr(drupal_hash_base64($delta), 0, 32); + db_update('block') + ->fields(array('delta' => $delta)) + ->condition('module', 'facetapi') + ->condition('delta', $delta_old) + ->execute(); + } + } + } + } +} + +/** + * Update cache table schema for Drupal 7. + */ +function apachesolr_update_7010() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_field('cache_apachesolr', 'headers'); + return 'Updated cache table schema for Drupal 7.'; +} + +/** + * Change the namespace for the indexer from apachesolr_search to apachesolr + */ +function apachesolr_update_7011() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + $stored = variable_get('apachesolr_index_last', array()); + if (isset($stored['apachesolr_search'])) { + $stored['apachesolr'] = $stored['apachesolr_search']; + unset($stored['apachesolr_search']); + variable_set('apachesolr_index_last', $stored); + } + return 'Updated the namespace variable for the index process.'; +} + +/** + * Rename some variables and update the database tables + */ +function apachesolr_update_7012() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + // @see: drupal_load() + if (!function_exists('apachesolr_default_environment')) { + include_once dirname(__FILE__) . '/apachesolr.module'; + } + + $env_id = apachesolr_default_environment(); + + // Variable changed from integer to array with environment integers + $stored = variable_get('apachesolr_index_last', array()); + if (isset($stored['apachesolr'])) { + $stored[$env_id]['node']['last_entity_id'] = $stored['apachesolr']['last_nid']; + $stored[$env_id]['node']['last_changed'] = $stored['apachesolr']['last_change']; + unset($stored['apachesolr']); + variable_set('apachesolr_index_last', $stored); + } + $last = variable_get('apachesolr_index_updated', NULL); + if (isset($last)) { + variable_set('apachesolr_index_updated', array($env_id => (int) $last)); + } + + // Change namespace to environment id + $excluded_types = apachesolr_environment_variable_get('apachesolr', 'apachesolr_search_excluded_types', array()); + if (!empty($excluded_types)) { + apachesolr_environment_variable_set($env_id, 'apachesolr_search_excluded_types', $excluded_types); + apachesolr_environment_variable_del('apachesolr', 'apachesolr_search_excluded_types'); + } + + // Install the new schema + //Predefine an amount of types that get their own table + $types = array( + 'other' => 'apachesolr_index_entities', + 'node' => 'apachesolr_index_entities_node', + ); + foreach ($types as $type => $table) { + $schema[$table] = array( + 'description' => 'Stores a record of when an entity changed to determine if it needs indexing by Solr.', + 'fields' => array( + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'entity_id' => array( + 'description' => 'The primary identifier for an entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to which this entity belongs.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the entity is visible to non-administrators (eg, published for nodes).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when an entity was changed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'changed' => array('bundle', 'status', 'changed'), + ), + 'primary key' => array('entity_id'), + ); + if ($type == 'other') { + // Need the entity type also in the pkey for multiple entities in one table. + $schema[$table]['primary key'][] = 'entity_type'; + } + // Create the table + db_create_table($table, $schema[$table]); + } + + $schema['apachesolr_index_bundles'] = array( + 'description' => 'Records what bundles we should be indexing for a given environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'The name of the environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to index.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + ), + 'primary key' => array('env_id', 'entity_type', 'bundle'), + ); + db_create_table('apachesolr_index_bundles', $schema['apachesolr_index_bundles']); + + + // Move the data from apachesolr_search_node to apachesolr_index_entities_node + $select = db_select('apachesolr_search_node', 'asn'); + $select->join('node', 'n', 'asn.nid = n.nid'); + $select->addField('n', 'nid', 'entity_id'); + $select->addField('n', 'type', 'bundle'); + $select->addField('asn', 'status', 'status'); + $select->addField('asn', 'changed', 'changed'); + $select->addExpression("'node'", 'entity_type'); + $return_value = db_insert('apachesolr_index_entities_node') + ->fields(array('entity_id', 'bundle', 'status', 'changed', 'entity_type')) + ->from($select) + ->execute(); + // Drop the table apachesolr_search_node + db_drop_table('apachesolr_search_node'); + + $environments = apachesolr_load_all_environments(); + foreach ($environments as $env_id => $environment) { + $excluded_types = apachesolr_environment_variable_get($env_id, 'apachesolr_search_excluded_types', array()); + // Get indexable entity types + $options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if ($entity_type == 'node') { + foreach ($entity_info['bundles'] as $key => $info) { + // See if it was excluded & only of entity node. We will not enable + // other entity types by default + if (empty($excluded_types[$key])) { + $options[$entity_type][$key] = $key; + } + } + } + } + // Set all except the excluded types + // @see apachesolr_index_set_bundles() + foreach ($options as $entity_type => $bundles) { + db_delete('apachesolr_index_bundles') + ->condition('env_id', $env_id) + ->condition('entity_type', $entity_type) + ->execute(); + + if ($bundles) { + $insert = db_insert('apachesolr_index_bundles') + ->fields(array('env_id', 'entity_type', 'bundle')); + + foreach ($bundles as $bundle) { + $insert->values(array( + 'env_id' => $env_id, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + )); + } + $insert->execute(); + } + } + // Remove the excluded types + apachesolr_environment_variable_del($env_id, 'apachesolr_search_excluded_types'); + } +} + +/** + * Make consistent (and reduce) field lengths which cause excess pkey length. + */ +function apachesolr_update_7013() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_primary_key('apachesolr_index_entities'); + db_change_field('apachesolr_index_entities', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_add_primary_key('apachesolr_index_entities', array('entity_id', 'entity_type')); + db_change_field('apachesolr_index_entities_node', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_drop_primary_key('apachesolr_index_bundles'); + db_change_field('apachesolr_index_bundles', 'env_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_change_field('apachesolr_index_bundles', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_add_primary_key('apachesolr_index_bundles', array('env_id', 'entity_type', 'bundle')); +} + +/** + * Remove status from the key. + */ +function apachesolr_update_7014() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + $types = array( + 'other' => 'apachesolr_index_entities', + 'node' => 'apachesolr_index_entities_node', + ); + foreach ($types as $type => $table) { + db_drop_index($table, 'changed'); + db_add_index($table, 'bundle_changed', array('bundle', 'changed')); + } +} + + +/** + * Fix primary key schema mismatch for those who cleanly installed with beta16. + */ +function apachesolr_update_7015() { + if (variable_get('apachesolr_update_from_6303', FALSE)) { + return NULL; + } + + // Brand new installations since update_7013 have the wrong primary key. + db_drop_primary_key('apachesolr_index_entities'); + db_add_primary_key('apachesolr_index_entities', array('entity_id', 'entity_type')); +} + +/** + * Clean up apachesolr_update_from_6303. + * + * This variable had been used to bypass 7.x-1.x updates which are redundant + * with 6.x-3.x. + */ +function apachesolr_update_7016() { + variable_del('apachesolr_update_from_6303'); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.interface.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.interface.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,544 @@ +abort_search is TRUE. + * + * @param string $keys + * The search keys. + * + * @return + * A stdClass response object. + */ + function search($keys = NULL); + + /** + * Calls a method, without arguments, on the Solr object with which the query + * object was initialized. + * + * @param string $method + * The method to call on the Solr object. + * + * @return + * Any method return. + */ + function solr($method); +} + +/** + * The interface for all 'Service' objects. + */ +interface DrupalApacheSolrServiceInterface { + /** + * Call the /admin/ping servlet, to test the connection to the server. + * + * @param $timeout + * maximum time to wait for ping in seconds, -1 for unlimited (default 2). + * @return + * (float) seconds taken to ping the server, FALSE if timeout occurs. + */ + function ping($timeout = 2); + + /** + * Get information about the Solr Core. + * + * @return + * (string) system info encoded in json + */ + function getSystemInfo(); + + /** + * Get just the field meta-data about the index. + */ + function getFields($num_terms = 0); + + /** + * Get meta-data about the index. + */ + function getLuke($num_terms = 0); + + /** + * Get information about the Solr Core. + * + * Returns a Simple XMl document + */ + function getStats(); + + /** + * Get summary information about the Solr Core. + */ + function getStatsSummary(); + + /** + * Clear cached Solr data. + */ + function clearCache(); + + /** + * Constructor + * + * @param $url + * The URL to the Solr server, possibly including a core name. E.g. http://localhost:8983/solr/ + * or https://search.example.com/solr/core99/ + * @param $env_id + * The machine name of a corresponding saved configuration used for loading + * data like which facets are enabled. + */ + function __construct($url, $env_id = NULL); + + function getId(); + + /** + * Make a request to a servlet (a path) that's not a standard path. + * + * @param string $servlet + * A path to be added to the base Solr path. e.g. 'extract/tika' + * + * @param array $params + * Any request parameters when constructing the URL. + * + * @param array $options + * @see drupal_http_request() $options. + * + * @return + * response object + * + * @thows Exception + */ + function makeServletRequest($servlet, $params = array(), $options = array()); + + /** + * Get the Solr url + * + * @return string + */ + function getUrl(); + + /** + * Set the Solr url. + * + * @param $url + * + * @return $this + */ + function setUrl($url); + + /** + * Raw update Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed xml document. + * + * @param string $rawPost + * @param float $timeout Maximum expected duration (in seconds) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function update($rawPost, $timeout = FALSE); + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of ApacheSolrDocument instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * + * @return response objecte + * + * @throws Exception If an error occurs during the service call + */ + function addDocuments($documents, $overwrite = NULL, $commitWithin = NULL); + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $optimize Defaults to true + * @param boolean $waitFlush Defaults to true + * @param boolean $waitSearcher Defaults to true + * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600); + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteById($id, $timeout = 3600); + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByMultipleIds($ids, $timeout = 3600); + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return stdClass response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByQuery($rawQuery, $timeout = 3600); + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * @param boolean $waitSearcher + * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function optimize($waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600); + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function search($query = '', array $params = array(), $method = 'GET'); + + /** + * Get the current solr version. This could be 1, 3 or 4 + * + * @return int + * 1, 3 or 4. Does not give a more details version, for that you need + * to get the system info. + */ + function getSolrVersion(); + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,2793 @@ + '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/solrsearch/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 of type @type.', array('@type' => $field_info['field']['type'])), + '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), + ), + ); + + return $facets; +} + +/** + * 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)); + $callback = apachesolr_entity_get_callback('node', 'bundles changed callback'); + if (!empty($callback)) { + call_user_func($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 = variable_get('apachesolr_index_last', array()); + return isset($stored[$env_id][$entity_type]) ? $stored[$env_id][$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 = variable_get('apachesolr_index_last', array()); + $stored[$env_id][$entity_type] = array('last_changed' => $last_changed, 'last_entity_id' => $last_entity_id); + variable_set('apachesolr_index_last', $stored); +} + +/** + * Clear a specific environment, or clear all. + */ +function apachesolr_clear_last_index_position($env_id = NULL, $entity_type = NULL) { + $stored = variable_get('apachesolr_index_last', array()); + if (empty($env_id)) { + $stored = array(); + } + elseif ($entity_type) { + unset($stored[$env_id][$entity_type]); + } + else { + unset($stored[$env_id]); + } + variable_set('apachesolr_index_last', $stored); +} + +/** + * 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) { + $updated = variable_get('apachesolr_index_updated', array()); + if ($timestamp > 0) { + $updated[$env_id] = $timestamp; + } + else { + unset($updated[$env_id]); + } + variable_set('apachesolr_index_updated', $updated); +} + +/** + * Get the timestamp of the last index update. + * @return integer (timestamp) + */ +function apachesolr_get_last_index_updated($env_id) { + $updated = variable_get('apachesolr_index_updated', array()); + return isset($updated[$env_id]) ? $updated[$env_id] : 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); + $last = variable_get('apachesolr_last_optimize', 0); + $time = REQUEST_TIME; + + foreach($environments as $env_id) { + // 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); + variable_set('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); + $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(); + if ($is_default && !user_access($permission)) { + return FALSE; + } + return TRUE; +} + +/** + * 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' => ''); + + $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) { + 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) { + db_delete('apachesolr_environment_variable') + ->condition('env_id', $env_id) + ->condition('name', $name) + ->execute(); + apachesolr_environments_clear_cache(); +} + +/** + * 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); + } + } + + foreach (module_implements('apachesolr_query_prepare') as $module) { + $function_name = $module . '_apachesolr_query_prepare'; + $function_name($current_query); + } + + // Cache the original query. Since all the built queries go through + // this process, all the hook_invocations will happen later + $env_id = $current_query->solr('getId'); + + // Add our defType setting here. Normally this would be dismax or the setting + // from the solrconfig.xml. This allows the setting to be overridden. + $defType = apachesolr_environment_variable_get($env_id, 'apachesolr_query_type'); + if (!empty($defType)) { + $current_query->addParam('defType', $defType); + } + + $query = apachesolr_current_query($env_id, $current_query); + + // Verify if this query was already executed in the same page load + if ($response = apachesolr_static_response_cache($searcher)) { + // Return cached query object + return array($query, $response); + } + $query->addParam('start', $query->page * $query->getParam('rows')); + + // This hook allows modules to modify the query and params objects. + drupal_alter('apachesolr_query', $query); + + if ($query->abort_search) { + // A module implementing HOOK_apachesolr_query_alter() aborted the search. + return array(NULL, array()); + } + + + $keys = $query->getParam('q'); + + if (strlen($keys) == 0 && ($filters = $query->getFilters())) { + // Move the fq params to q.alt for better performance. Only suitable + // when using dismax or edismax, so we keep this out of the query class itself + // for now. + $qalt = array(); + foreach ($filters as $delta => $filter) { + // Move the fq param if it has no local params and is not negative. + if (!$filter['#exclude'] && !$filter['#local']) { + $qalt[] = '(' . $query->makeFilterQuery($filter) . ')'; + $query->removeFilter($filter['#name'], $filter['#value'], $filter['#exclude']); + } + } + if ($qalt) { + $query->addParam('q.alt', implode(' ', $qalt)); + } + } + // We must run htmlspecialchars() here since converted entities are in the index. + // and thus bare entities &, > or < won't match. Single quotes are converted + // too, but not double quotes since the dismax parser looks at them for + // phrase queries. + $keys = htmlspecialchars($keys, ENT_NOQUOTES, 'UTF-8'); + $keys = str_replace("'", ''', $keys); + $response = $query->search($keys); + // The response is cached so that it is accessible to the blocks and anything + // else that needs it beyond the initial search. + apachesolr_static_response_cache($searcher, $response); + return array($query, $response); +} + +/** + * It is important to hold on to the Solr response object for the duration of the + * page request so that we can use it for things like building facet blocks. + * + * @param $searcher + * Name of the searcher - e.g. from $query->getSearcher(). + */ +function apachesolr_static_response_cache($searcher, $response = NULL) { + $_response = &drupal_static(__FUNCTION__, array()); + + if (is_object($response)) { + $_response[$searcher] = clone $response; + } + if (!isset($_response[$searcher])) { + $_response[$searcher] = NULL; + } + return $_response[$searcher]; +} + +/** + * Factory function for query objects. + * + * @param string $name + * The search name, used for finding the correct blocks and other config. + * Typically "apachesolr". + * @param array $params + * Array of params , such as 'q', 'fq' to be applied. + * @param string $solrsort + * Visible string telling solr how to sort. + * @param string $base_path + * The search base path (without the keywords) for this query. + * @param DrupalApacheSolrServiceInterface $solr + * An instance of DrupalApacheSolrServiceInterface. + * + * @return DrupalSolrQueryInterface + * DrupalSolrQueryInterface object. + * + * @throws Exception + */ +function apachesolr_drupal_query($name, array $params = array(), $solrsort = '', $base_path = '', DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) { + if (!interface_exists('DrupalSolrQueryInterface')) { + require_once(dirname(__FILE__) . '/apachesolr.interface.inc'); + } + $class_info = variable_get('apachesolr_query_class', array( + 'file' => 'Solr_Base_Query', + 'module' => 'apachesolr', + 'class' => 'SolrBaseQuery')); + $class = $class_info['class']; + if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) { + module_load_include('php', $class_info['module'], $class_info['file']); + } + if (empty($solr)) { + $solr = apachesolr_get_solr(); + } + return new $class($name, $solr, $params, $solrsort, $base_path, $context); +} + +/** + * Factory function for query objects. + * + * @param $operator + * Whether the subquery should be added to another query as OR or AND + * + * @return DrupalSolrQueryInterface|false + * Subquery or error. + * + * @throws Exception + */ +function apachesolr_drupal_subquery($operator = 'OR') { + if (!interface_exists('DrupalSolrQueryInterface')) { + require_once(dirname(__FILE__) . '/apachesolr.interface.inc'); + } + + $class_info = variable_get('apachesolr_subquery_class', array( + 'file' => 'Solr_Base_Query', + 'module' => 'apachesolr', + 'class' => 'SolrFilterSubQuery')); + $class = $class_info['class']; + if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) { + module_load_include('php', $class_info['module'], $class_info['file']); + } + $query = new $class($operator); + return $query; +} + +/** + * Static getter/setter for the current query. Only set once per page. + * + * @param $env_id + * Environment from which to save or get the current query + * @param DrupalSolrQueryInterface $query + * $query object to save in the static + * + * @return DrupalSolrQueryInterface|null + * return the $query object if it is available in the drupal_static or null otherwise + */ +function apachesolr_current_query($env_id, DrupalSolrQueryInterface $query = NULL) { + $saved_query = &drupal_static(__FUNCTION__, NULL); + if (is_object($query)) { + $saved_query[$env_id] = clone $query; + } + if (empty($saved_query[$env_id])) { + return NULL; + } + return is_object($saved_query[$env_id]) ? clone $saved_query[$env_id] : NULL; +} + +/** + * + */ + +/** + * Construct a dynamic index name based on information about a field. + * + * @param array $field + * array( + * 'index_type' => 'integer', + * 'multiple' => TRUE, + * 'name' => 'fieldname', + * ), + * @return string + * Fieldname as it appears in the solr index + */ +function apachesolr_index_key($field) { + $index_type = !empty($field['index_type']) ? $field['index_type'] : NULL; + switch ($index_type) { + case 'text': + $type_prefix = 't'; + break; + case 'text-omitNorms': + $type_prefix = 'to'; + break; + case 'text-unstemmed': + $type_prefix = 'tu'; + break; + case 'text-edgeNgram': + $type_prefix = 'te'; + break; + case 'text-whiteSpace': + $type_prefix = 'tw'; + break; + case 'integer': + $type_prefix = 'i'; // long integer + break; + case 'half-int': + $type_prefix = 'h'; // 32 bit integer + break; + case 'float': + $type_prefix = 'f'; // float; sortable. + break; + case 'double': + $type_prefix = 'p'; // double; sortable d was used for date. + break; + case 'boolean': + $type_prefix = 'b'; + break; + case 'tint': + $type_prefix = 'it'; // long integer trie; sortable, best for range queries + break; + case 'thalf-int': + $type_prefix = 'ht'; // 32 bit integer trie (sortable) + break; + case 'tfloat': + $type_prefix = 'ft'; // float trie; sortable, best for range queries. + break; + case 'tdouble': + $type_prefix = 'pt'; // double trie; + break; + case 'sint': + $type_prefix = 'is'; // long integer sortable (deprecated) + break; + case 'half-sint': + $type_prefix = 'hs'; // 32 bit integer long sortable (deprecated) + break; + case 'sfloat': + $type_prefix = 'fs'; // float, sortable (use for sorting missing last) (deprecated). + break; + case 'sdouble': + $type_prefix = 'ps'; // double sortable; (use for sorting missing last) (deprecated). + break; + case 'date': + $type_prefix = 'd'; // date trie (sortable) + break; + case 'date-deprecated': + $type_prefix = 'dd'; // date (regular) + break; + case 'binary': + $type_prefix = 'x'; // Anything that is base64 encoded + break; + case 'storage': + $type_prefix = 'z'; // Anything that just need to be stored, not indexed + break; + case 'point': + $type_prefix = 'point'; // PointType. "52.3672174,4.9126891" + break; + case 'location': + $type_prefix = 'loc'; // LatLonType. "52.3672174,4.9126891" + break; + case 'geohash': + $type_prefix = 'geo'; // GeohashField. "42.6" http://en.wikipedia.org/wiki/Geohash + break; + case 'string': + default: + $type_prefix = 's'; // String + } + $sm = !empty($field['multiple']) ? 'm_' : 's_'; + // Block deltas are limited to 32 chars. + return substr($type_prefix . $sm . $field['name'], 0, 32); +} + +/** + * Try to map a schema field name to a human-readable description. + */ +function apachesolr_field_name_map($field_name) { + $map = &drupal_static(__FUNCTION__); + + if (!isset($map)) { + $map = array( + 'content' => t('The full, rendered content (e.g. the rendered node body)'), + 'ts_comments' => t('The rendered comments associated with a node'), + 'tos_content_extra' => t('Extra rendered content or keywords'), + 'tos_name_formatted' => t('Author name (Formatted)'), + 'label' => t('Title or label'), + 'teaser' => t('Teaser or preview'), + 'tos_name' => t('Author name'), + 'path_alias' => t('Path alias'), + 'taxonomy_names' => t('All taxonomy term names'), + 'tags_h1' => t('Body text inside H1 tags'), + 'tags_h2_h3' => t('Body text inside H2 or H3 tags'), + 'tags_h4_h5_h6' => t('Body text inside H4, H5, or H6 tags'), + 'tags_inline' => t('Body text in inline tags like EM or STRONG'), + 'tags_a' => t('Body text inside links (A tags)'), + 'tid' => t('Taxonomy term IDs'), + 'is_uid' => t('User IDs'), + 'bundle' => t('Content type names eg. article'), + 'entity_type' => t('Entity type names eg. node'), + 'ss_language' => t('Language type eg. en or und (undefinded)'), + ); + if (module_exists('taxonomy')) { + foreach (taxonomy_get_vocabularies() as $vocab) { + $map['tm_vid_' . $vocab->vid . '_names'] = t('Taxonomy term names only from the %name vocabulary', array('%name' => $vocab->name)); + $map['im_vid_' . $vocab->vid] = t('Taxonomy term IDs from the %name vocabulary', array('%name' => $vocab->name)); + } + } + foreach (apachesolr_entity_fields('node') as $field_nm => $nodefields) { + foreach ($nodefields as $field_info) { + $map[apachesolr_index_key($field_info)] = t('Field of type @type: %label', array('@type' => $field_info['field']['type'], '%label' => $field_info['display_name'])); + } + } + drupal_alter('apachesolr_field_name_map', $map); + } + return isset($map[$field_name]) ? $map[$field_name] : $field_name; +} + +/** + * Validation function for the Facet API facet settings form. + * + * Apache Solr does not support the combination of OR facets + * and facet missing, so catch that at validation. + */ +function apachesolr_facet_form_validate($form, &$form_state) { + if (($form_state['values']['global']['operator'] == FACETAPI_OPERATOR_OR) && $form_state['values']['global']['facet_missing']) { + form_set_error('operator', t('Apache Solr does not support facet missing in combination with the OR operator.')); + } +} + +/** + * Implements hook_entity_info_alter(). + */ +function apachesolr_entity_info_alter(&$entity_info) { + // Load all environments + $environments = apachesolr_load_all_environments(); + + // Set those values that we know. Other modules can do so + // for their own entities if they want. + $default_entity_info = array(); + $default_entity_info['node']['indexable'] = TRUE; + $default_entity_info['node']['status callback'][] = 'apachesolr_index_node_status_callback'; + $default_entity_info['node']['document callback'][] = 'apachesolr_index_node_solr_document'; + $default_entity_info['node']['reindex callback'] = 'apachesolr_index_node_solr_reindex'; + $default_entity_info['node']['bundles changed callback'] = 'apachesolr_index_node_bundles_changed'; + $default_entity_info['node']['index_table'] = 'apachesolr_index_entities_node'; + $default_entity_info['node']['cron_check'] = 'apachesolr_index_node_check_table'; + // apachesolr_search implements a new callback for every entity type + // $default_entity_info['node']['apachesolr']['result callback'] = 'apachesolr_search_node_result'; + //Allow implementations of HOOK_apachesolr_entity_info to modify these default indexers + drupal_alter('apachesolr_entity_info', $default_entity_info); + + // First set defaults so that we don't need to worry about NULL keys. + foreach (array_keys($entity_info) as $type) { + if (!isset($entity_info[$type]['apachesolr'])) { + $entity_info[$type]['apachesolr'] = array(); + } + if (isset($default_entity_info[$type])) { + $entity_info[$type]['apachesolr'] += $default_entity_info[$type]; + } + $default = array( + 'indexable' => FALSE, + 'status callback' => '', + 'document callback' => '', + 'reindex callback' => '', + 'bundles changed callback' => '', + ); + $entity_info[$type]['apachesolr'] += $default; + } + + // For any supported entity type and bundle, flag it for indexing. + foreach ($entity_info as $entity_type => $info) { + if ($info['apachesolr']['indexable']) { + // Loop over each environment and check if any of them have other entity + // bundles of any entity type enabled and set the index value to TRUE + foreach ($environments as $env) { + // Skip if the environment is set to read only + if (empty($env['env_id']['conf']['apachesolr_read_only'])) { + // Get the supported bundles + $supported = apachesolr_get_index_bundles($env['env_id'], $entity_type); + // For each bundle in drupal, compare to the supported apachesolr + // bundles and enable where possible + foreach (array_keys($info['bundles']) as $bundle) { + if (in_array($bundle, $supported)) { + $entity_info[$entity_type]['bundles'][$bundle]['apachesolr']['index'] = TRUE; + } + } + } + } + } + } +} + +/** + * Gets a list of the bundles on the specified entity type that should be indexed. + * + * @param string $core + * The Solr environment for which to index entities. + * @param string $entity_type + * The entity type to index. + * @return array + * The bundles that should be indexed. + */ +function apachesolr_get_index_bundles($env_id, $entity_type) { + $environment = apachesolr_environment_load($env_id); + return !empty($environment['index_bundles'][$entity_type]) ? $environment['index_bundles'][$entity_type] : array(); +} + +/** + * Implements hook_entity_insert(). + */ +function apachesolr_entity_insert($entity, $type) { + // For our purposes there's really no difference between insert and update. + return apachesolr_entity_update($entity, $type); +} + +/** + * Determines if we should index the provided entity. + * + * Whether or not a given entity is indexed is determined on a per-bundle basis. + * Entities/Bundles that have no index flag are presumed to not get indexed. + * + * @param stdClass $entity + * The entity we may or may not want to index. + * @param string $type + * The type of entity. + * @return boolean + * TRUE if this entity should be indexed, FALSE otherwise. + */ +function apachesolr_entity_should_index($entity, $type) { + $info = entity_get_info($type); + list($id, $vid, $bundle) = entity_extract_ids($type, $entity); + + if ($bundle && isset($info['bundles'][$bundle]['apachesolr']['index']) && $info['bundles'][$bundle]['apachesolr']['index']) { + return TRUE; + } + return FALSE; +} + +/** + * Implements hook_entity_update(). + */ +function apachesolr_entity_update($entity, $type) { + // Include the index file for the status callback + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + if (apachesolr_entity_should_index($entity, $type)) { + $info = entity_get_info($type); + list($id, $vid, $bundle) = entity_extract_ids($type, $entity); + + // Check status callback before sending to the index + $status_callbacks = apachesolr_entity_get_callback($type, 'status callback', $bundle); + + $status = TRUE; + if (is_array($status_callbacks)) { + foreach($status_callbacks as $status_callback) { + if (is_callable($status_callback)) { + // by placing $status in front we prevent calling any other callback + // after one status callback returned false + $status = $status && $status_callback($id, $type); + } + } + } + + // Delete the entity from our index if the status callback returns FALSE + if (!$status) { + apachesolr_entity_delete($entity, $type); + return NULL; + } + + $indexer_table = apachesolr_get_indexer_table($type); + + // If we haven't seen this entity before it may not be there, so merge + // instead of update. + db_merge($indexer_table) + ->key(array( + 'entity_type' => $type, + 'entity_id' => $id, + )) + ->fields(array( + 'bundle' => $bundle, + 'status' => 1, + 'changed' => REQUEST_TIME, + )) + ->execute(); + } +} + +/** + * Retrieve the indexer table for an entity type. + */ +function apachesolr_get_indexer_table($type) { + $entity_info = entity_get_info(); + if (isset($entity_info[$type]['apachesolr']['index_table'])) { + $indexer_table = $entity_info[$type]['apachesolr']['index_table']; + } + else { + $indexer_table = 'apachesolr_index_entities'; + } + return $indexer_table; +} + +/** + * Implements hook_entity_delete(). + */ +function apachesolr_entity_delete($entity, $entity_type) { + $env_id = apachesolr_default_environment(); + + // Delete the entity's entry from a fictional table of all entities. + $info = entity_get_info($entity_type); + list($entity_id) = entity_extract_ids($entity_type, $entity); + apachesolr_remove_entity($env_id, $entity_type, $entity_id); +} + +function apachesolr_remove_entity($env_id, $entity_type, $entity_id) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + + $indexer_table = apachesolr_get_indexer_table($entity_type); + if (apachesolr_index_delete_entity_from_index($env_id, $entity_type, $entity_id)) { + // There was no exception, so delete from the table. + db_delete($indexer_table) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + } + else { + // Set status 0 so we try to delete from the index again in the future. + db_update($indexer_table) + ->condition('entity_id', $entity_id) + ->fields(array('changed' => REQUEST_TIME, 'status' => 0)) + ->execute(); + } +} + +/** + * Returns array containing information about node fields that should be indexed + */ +function apachesolr_entity_fields($entity_type = 'node') { + $fields = &drupal_static(__FUNCTION__, array()); + + if (!isset($fields[$entity_type])) { + $fields[$entity_type] = array(); + + $mappings = module_invoke_all('apachesolr_field_mappings'); + foreach (array_keys($mappings) as $key) { + // Set all values with defaults. + $defaults = array( + 'dependency plugins' => array('bundle', 'role'), + 'map callback' => FALSE, + 'name callback' => '', + 'hierarchy callback' => FALSE, + 'indexing_callback' => '', + 'index_type' => 'string', + 'facets' => FALSE, + 'facet missing allowed' => FALSE, + 'facet mincount allowed' => FALSE, + // Field API allows any field to be multi-valued. + 'multiple' => FALSE, + ); + if ($key !== 'per-field') { + $mappings[$key] += $defaults; + } + else { + foreach (array_keys($mappings[$key]) as $field_key) { + $mappings[$key][$field_key] += $defaults; + } + } + } + + // Allow other modules to add or alter mappings. + drupal_alter('apachesolr_field_mappings', $mappings, $entity_type); + + $modules = system_get_info('module'); + $instances = field_info_instances($entity_type); + foreach (field_info_fields() as $field_name => $field) { + $row = array(); + if (isset($field['bundles'][$entity_type]) && (isset($mappings['per-field'][$field_name]) || isset($mappings[$field['type']]))) { + // Find the mapping. + if (isset($mappings['per-field'][$field_name])) { + $row = $mappings['per-field'][$field_name]; + } + else { + $row = $mappings[$field['type']]; + } + // The field info array. + $row['field'] = $field; + + // Cardinality: The number of values the field can hold. Legal values + // are any positive integer or FIELD_CARDINALITY_UNLIMITED. + if ($row['field']['cardinality'] != 1) { + $row['multiple'] = TRUE; + } + + // @todo: for fields like taxonomy we are indexing multiple Solr fields + // per entity field, but are keying on a single Solr field name here. + $function = !empty($row['name callback']) ? $row['name callback'] : NULL; + if ($function && is_callable($function)) { + $row['name'] = $function($field); + } + else { + $row['name'] = $field['field_name']; + } + $row['module_name'] = $modules[$field['module']]['name']; + // Set display name + $display_name = array(); + foreach ($field['bundles'][$entity_type] as $bundle) { + if (empty($instances[$bundle][$field_name]['display']['search_index']) || $instances[$bundle][$field_name]['display']['search_index'] != 'hidden') { + $row['display_name'] = $instances[$bundle][$field_name]['label']; + $row['bundles'][] = $bundle; + } + } + // Only add to the $fields array if some instances are displayed for the search index. + if (!empty($row['bundles'])) { + // Use the Solr index key as the array key. + $fields[$entity_type][apachesolr_index_key($row)][] = $row; + } + } + } + } + return $fields[$entity_type]; +} + +/** + * Implements hook_apachesolr_index_document_build(). + */ +function field_apachesolr_index_document_build(ApacheSolrDocument $document, $entity, $entity_type) { + $info = entity_get_info($entity_type); + if ($info['fieldable']) { + // Handle fields including taxonomy. + $indexed_fields = apachesolr_entity_fields($entity_type); + foreach ($indexed_fields as $index_key => $nodefields) { + foreach ($nodefields as $field_info) { + $field_name = $field_info['field']['field_name']; + // See if the node has fields that can be indexed + if (isset($entity->{$field_name})) { + // Got a field. + $function = $field_info['indexing_callback']; + if ($function && function_exists($function)) { + // NOTE: This function should always return an array. One + // entity field may be indexed to multiple Solr fields. + $fields = $function($entity, $field_name, $index_key, $field_info); + foreach ($fields as $field) { + // It's fine to use this method also for single value fields. + $document->setMultiValue($field['key'], $field['value']); + } + } + } + } + } + } +} + +/** + * Implements hook_apachesolr_index_document_build_node(). + * + * Adds book module support + */ +function apachesolr_apachesolr_index_document_build_node(ApacheSolrDocument $document, $entity, $env_id) { + // Index book module data. + if (!empty($entity->book['bid'])) { + // Hard-coded - must change if apachesolr_index_key() changes. + $document->is_book_bid = (int) $entity->book['bid']; + } +} + +/** + * Strip html tags and also control characters that cause Jetty/Solr to fail. + */ +function apachesolr_clean_text($text) { + // Remove invisible content. + $text = preg_replace('@<(applet|audio|canvas|command|embed|iframe|map|menu|noembed|noframes|noscript|script|style|svg|video)[^>]*>.*@siU', ' ', $text); + // Add spaces before stripping tags to avoid running words together. + $text = filter_xss(str_replace(array('<', '>'), array(' <', '> '), $text), array()); + // Decode entities and then make safe any < or > characters. + $text = htmlspecialchars(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); + // Remove extra spaces. + $text = preg_replace('/\s+/s', ' ', $text); + // Remove white spaces around punctuation marks probably added + // by the safety operations above. This is not a world wide perfect solution, + // but a rough attempt for at least US and Western Europe. + // Pc: Connector punctuation + // Pd: Dash punctuation + // Pe: Close punctuation + // Pf: Final punctuation + // Pi: Initial punctuation + // Po: Other punctuation, including ¿?¡!,.:; + // Ps: Open punctuation + $text = preg_replace('/\s(\p{Pc}|\p{Pd}|\p{Pe}|\p{Pf}|!|\?|,|\.|:|;)/s', '$1', $text); + $text = preg_replace('/(\p{Ps}|¿|¡)\s/s', '$1', $text); + return $text; +} + +/** + * Use the list.module's list_allowed_values() to format the + * field based on its value ($facet). + * + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + */ +function apachesolr_fields_list_facet_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = list_allowed_values($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + * @see http://drupal.org/node/1059372 + */ +function apachesolr_nodereference_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = node_reference_potential_references($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + * @see http://drupal.org/node/1059372 + */ +function apachesolr_userreference_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = user_reference_potential_references($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * Mapping callback for entity references. + */ +function apachesolr_entityreference_facet_map_callback(array $values, array $options) { + $map = array(); + // Gathers entity ids so we can load multiple entities at a time. + $entity_ids = array(); + foreach ($values as $value) { + list($entity_type, $id) = explode(':', $value); + $entity_ids[$entity_type][] = $id; + } + // Loads and maps entities. + foreach ($entity_ids as $entity_type => $ids) { + $entities = entity_load($entity_type, $ids); + foreach ($entities as $id => $entity) { + $key = $entity_type . ':' . $id; + $map[$key] = entity_label($entity_type, $entity); + } + } + return $map; +} + +/** + * Returns the callback function appropriate for a given entity type/bundle. + * + * @param string $entity_type + * The entity type for which we want to know the approprite callback. + * @param string $callback + * The callback for which we want the appropriate function. + * @param string $bundle + * If specified, the bundle of the entity in question. Some callbacks may + * be overridden on a bundle-level. Not specified only the entity-level + * callback will be checked. + * @return string + * The function name for this callback, or NULL if not specified. + */ +function apachesolr_entity_get_callback($entity_type, $callback, $bundle = NULL) { + $info = entity_get_info($entity_type); + + // A bundle-specific callback takes precedence over the generic one for the + // entity type. + if ($bundle && isset($info['bundles'][$bundle]['apachesolr'][$callback])) { + $callback_function = $info['bundles'][$bundle]['apachesolr'][$callback]; + } + elseif (isset($info['apachesolr'][$callback])) { + $callback_function = $info['apachesolr'][$callback]; + } + else { + $callback_function = NULL; + } + return $callback_function; +} + + +/** + * Function to retrieve all the nodes to index. + * Deprecated but kept for backwards compatibility + * @param String $namespace + * @param type $limit + */ +function apachesolr_get_nodes_to_index($namespace, $limit) { + $env_id = apachesolr_default_environment(); + // Hardcode node as an entity type + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + apachesolr_index_get_entities_to_index($env_id, 'node', $limit); +} + + +/** + * Implements hook_theme(). + */ +function apachesolr_theme() { + return array( + /** + * Returns a list of links generated by apachesolr_sort_link + */ + 'apachesolr_sort_list' => array( + 'variables' => array('items' => NULL), + ), + /** + * Returns a link which can be used to search the results. + */ + 'apachesolr_sort_link' => array( + 'variables' => array('text' => NULL, 'path' => NULL, 'options' => NULL, 'active' => FALSE, 'direction' => ''), + ), + /** + * Themes the title links in admin settings pages. + */ + 'apachesolr_settings_title' => array( + 'variables' => array('env_id' => NULL), + ), + ); +} + +/** + * Implements hook_hook_info(). + */ +function apachesolr_hook_info() { + $hooks = array( + 'apachesolr_field_mappings' => array( + 'group' => 'apachesolr', + ), + 'apachesolr_field_mappings_alter' => array( + 'group' => 'apachesolr', + ), + 'apachesolr_query_prepare' => array( + 'group' => 'apachesolr', + ), + 'apachesolr_query_alter' => array( + 'group' => 'apachesolr', + ), + 'apachesolr_search_result_alter' => array( + 'group' => 'apachesolr', + ), + 'apachesolr_environment_delete' => array( + 'group' => 'apachesolr', + ) + ); + $hooks['apachesolr_index_document_build'] = array( + 'group' => 'apachesolr', + ); + return $hooks; +} + +/** + * Implements hook_apachesolr_field_mappings(). + */ +function field_apachesolr_field_mappings() { + $mappings = array( + 'list_integer' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'map callback' => 'apachesolr_fields_list_facet_map_callback', + 'index_type' => 'integer', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet missing allowed' => TRUE, + ), + 'list_float' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'map callback' => 'apachesolr_fields_list_facet_map_callback', + 'index_type' => 'float', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet missing allowed' => TRUE, + ), + 'list_text' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'map callback' => 'apachesolr_fields_list_facet_map_callback', + 'index_type' => 'string', + 'facets' => TRUE, + 'facet missing allowed' => TRUE, + ), + 'list_boolean' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'map callback' => 'apachesolr_fields_list_facet_map_callback', + 'index_type' => 'boolean', + 'facets' => TRUE, + 'facet missing allowed' => TRUE, + ), + 'number_integer' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'index_type' => 'tint', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'number_decimal' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'index_type' => 'tfloat', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'number_float' => array( + 'indexing_callback' => 'apachesolr_fields_default_indexing_callback', + 'index_type' => 'tfloat', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'taxonomy_term_reference' => array( + 'map callback' => 'facetapi_map_taxonomy_terms', + 'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy', + 'indexing_callback' => 'apachesolr_term_reference_indexing_callback', + 'index_type' => 'integer', + 'facet_block_callback' => 'apachesolr_search_taxonomy_facet_block', + 'facets' => TRUE, + 'query types' => array('term'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + ); + + return $mappings; +} + +/** + * Implements hook_apachesolr_field_mappings() on behalf of date module. + */ +function date_apachesolr_field_mappings() { + $mappings = array(); + $default = array( + 'indexing_callback' => 'apachesolr_date_default_indexing_callback', + 'index_type' => 'date', + 'facets' => TRUE, + 'query types' => array('date'), + 'query type' => 'date', + 'min callback' => 'apachesolr_get_min_date', + 'max callback' => 'apachesolr_get_max_date', + 'map callback' => 'facetapi_map_date', + ); + + // DATE and DATETIME fields can use the same indexing callback. + $mappings['date'] = $default; + $mappings['datetime'] = $default; + + // DATESTAMP fields need a different callback. + $mappings['datestamp'] = $default; + $mappings['datestamp']['indexing_callback'] = 'apachesolr_datestamp_default_indexing_callback'; + + return $mappings; +} + + +/** + * Callback that returns the minimum date of the facet's datefield. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The minimum time in the node table. + * + * @todo Cache this value. + */ +function apachesolr_get_min_date(array $facet) { + // FieldAPI date fields. + $table = 'field_data_' . $facet['field api name']; + $column = $facet['field api name'] . '_value'; + $query = db_select($table, 't'); + $query->addExpression('MIN(' . $column . ')', 'min'); + $query_min = $query->execute()->fetch()->min; + // Update to unix timestamp if this is an ISO or other format. + if (!is_int($query_min)) { + $return = strtotime($query_min); + if ($return === FALSE) { + // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00'). + // Return default start date of 1 as the date query type getDateRange() + // function expects a non-0 integer. + $return = 1; + } + } + return $return; +} + +/** + * Callback that returns the maximum value of the facet's date field. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The maximum time of the field. + * + * @todo Cache this value. + */ +function apachesolr_get_max_date(array $facet) { + + // FieldAPI date fields. + $table = 'field_data_' . $facet['field api name']; + $column = $facet['field api name'] . '_value'; + $query = db_select($table, 't'); + $query->addExpression('MAX(' . $column . ')', 'max'); + $query_max = $query->execute()->fetch()->max; + // Update to unix timestamp if this is an ISO or other format. + if (!is_int($query_max)) { + $return = strtotime($query_max); + if ($return === FALSE) { + // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00'). + // Return default end date of 1 year from now. + $return = time() + (52 * 7 * 24 * 60 * 60); + } + } + return $return; +} + +/** + * Implements hook_apachesolr_field_mappings() on behalf of References (node_reference). + * @see http://drupal.org/node/1059372 + */ +function node_reference_apachesolr_field_mappings() { + $mappings = array( + 'node_reference' => array( + 'indexing_callback' => 'apachesolr_nodereference_indexing_callback', + 'index_type' => 'integer', + 'map callback' => 'apachesolr_nodereference_map_callback', + 'facets' => TRUE, + ) + ); + + return $mappings; +} + +/** + * Implements hook_apachesolr_field_mappings() on behalf of References (user_reference). + * @see http://drupal.org/node/1059372 + */ +function user_reference_apachesolr_field_mappings() { + $mappings = array( + 'user_reference' => array( + 'indexing_callback' => 'apachesolr_userreference_indexing_callback', + 'index_type' => 'integer', + 'map callback' => 'apachesolr_userreference_map_callback', + 'facets' => TRUE, + ), + ); + + return $mappings; +} +/** + * Implements hook_apachesolr_field_mappings() on behalf of EntityReferences (entityreference) + * @see http://drupal.org/node/1572722 + */ +function entityreference_apachesolr_field_mappings() { + $mappings = array( + 'entityreference' => array( + 'indexing_callback' => 'apachesolr_entityreference_indexing_callback', + 'map callback' => 'apachesolr_entityreference_facet_map_callback', + 'index_type' => 'string', + 'facets' => TRUE, + 'query types' => array('term'), + 'facet missing allowed' => TRUE, + ), + ); + + return $mappings; +} + +/** + * A replacement for l() + * - doesn't add the 'active' class + * - retains all $_GET parameters that ApacheSolr may not be aware of + * - if set, $options['query'] MUST be an array + * + * @see http://api.drupal.org/api/function/l/6 + * for parameters and options. + * + * @return + * an HTML string containing a link to the given path. + */ +function apachesolr_l($text, $path, $options = array()) { + // Merge in defaults. + $options += array( + 'attributes' => array(), + 'html' => FALSE, + 'query' => array(), + ); + + // Don't need this, and just to be safe. + unset($options['attributes']['title']); + + // Retain GET parameters that Apache Solr knows nothing about. + $get = array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1), $options['query']); + $options['query'] += $get; + + return '' . ($options['html'] ? $text : check_plain(html_entity_decode($text))) . ''; +} + +function theme_apachesolr_sort_link($vars) { + $icon = ''; + if ($vars['direction']) { + $icon = ' ' . theme('tablesort_indicator', array('style' => $vars['direction'])); + } + if ($vars['active']) { + if (isset($vars['options']['attributes']['class'])) { + $vars['options']['attributes']['class'] .= ' active'; + } + else { + $vars['options']['attributes']['class'] = 'active'; + } + } + return $icon . apachesolr_l($vars['text'], $vars['path'], $vars['options']); +} + +function theme_apachesolr_sort_list($vars) { + // theme('item_list') expects a numerically indexed array. + $vars['items'] = array_values($vars['items']); + return theme('item_list', array('items' => $vars['items'])); +} + +/** + * Themes the title for settings pages. + */ +function theme_apachesolr_settings_title($vars) { + $output = ''; + + // Gets environment information, builds header with nested link to the environment's + // edit page. Skips building title if environment info could not be retrieved. + if ($environment = apachesolr_environment_load($vars['env_id'])) { + $url = url( + 'admin/config/search/apachesolr/settings/', + array('query' => array('destination' => current_path())) + ); + $output .= '

'; + $output .= t( + 'Settings for: @environment (Overview)', + array('@url' => $url, '@environment' => $environment['name']) + ); + $output .= "

\n"; + } + + return $output; +} + +/** + * Export callback to load the view subrecords, which are the index bundles. + */ +function apachesolr_environment_load_subrecords(&$environments) { + if (empty($environments)) { + // Nothing to do. + return NULL; + } + + $all_index_bundles = db_select('apachesolr_index_bundles', 'ib') + ->fields('ib', array('env_id', 'entity_type', 'bundle')) + ->condition('env_id', array_keys($environments), 'IN') + ->orderBy('env_id') + ->orderBy('entity_type') + ->orderBy('bundle') + ->execute() + ->fetchAll(PDO::FETCH_ASSOC); + + $all_index_bundles_keyed = array(); + foreach ($all_index_bundles as $env_info) { + extract($env_info); + $all_index_bundles_keyed[$env_id][$entity_type][] = $bundle; + } + + $all_variables = db_select('apachesolr_environment_variable', 'v') + ->fields('v', array('env_id', 'name', 'value')) + ->condition('env_id', array_keys($environments), 'IN') + ->orderBy('env_id') + ->orderBy('name') + ->orderBy('value') + ->execute() + ->fetchAll(PDO::FETCH_ASSOC); + + $variables = array(); + foreach ($all_variables as $variable) { + extract($variable); + $variables[$env_id][$name] = unserialize($value); + } + + foreach ($environments as $env_id => &$environment) { + $index_bundles = !empty($all_index_bundles_keyed[$env_id]) ? $all_index_bundles_keyed[$env_id] : array(); + $conf = !empty($variables[$env_id]) ? $variables[$env_id] : array(); + if (is_array($environment)) { + // Environment is an array. + // If we have different values in the database compared with what we + // have in the given environment argument we allow the admin to revert + // the db values so we can stick with a consistent system + if (!empty($environment['index_bundles']) && !empty($index_bundles) && $environment['index_bundles'] !== $index_bundles) { + unset($environment['in_code_only']); + $environment['type'] = 'Overridden'; + } + if (!empty($environment['conf']) && !empty($conf) && $environment['conf'] !== $conf) { + unset($environment['in_code_only']); + $environment['type'] = 'Overridden'; + } + $environment['index_bundles'] = (empty($environment['index_bundles']) || !empty($index_bundles)) ? $index_bundles : $environment['index_bundles']; + $environment['conf'] = (empty($environment['conf']) || !empty($conf)) ? $conf : $environment['conf']; + } + elseif (is_object($environment)) { + // Environment is an object. + if ($environment->index_bundles !== $index_bundles && !empty($index_bundles)) { + unset($environment->in_code_only); + $environment->type = 'Overridden'; + } + if ($environment->conf !== $conf && !empty($conf)) { + unset($environment->in_code_only); + $environment->type = 'Overridden'; + } + $environment->index_bundles = (empty($environment->index_bundles) || !empty($index_bundles)) ? $index_bundles : $environment->index_bundles; + $environment->conf = (empty($environment->conf) || !empty($conf)) ? $conf : $environment->conf; + } + } +} + +/** + * Callback for saving Apache Solr environment CTools exportables. + * + * CTools uses objects, while Apache Solr uses arrays; turn CTools value into an + * array, then call the normal save function. + * + * @param stdclass $environment + * An environment object. + */ +function apachesolr_ctools_environment_save($environment) { + apachesolr_environment_save((array) $environment); +} + +/** + * Callback for reverting Apache Solr environment CTools exportables. + * + * @param mixed $env_id + * An environment machine name. CTools may provide an id OR a complete + * environment object; Since Apache Solr loads environments as arrays, this + * may also be an environment array. + */ +function apachesolr_ctools_environment_delete($env_id) { + if (is_object($env_id) || is_array($env_id)) { + $env_id = (object) $env_id; + $env_id = $env_id->env_id; + } + apachesolr_environment_delete($env_id); +} + +/** + * Callback for exporting Apache Solr environments as CTools exportables. + * + * @param array $environment + * An environment array from Apache Solr. + * @param string $indent + * White space for indentation from CTools. + */ +function apachesolr_ctools_environment_export($environment, $indent) { + ctools_include('export'); + $environment = (object) $environment; + // Re-load the enviroment, since in some cases the conf + // is stripped since it's not in the actual schema. + $environment = (object) apachesolr_environment_load($environment->env_id); + + $index_bundles = array(); + foreach (entity_get_info() as $type => $info) { + if ($bundles = apachesolr_get_index_bundles($environment->env_id, $type)) { + $index_bundles[$type] = $bundles; + } + } + $additions_top = array(); + $additions_bottom = array('conf' => $environment->conf, 'index_bundles' => $index_bundles); + return ctools_export_object('apachesolr_environment', $environment, $indent, NULL, $additions_top, $additions_bottom); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_access/apachesolr_access.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_access/apachesolr_access.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,15 @@ +name = Apache Solr Access +description = Integrates node access and other permissions with Apache Solr search +dependencies[] = apachesolr +package = Search Toolkit +core = 7.x + +files[] = apachesolr_access.module +files[] = tests/apachesolr_access.test + +; Information added by drupal.org packaging script on 2013-03-15 +version = "7.x-1.1+34-dev" +core = "7.x" +project = "apachesolr" +datestamp = "1363307665" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_access/apachesolr_access.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_access/apachesolr_access.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,218 @@ + $node->nid)); + foreach ($result as $grant) { + $grant_realm = apachesolr_access_clean_realm_name($grant->realm); + $key = 'access_node_' . apachesolr_site_hash() . '_' . $grant_realm; + $document->addField($key, $grant->gid); + } + } + else { + // Add the generic view grant if we are not using + // node access or the node is viewable by anonymous users. + // We assume we'll never have an entity with the name '__all'. + $document->addField('access__all', 0); + } +} + +/** + * Creates a Solr query for a given user + * + * @param $account + * an account to get grants for and build a solr query + * + * @throws Exception + * + * @return SolrFilterSubQuery + * Instance of SolrFilterSubQuery + */ +function apachesolr_access_build_subquery($account) { + if (!user_access('access content', $account)) { + throw new Exception('No access'); + } + $node_access_query = apachesolr_drupal_subquery(); + if (user_access('bypass node access', $account)) { + // Access all content from the current site. + $node_access_query->addFilter('hash', apachesolr_site_hash()); + } + else { + // Get node access grants. + $grants = node_access_grants('view', $account); + foreach ($grants as $realm => $gids) { + $realm = apachesolr_access_clean_realm_name($realm); + foreach ($gids as $gid) { + $node_access_query->addFilter('access_node_' . apachesolr_site_hash() . '_' . $realm, $gid); + } + } + } + // Everyone can access public content. Note that if the variable + // 'apachesolr_access_always_add_grants' is TRUE, no content from this site + // is considered "public". However, this condition may match documents in + // the Solr index supplied by other sites when multiple sites are indexing + // into the same index , i.e. multisite search. + $node_access_query->addFilter('access__all', 0); + return $node_access_query; +} + +/** + * Implements hook_apachesolr_query_alter(). + * + * Alter the query to include the access subquery + * + * @param DrupalSolrQueryInterface $query + * + */ +function apachesolr_access_apachesolr_query_alter(DrupalSolrQueryInterface $query) { + global $user; + try { + $subquery = apachesolr_access_build_subquery($user); + $query->addFilterSubQuery($subquery); + } + catch (Exception $e) { + watchdog("apachesolr_access", 'User %name (UID:!uid) cannot search: @message', array('%name' => $user->name, '!uid' => $user->uid, '@message' => $e->getMessage())); + $query->abort_search = TRUE; + } +} + +/** + * Implements hook_node_insert(). + * + * hook_node_ACTION() is called before hook_node_access_records() in node_save(). + * + * @param object $node + */ +function apachesolr_access_node_insert($node) { + $node->apachesolr_access_node_ignore = 1; +} + +/** + * Implements hook_node_update(). + * + * hook_node_ACTION() is called before hook_node_access_records() in node_save(). + * + * @param object $node + */ +function apachesolr_access_node_update($node) { + $node->apachesolr_access_node_ignore = 1; +} + +/** + * Implements hook_node_access_records(). + * + * Listen to this hook to find out when a node needs to be re-indexed + * for its node access grants. + * + * @param object $node + */ +function apachesolr_access_node_access_records($node) { + // node_access_needs_rebuild() will usually be TRUE during a + // full rebuild. + if (empty($node->apachesolr_access_node_ignore) && !node_access_needs_rebuild()) { + // Only one node is being changed - mark for re-indexing. + apachesolr_mark_entity('node', $node->nid); + } +} + +/** + * Implements hook_form_alter(). + * + * @param array $form + * @param array $form_state + * @param string $form_id + * + */ +function apachesolr_access_form_alter(&$form, $form_state, $form_id) { + $form['#submit'][] = 'apachesolr_access_rebuild_nodeaccess'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function apachesolr_access_form_apachesolr_environment_edit_form_alter(&$form, $form_state) { + $form['conf']['apachesolr_access_always_add_grants'] = array( + '#type' => 'checkbox', + '#title' => t('Add access grants even for public content'), + '#default_value' => empty($form['#environment']['conf']['apachesolr_access_always_add_grants']) ? 0 : 1, + '#description' => t('Normally should be disabled. Changing this value requires all content to be re-indexed. Useful for sites using Domamin Access or simliar node acess modules with grants that vary for anonymous users.'), + ); + $form['actions']['save']['#submit'][] = 'apachesolr_access_environment_edit_form_submit'; + $form['actions']['save_edit']['#submit'][] = 'apachesolr_access_environment_edit_form_submit'; +} + +/** + * Added button-level form submit function for apachesolr_environment_edit_form. + */ +function apachesolr_access_environment_edit_form_submit($form, &$form_state) { + $prior = empty($form['#environment']['conf']['apachesolr_access_always_add_grants']) ? 0 : 1; + if ($form_state['values']['conf']['apachesolr_access_always_add_grants'] != $prior) { + apachesolr_access_enable(); + } +} + +/** + * Force Solr to do a total re-index when node access rules change. + * + * This is unfortunate because not every node is going to be affected, but + * there is little we can do. + * + * @param $form + * @param $form_state + * + */ +function apachesolr_access_rebuild_nodeaccess($form, $form_state) { + drupal_set_message(t('Solr search index will be rebuilt.')); + // Clear last updated + apachesolr_clear_last_index_position(); +} + +/** + * Implements hook_enable(). + * + * On enabling the module, tell the user to reindex + */ +function apachesolr_access_enable() { + drupal_set_message(t('Your content must be re-indexed before Apache Solr Access will be functional on searches.', array('@url' => url('admin/config/search/apachesolr/index'))), 'warning'); +} + +/** + * Helper function - return a safe (PHP identifier) realm name. + * + * @todo See if we can replace this with a native php function + * + * @param string $realm + * + * @return string + * Clean string without bad characters + */ +function apachesolr_access_clean_realm_name($realm) { + return preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $realm); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_access/tests/apachesolr_access.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_access/tests/apachesolr_access.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,157 @@ + 'Node Access', + 'description' => 'Test Access Control', + 'group' => 'ApacheSolr' + ); + } + + /** + * Defines what is required to start the DrupalApacheSolrNodeAccess test. + */ + function setUp() { + parent::setUp('node_access_test', 'apachesolr', 'apachesolr_search', 'apachesolr_access'); + + // Create a basic user, which is subject to moderation. + $permissions = array( + 'access content', + 'create page content', + 'edit own page content', + 'create article content', + 'edit own article content', + ); + $this->basic_user = $this->drupalCreateUser($permissions); + // Create an admin user. + $permissions = array( + 'access content', + 'search content', + 'administer nodes', + 'administer search', + 'access administration pages', + ); + $this->admin_user = $this->drupalCreateUser($permissions); + } + + /** + * Tests indexing and check if it adds the correct grants for those specific users + */ + function testIndexing() { + $basic_user = $this->basic_user; + // Login as basic user to perform initial content creation. + + //Create 2 nodes + $edit = array(); + $edit['uid'] = $basic_user->uid; + $role_restricted_node = $this->drupalCreateNode($edit); + + $edit = array(); + $edit['uid'] = $basic_user->uid; + $author_restricted_node = $this->drupalCreateNode($edit); + // Delete the generic node access grant for all nodes. + db_delete('node_access')->condition('nid', '0')->execute(); + + $roles = array_keys($basic_user->roles); + // The assigned role will be the last in the array. + $assigned_role = end($roles); + $role_grant = array( + 'gid' => $assigned_role, + 'realm' => 'nodeaccess_rid', + 'grant_view' => '1', + 'grant_update' => '0', + 'grant_delete' => '0', + ); + node_access_write_grants($role_restricted_node, array($role_grant)); + + $author_grant = array( + 'gid' => $basic_user->uid, + 'realm' => 'nodeaccess_author', + 'grant_view' => '1', + 'grant_update' => '0', + 'grant_delete' => '0', + ); + + node_access_write_grants($author_restricted_node, array($author_grant)); + + // This loads the document class too. + $env_id = apachesolr_default_environment(); + $solr = apachesolr_get_solr($env_id); + + $document = new ApacheSolrDocument(); + apachesolr_access_apachesolr_index_document_build_node($document, $role_restricted_node, $env_id); + $field = 'access_node_' . apachesolr_site_hash() . '_nodeaccess_rid'; + $this->assertEqual($document->{$field}[0], $assigned_role, 'Solr Document being indexed is restricted by the proper role' . print_r(db_query('SELECT * FROM {node_access}')->fetchAllAssoc('nid'), 1)); + $this->drupalGet('node'); + + $document = new ApacheSolrDocument(); + apachesolr_access_apachesolr_index_document_build_node($document, $author_restricted_node, $env_id); + $field = 'access_node_' . apachesolr_site_hash() . '_nodeaccess_author'; + $this->assertEqual($document->{$field}[0], $basic_user->uid, 'Solr Document being indexed is restricted by the proper author'); + + $expected_criterion = array( + 'access__all' => 0, + 'access_node_' . apachesolr_site_hash() . '_all' => 0, + // The node_access_test module writes this as of core 7.3. + 'access_node_' . apachesolr_site_hash() . '_node_access_test_author' => $basic_user->uid, + ); + + // Test addition of filters to query. + $subquery = apachesolr_access_build_subquery($basic_user); + $fields = $subquery->getFilters(); + + foreach ($fields as $field) { + if (is_array($expected_criterion[$field['#name']])) { + $this->assertTrue(in_array($field['#value'], $expected_criterion[$field['#name']]), t('Expected node access grant @name == @value found', array('@name' => $field['#name'], '@value' => $field['#value']))); + //This is sorta a bug + $found_criterion[$field['#name']] = $expected_criterion[$field['#name']]; + } + else { + $this->assertEqual($field['#value'], $expected_criterion[$field['#name']], t('Expected node access grant @name == @value found', array('@name' => $field['#name'], '@value' => $field['#value']))); + $found_criterion[$field['#name']] = $expected_criterion[$field['#name']]; + } + } + + $this->assertEqual($expected_criterion, $found_criterion, 'All Criteria was accounted for in fields. If not accounted for, Unaccounted Criteria [' . var_export(array_diff($expected_criterion, $found_criterion), 1) . ']'); + // Run a query through the MLT code to be sure access filters are added. + $solr = new DummySolr($url = NULL, $env_id); + $settings = apachesolr_search_mlt_block_defaults(); + // Dummy value + $id = apachesolr_document_id($author_restricted_node->nid); + drupal_save_session(false); + $GLOBALS['user'] = $basic_user; + $response = apachesolr_search_mlt_suggestions($settings, $id, $solr); + $search = $solr->getLastSearch(); + // Should only be one fq + $this->assertEqual(count($search['params']['fq']), 1, 'One fq param found'); + // Do some manipulation to avoid having to guess the order. + $filter = trim(end($search['params']['fq']), ')('); + $parts = explode(' OR ', $filter); + $this->assertEqual(count($expected_criterion), count($parts), 'Number of parts is the same as the number of critera'); + foreach ($expected_criterion as $k => $v) { + $this->assertTrue(in_array("$k:$v", $parts), "Filter $k:$v found in the parts"); + } + // Test reset of index position. + $this->drupalLogin($this->admin_user); + $env_id = apachesolr_default_environment(); + apachesolr_set_last_index_position($env_id, 'node', 1, 1); + $empty = serialize(array()); + $value = db_query('SELECT value FROM {variable} WHERE name = :name', array(':name' => 'apachesolr_index_last'))->fetchField(); + $this->assertNotEqual($value, $empty, 'value is not empty array'); + $this->drupalPost('admin/reports/status/rebuild', array(), t('Rebuild permissions')); + $value = db_query('SELECT value FROM {variable} WHERE name = :name', array(':name' => 'apachesolr_index_last'))->fetchField(); + $this->assertEqual($value, $empty, 'value is empty array'); + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_search.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_search.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1196 @@ +Facets will not be shown until you enable Facet API module.'); + } + else { + $description .= t('Remember to configure the facets on the search environment page and assign blocks to regions on the block settings page', array( + '!facetslink' => url('admin/config/search/apachesolr/settings/'), + '!blocklink' => url('admin/structure/block'), + )); + } + return array( + '#type' => 'radios', + '#title' => t('Behavior on empty search'), + '#options' => array( + 'none' => t("Show search box"), + 'browse' => t("Show enabled facets' blocks under the search box"), + 'blocks' => t("Show enabled facets' blocks in their configured regions"), + 'results' => t("Show enabled facets' blocks in their configured regions and first page of all available results"), + ), + '#default_value' => $default_value, + '#description' => $description, + ); +} + +/** + * Menu callback for the overview page showing custom search pages and blocks. + * @return array $build + */ +function apachesolr_search_page_list_all() { + $build['pages'] = apachesolr_search_page_list_pages(); + $build['blocks'] = apachesolr_search_page_list_blocks(); + return $build; + +} + +/** + * Listing of all the search pages + * @return array $build + */ +function apachesolr_search_page_list_pages() { + $build = array(); + $rows = array(); + $rows['core_search'] = array(); + + // Build the sortable table header. + $header = array( + 'label' => array('data' => t('Name'), 'field' => 's.label'), + 'path' => array('data' => t('Path'), 'field' => 's.search_path'), + 'environment' => array('data' => t('Search environment')), + 'operations' => array('data' => t('Operations')), + ); + + $search_pages = apachesolr_search_load_all_search_pages(); + $default_search_page = apachesolr_search_default_search_page(); + foreach ($search_pages as $search_page) { + $row = array(); + + // Add the label + $label = check_plain($search_page['label']); + // Is this row our default environment? + if ($search_page['page_id'] == $default_search_page) { + $label = t('!search_page (Default)', array('!search_page' => $label)); + } + + $row[] = $label; + // Add the link + $row[] = array( + 'data' => array( + '#type' => 'link', + '#title' => $search_page['search_path'], + '#href' => $search_page['search_path'], + ), + ); + + // Add the search environment + $environment = apachesolr_environment_load($search_page['env_id']); + $row[] = $environment ? check_plain($environment['name']) : check_plain(t('')); + // Operations + $row[] = array('data' => l(t('Edit'), 'admin/config/search/apachesolr/search-pages/' . $search_page['page_id'] . '/edit')); + $row[] = array('data' => l(t('Clone'), 'admin/config/search/apachesolr/search-pages/' . $search_page['page_id'] . '/clone')); + + // Allow to revert a search page or to delete it + if (!isset($search_page['settings']['apachesolr_search_not_removable']) && !isset($search_page['in_code_only'])) { + if ((isset($search_page['type']) && $search_page['type'] == 'Overridden')) { + $row[] = array('data' => l(t('Revert'), 'admin/config/search/apachesolr/search-pages/' . $search_page['page_id'] . '/delete')); + } else { + $row[] = array('data' => l(t('Delete'), 'admin/config/search/apachesolr/search-pages/' . $search_page['page_id'] . '/delete')); + } + } + else { + $row[] = ''; + } + $rows[$search_page['page_id']] = $row; + } + + // Automatically enlarge our header with the operations size + $header['operations']['colspan'] = count(reset($rows)) - 3; + + $build['list'] = array( + '#prefix' => '

Pages

', + '#theme' => 'table', + '#header' => $header, + '#rows' => array_values($rows), + '#empty' => t('No available search pages.'), + ); + $build['pager'] = array( + '#theme' => 'pager', + '#quantity' => 20, + '#weight' => 10, + ); + + return $build; +} + +/** + * Listing of all the search blocks + * @return array $build + */ +function apachesolr_search_page_list_blocks() { + $build = array(); + $rows = array(); + + // Build the sortable table header. + $header = array( + 'label' => array('data' => t('Name'), 'field' => 's.label'), + 'environment' => array('data' => t('Search environment')), + 'operations' => array('data' => t('Operations')), + ); + + $search_blocks = variable_get('apachesolr_search_mlt_blocks', array()); + foreach ($search_blocks as $search_block_id => $search_block) { + $row = array(); + + // Add the label + $label = check_plain($search_block['name']); + $row[] = $label; + + // Add the search environment + $environment = apachesolr_environment_load($search_block['mlt_env_id']); + $row[] = $environment ? check_plain($environment['name']) : check_plain(t('')); + // Operations + if (module_exists('block')) { + $row[] = array('data' => l(t('Configure'), 'admin/structure/block/manage/apachesolr_search/' . $search_block_id . '/configure', array('query' => array('destination' => current_path())))); + } + $row[] = array('data' => l(t('Delete'), 'admin/config/search/apachesolr/search-pages/block/' . $search_block_id . '/delete')); + $rows[$search_block_id] = $row; + } + + // Automatically enlarge our header with the operations size + $header['operations']['colspan'] = count(reset($rows)) - 2; + + $build['list'] = array( + '#prefix' => '

Blocks "More Like This"

', + '#theme' => 'table', + '#header' => $header, + '#rows' => array_values($rows), + '#empty' => t('No available search blocks.'), + ); + $build['pager'] = array( + '#theme' => 'pager', + '#quantity' => 20, + '#weight' => 10, + ); + + return $build; +} + +/** + * Menu callback/form-builder for the form to create or edit a search page. + */ +function apachesolr_search_page_settings_form($form, &$form_state, $search_page = NULL) { + $environments = apachesolr_load_all_environments(); + $options = array('' => t('')); + foreach ($environments as $id => $environment) { + $options[$id] = $environment['name']; + } + // Validate the env_id. + if (!empty($search_page['env_id']) && !apachesolr_environment_load($search_page['env_id'])) { + $search_page['env_id'] = ''; + } + + // Initializes form with common settings. + $form['search_page'] = array( + '#type' => 'value', + '#value' => $search_page, + ); + + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#description' => '', + '#required' => TRUE, + '#size' => 30, + '#maxlength' => 32, + '#default_value' => !empty($search_page['label']) ? $search_page['label'] : '', + '#description' => t('The human-readable name of the search page configuration.'), + ); + + $form['page_id'] = array( + '#type' => 'machine_name', + '#maxlength' => 32, + '#required' => TRUE, + '#machine_name' => array( + 'exists' => 'apachesolr_search_page_exists', + 'source' => array('label'), + ), + '#description' => '', + '#default_value' => !empty($search_page['page_id']) ? $search_page['page_id'] : '', + '#disabled' => !empty($search_page), + '#description' => t('A unique machine-readable identifier for the search page configuration. It must only contain lowercase letters, numbers, and underscores.'), + ); + + $form['description_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Description'), + '#default_value' => !empty($search_page['description']) ? TRUE : FALSE + ); + + $form['description'] = array( + '#type' => 'textfield', + '#title' => t('Provide description'), + '#title_display' => 'invisible', + '#size' => 64, + '#default_value' => !empty($search_page['description']) ? $search_page['description'] : '', + '#dependency' => array( + 'edit-description-enable' => array(1), + ), + ); + + $is_default = FALSE; + if (!empty($search_page)) { + $is_default = $search_page['page_id'] == apachesolr_search_default_search_page(); + } + $form['make_default'] = array( + '#type' => 'checkbox', + '#title' => t('Make this Solr Search Page the default'), + '#description' => t('Useful for eg. making facets to link to this page when they are shown on non-search pages'), + '#default_value' => $is_default, + '#disabled' => $is_default, + ); + + $form['info'] = array( + '#title' => t('Search Page Information'), + '#type' => 'fieldset', + '#collapsible' => FALSE, + '#prefix' => '
', + '#suffix' => '
', + ); + + $core_search = FALSE; + if (!empty($search_page['page_id']) && ($search_page['page_id'] == 'core_search')) { + $core_search = TRUE; + } + if ($core_search) { + $description = t('This page always uses the current default search environment'); + } + else { + $description = t('The environment that is used by this search page. If no environment is selected, this page will be disabled.'); + } + + $form['info']['env_id'] = array( + '#title' => t('Search environment'), + '#type' => 'select', + '#options' => $options, + '#default_value' => !empty($search_page['env_id']) ? $search_page['env_id'] : '', + '#disabled' => $core_search, + '#description' => $description, + ); + + $form['info']['page_title'] = array( + '#title' => t('Title'), + '#type' => 'textfield', + '#required' => TRUE, + '#maxlength' => 255, + '#description' => 'You can use %value to place the search term in the title', + '#default_value' => !empty($search_page['page_title']) ? $search_page['page_title'] : '', + ); + + $search_types = apachesolr_search_load_all_search_types(); + $options = array('custom' => t('Custom Field')); + foreach ($search_types as $id => $search_type) { + $options[$id] = $search_type['name']; + } + + $form['info']['search_type'] = array( + '#title' => t('Search Type'), + '#type' => 'select', + '#options' => $options, + '#default_value' => !empty($search_page['settings']['apachesolr_search_search_type']) ? $search_page['settings']['apachesolr_search_search_type'] : '', + '#access' => !$core_search, + '#description' => t('Use this only when filtering on a value from the search path. + For example, select Taxonomy Term to filter on a term ID (search/taxonomy/%).'), + '#ajax' => array( + 'callback' => 'apachesolr_search_ajax_search_page_default', + 'wrapper' => 'dynamic-search-page', + 'method' => 'replace', + ), + ); + + // Token element validate is added to validate the specific + // tokens that are allowed + $form['info']['search_path'] = array( + '#title' => t('Path'), + '#type' => 'textfield', + '#required' => TRUE, + '#maxlength' => 255, + '#description' => t('For example: search/my-search-page. Search keywords will appear at the end of the path.'), + '#default_value' => !empty($search_page['search_path']) ? $search_page['search_path'] : '', + ); + if (!$core_search) { + $form['info']['search_path']['#description'] .= ' ' . t('You can use one % to make the search page dynamic.'); + } + + $form['info']['custom_filter_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Custom Filter'), + '#default_value' => !empty($search_page['settings']['apachesolr_search_custom_enable']) ? TRUE : FALSE + ); + + $form['info']['filters'] = array( + '#title' => t('Custom filters'), + '#type' => 'textfield', + '#required' => FALSE, + '#maxlength' => 255, + '#description' => t('A comma-separated list of lucene filter queries to apply by default.'), + '#default_value' => !empty($search_page['settings']['fq']) ? implode(', ', $search_page['settings']['fq']) : '', + '#dependency' => array( + 'edit-custom-filter-enable' => array(1), + 'edit-search-type' => array('custom'), + ), + ); + if (!$core_search) { + $form['info']['filters']['#description'] .= ' ' . t('E.g. "bundle:blog, is_uid:(1 OR 2 OR %). % will be replaced by the value of % in the path"'); + } + + $form['advanced'] = array( + '#title' => t('Advanced Search Page Options'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + ); + + // Results per page per search page + $default_value = isset($search_page['settings']['apachesolr_search_per_page']) ? $search_page['settings']['apachesolr_search_per_page'] : '10'; + $form['advanced']['apachesolr_search_per_page'] = array( + '#type' => 'textfield', + '#size' => 3, + '#title' => t('Results per page'), + '#description' => t('How many items will be displayed on one page of the search result.'), + '#default_value' => $default_value, + ); + + // Enable/disable spellcheck on pages + $default_value = isset($search_page['settings']['apachesolr_search_spellcheck']) ? $search_page['settings']['apachesolr_search_spellcheck'] : TRUE; + $form['advanced']['apachesolr_search_spellcheck'] = array( + '#type' => 'checkbox', + '#title' => t('Enable spell check'), + '#description' => t('Display "Did you mean … ?" above search results.'), + '#default_value' => $default_value, + ); + + // Enable/disable search form on search page (replaced by a block perhaps) + $default_value = isset($search_page['settings']['apachesolr_search_search_box']) ? $search_page['settings']['apachesolr_search_search_box'] : TRUE; + $form['advanced']['apachesolr_search_search_box'] = array( + '#type' => 'checkbox', + '#title' => t('Enable the search box on the page'), + '#description' => t('Display a search box on the page.'), + '#access' => !$core_search, + '#default_value' => $default_value, + ); + + // Enable/disable search form on search page (replaced by a block perhaps) + $default_value = isset($search_page['settings']['apachesolr_search_allow_user_input']) ? $search_page['settings']['apachesolr_search_allow_user_input'] : FALSE; + $form['advanced']['apachesolr_search_allow_user_input'] = array( + '#type' => 'checkbox', + '#title' => t('Allow user input using the URL'), + '#description' => t('Allow users to use the URL for manual facetting via fq[] params (e.g. http://example.com/search/site/test?fq[]=uid:1&fq[]=tid:99). This will only work in combination with a keyword search. The recommended value is unchecked'), + '#default_value' => $default_value, + ); + + // Use the main search page setting as the default for new pages. + $default_value = isset($search_page['settings']['apachesolr_search_browse']) ? $search_page['settings']['apachesolr_search_browse'] : 'browse'; + $form['advanced']['apachesolr_search_browse'] = _apachesolr_search_browse_form($default_value); + + // Button for the corresponding actions + $form['actions'] = array( + '#type' => 'actions', + ); + + $form['actions']['submit'] = array( + '#type' => 'submit', + '#redirect' => 'admin/config/search/apachesolr/search-pages', + '#value' => t('Save'), + ); + $form['actions']['submit_edit'] = array( + '#type' => 'submit', + '#value' => t('Save and edit'), + ); + + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => 'admin/config/search/apachesolr/search-pages', + ); + + $form['#submit'][] = 'apachesolr_search_page_settings_form_submit'; + + return $form; +} + +/** + * Callback element needs only select the portion of the form to be updated. + * Since #ajax['callback'] return can be HTML or a renderable array (or an + * array of commands), we can just return a piece of the form. + */ +function apachesolr_search_ajax_search_page_default($form, $form_state, $search_page = NULL) { + + $search_page = $form_state['values']['search_page']; + $search_types = apachesolr_search_load_all_search_types(); + + // Helping with sensible defaults for the search path + $default_search_path = ''; + if (!empty($form_state['values']['search_type']) && $form_state['values']['search_type'] != 'custom') { + $default_search_path = $search_types[$form_state['values']['search_type']]['default menu']; + $form['info']['search_path']['#value'] = $default_search_path; + } + + // Helping with sensible defaults for the search title + $default_search_title = ''; + + if (empty($form_state['values']['page_title']) && $form_state['values']['search_type'] != 'custom') { + $default_search_title_callback = $search_types[$form_state['values']['search_type']]['title callback']; + $default_search_title = $default_search_title_callback(); + $form['info']['page_title']['#value'] = $default_search_title; + } + return $form['info']; +} + +function apachesolr_search_page_settings_form_validate($form, &$form_state) { + // Performs basic validation of the menu path. + if (url_is_external($form_state['values']['search_path'])) { + form_set_error('search_path', t('Path must be local.')); + } + $form_state['values']['search_path'] = trim($form_state['values']['search_path'], '/'); + if (empty($form_state['values']['search_path'])) { + form_set_error('search_path', t('Path required.')); + } + if (!is_numeric($form_state['values']['advanced']['apachesolr_search_per_page'])) { + form_set_error('advanced][apachesolr_search_per_page', t('The amount of search results must be an integer.')); + } + $form_state['values']['advanced']['apachesolr_search_per_page'] = (int) $form_state['values']['advanced']['apachesolr_search_per_page']; + if (empty($form_state['values']['advanced']['apachesolr_search_per_page'])) { + form_set_error('advanced][apachesolr_search_per_page', t('The amount of search results cannot be empty.')); + } + if ($form_state['values']['page_id'] == 'core_search') { + if (!preg_match('@^search/[^/%]+$@', $form_state['values']['search_path'])) { + form_set_error('search_path', t('The core Search page path must start with search/ and only have one /')); + } + } + elseif (count(explode('%', $form_state['values']['search_path'])) > 2) { + form_set_error('search_path', t('Only one % placeholder is allowed.')); + } +} + +/** + * Processes apachesolr_search_page_settings_form form submissions. + */ +function apachesolr_search_page_settings_form_submit($form, &$form_state) { + $settings = array(); + $settings['fq'] = array(); + if ($form_state['values']['filters']) { + foreach (explode(',', $form_state['values']['filters']) as $string) { + $string = trim($string); + // Minimal validation. ':' must exist and can't be the 1st char.. + if (strpos($string, ':')) { + $settings['fq'][] = $string; + } + } + } + $settings['apachesolr_search_custom_enable'] = $form_state['values']['custom_filter_enable']; + $settings['apachesolr_search_search_type'] = $form_state['values']['search_type']; + // Add all advanced settings. + $settings += $form_state['values']['advanced']; + + // Set the default search page settings + if (!empty($form_state['values']['make_default']) && isset($form_state['values']['page_id'])) { + variable_set('apachesolr_search_default_search_page', $form_state['values']['page_id']); + } + + $search_page = array(); + $search_page['page_id'] = $form_state['values']['page_id']; + $search_page['label'] = $form_state['values']['label']; + $search_page['description'] = $form_state['values']['description']; + $search_page['env_id'] = $form_state['values']['env_id']; + $search_page['search_path'] = $form_state['values']['search_path']; + $search_page['page_title'] = $form_state['values']['page_title']; + $search_page['settings'] = $settings; + apachesolr_search_page_save($search_page); + + // Saves our values in the database, sets redirect path on success. + drupal_set_message(t('The configuration options have been saved for %page.', array('%page' => $form_state['values']['label']))); + if (isset($form_state['clicked_button']['#redirect'])) { + $form_state['redirect'] = $form_state['clicked_button']['#redirect']; + } + else { + $form_state['redirect'] = current_path(); + } + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); + // Menu rebuild needed to pick up search path. + menu_rebuild(); +} + +/** + * Deletes a single search page configuration. + */ +function apachesolr_search_delete_search_page_confirm($form, &$form_state, $search_page) { + + // Sets values required for deletion. + $form['page_id'] = array('#type' => 'value', '#value' => $search_page['page_id']); + $form['label'] = array('#type' => 'value', '#value' => $search_page['label']); + + if (isset($search_page['export_type']) && $search_page['export_type'] == '3') { + $verb = t('Revert'); + } + else { + $verb = t('Delete'); + } + + // Sets the message, or the title of the page. + $message = t( + 'Are you sure you want to !verb the %label search page configuration?', + array('%label' => $form['label']['#value'], '!verb' => strtolower($verb)) + ); + + + // Builds caption. + $caption = '

'; + $caption .= t( + 'The %label search page configuration will be deleted.', + array('%label' => $form['label']['#value']) + ); + $caption .= '

'; + $caption .= '

' . t('This action cannot be undone.') . '

'; + + // Finalizes and returns the confirmation form. + $return_path = 'admin/config/search/apachesolr/search-pages'; + $button_text = $verb; + if (!isset($search_page['settings']['apachesolr_search_not_removable'])) { + return confirm_form($form, filter_xss($message), $return_path, filter_xss($caption), check_plain($button_text)); + } + else { + // Maybe this should be solved somehow else + drupal_access_denied(); + } +} + +/** + * Process content type delete confirm submissions. + */ +function apachesolr_search_delete_search_page_confirm_submit($form, &$form_state) { + // Deletes the index configuration settings. + // @todo Invoke a hook that allows backends and indexers to delete their stuff. + db_delete('apachesolr_search_page') + ->condition('page_id', $form_state['values']['page_id']) + ->execute(); + + // Sets message, logs action. + drupal_set_message(t( + 'The %label search page configuration has been deleted.', + array('%label' => $form_state['values']['label']) + )); + watchdog('apachesolr_search', 'Deleted search page configuration "@page_id".', array('@page_id' => $form_state['values']['page_id']), WATCHDOG_NOTICE); + + // Rebuilds the menu cache. + menu_rebuild(); + + // Returns back to search page list page. + $form_state['redirect'] = 'admin/config/search/apachesolr/search-pages'; +} + +/** + * Clones a single search page configuration + * @param $search_page + * The search page that needs to be cloned + */ +function apachesolr_search_clone_search_page_confirm($form, &$form_state, $search_page) { + $form['page_id'] = array( + '#type' => 'value', + '#value' => $search_page['page_id'], + ); + return confirm_form( + $form, + t('Are you sure you want to clone search page %name?', array('%name' => $search_page['label'])), + 'admin/config/search/apachesolr', + '', + t('Clone'), + t('Cancel') + ); +} + +/** + * Submits the confirmations of the cloning of a search page + */ +function apachesolr_search_clone_search_page_confirm_submit($form, &$form_state) { + if (apachesolr_search_page_clone($form_state['values']['page_id'])) { + drupal_set_message(t('The search page was cloned')); + } + $form_state['redirect'] = 'admin/config/search/apachesolr/search-pages'; +} + +/** + * Menu callback - the settings form. + */ +function apachesolr_search_get_fields($environment = NULL) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Try to fetch the schema fields. + try { + $solr = apachesolr_get_solr($env_id); + $fields = $solr->getFields(); + return $fields; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), 'warning'); + drupal_set_message(t('Cannot get information about the fields in the index.'), 'warning'); + } +} + +/** + * Menu callback - Bias settings form. + */ +function apachesolr_bias_settings_page($environment = NULL) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Initializes output with information about which environment's setting we are + // editing, as it is otherwise not transparent to the end user. + $output = array( + 'apachesolr_environment' => array( + '#theme' => 'apachesolr_settings_title', + '#env_id' => $env_id, + ), + ); + + // Adds content bias and type boost forms. + $fields = apachesolr_search_get_fields($environment); + $form = array(); + $form = drupal_get_form('apachesolr_search_bias_form', $env_id, $fields); + $output['bias_forms'] = $form; + return $output; +} + +function apachesolr_search_bias_form($form, &$form_state, $env_id, $fields) { + $form['#env_id'] = $env_id; + $form['bias_tabs'] = array( + '#type' => 'vertical_tabs', + ); + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#submit' => array('apachesolr_search_bias_form_submit'), + ); + $form['actions']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset to defaults'), + '#submit' => array('apachesolr_search_bias_form_reset'), + ); + $form += apachesolr_search_result_bias_form($env_id); + $form += apachesolr_search_type_boost_form($env_id); + $form += apachesolr_search_field_bias_form($fields, $env_id); + return $form; +} + +function apachesolr_search_bias_form_submit(&$form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + foreach ($form_state['values'] as $key => $value) { + if (is_array($value) && isset($form_state['values']['array_filter'])) { + $value = array_keys(array_filter($value)); + } + // There is no need to set default variable values. + if (!isset($form[$key]['#default_value']) || $form[$key]['#default_value'] != $value) { + switch ($key) { + case 'apachesolr_search_sticky_boost' : + case 'apachesolr_search_promote_boost' : + case 'apachesolr_search_date_boost' : + case 'apachesolr_search_comment_boost' : + case 'apachesolr_search_changed_boost' : + case 'apachesolr_search_type_boosts' : + case 'field_bias' : + apachesolr_environment_variable_set($form['#env_id'], $key, $value); + } + } + } + drupal_set_message(t('The configuration options have been saved.')); +} + +function apachesolr_search_bias_form_reset($form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + + foreach ($form_state['values'] as $key => $value) { + apachesolr_environment_variable_del($form['#env_id'], $key); + } + drupal_set_message(t('The configuration options have been reset to their default values.')); +} + +/** + * Form builder function to set date, comment, etc biases. + */ +function apachesolr_search_result_bias_form($env_id) { + + $date_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_date_boost', '0:0'); + $comment_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_comment_boost', '0:0'); + $changed_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_changed_boost', '0:0'); + $sticky_boost = apachesolr_environment_variable_get($env_id, 'apachesolr_search_sticky_boost', '0'); + $promote_boost = apachesolr_environment_variable_get($env_id, 'apachesolr_search_promote_boost', '0'); + + $options = array( + '10:2000.0' => '10', + '8:1000.0' => '9', + '8:700.0' => '8', + '8:500.0' => '7', + '4:300.0' => '6', + '4:200.0' => '5', + '4:150.0' => '4', + '2:150.0' => '3', + '2:100.0' => '2', + '1:100.0' => '1', + '0:0' => t('Ignore'), + ); + + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Ignore'); + + $form = array(); + $form['result_bias'] = array( + '#type' => 'fieldset', + '#title' => t('Result biasing'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#description' => t('Give bias to certain properties when ordering the search results. Any value except Ignore will increase the score of the given type in search results. Choose Ignore to ignore any given property.'), + '#group' => 'bias_tabs', + ); + $form['result_bias']['apachesolr_search_sticky_boost'] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => t("Sticky at top of lists"), + '#default_value' => $sticky_boost, + '#description' => t("Select additional bias to give to nodes that are set to be 'Sticky at top of lists'."), + ); + $form['result_bias']['apachesolr_search_promote_boost'] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => t("Promoted to home page"), + '#default_value' => $promote_boost, + '#description' => t("Select additional bias to give to nodes that are set to be 'Promoted to home page'."), + ); + $form['result_bias']['apachesolr_search_date_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More recently created"), + '#default_value' => $date_settings, + '#description' => t('This setting will change the result scoring so that nodes created more recently may appear before those with higher keyword matching.'), + ); + $form['result_bias']['apachesolr_search_comment_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More comments"), + '#default_value' => $comment_settings, + '#description' => t('This setting will change the result scoring so that nodes with more comments may appear before those with higher keyword matching.'), + ); + $form['result_bias']['apachesolr_search_changed_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More recent comments"), + '#default_value' => $changed_settings, + '#description' => t('This setting will change the result scoring so that nodes with the most recent comments (or most recent updates to the node itself) may appear before those with higher keyword matching.'), + ); + return $form; +} + +/** + * Form builder function to set query field weights. + */ +function apachesolr_search_field_bias_form($fields, $env_id) { + $form = array(); + // get the current weights + $defaults = array( + 'content' => '1.0', + 'ts_comments' => '0.5', + 'tos_content_extra' => '0.1', + 'label' => '5.0', + 'tos_name' => '3.0', + 'taxonomy_names' => '2.0', + 'tags_h1' => '5.0', + 'tags_h2_h3' => '3.0', + 'tags_h4_h5_h6' => '2.0', + 'tags_inline' => '1.0', + 'tags_a' => '0', + ); + $qf = apachesolr_environment_variable_get($env_id, 'field_bias', $defaults); + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Omit'); + if (!$qf) { + $qf = $defaults; + } + if ($fields) { + $form['field_bias'] = array( + '#type' => 'fieldset', + '#title' => t('Field biases'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#tree' => TRUE, + '#description' => t('Specify here which fields are more important when searching. Give a field a greater numeric value to make it more important. If you omit a field, it will not be searched.'), + '#group' => 'bias_tabs', + ); + foreach ($fields as $field_name => $field) { + // Only indexed feids are searchable. + if ($field->schema{0} == 'I') { + // By default we only show text fields. Use hook_form_alter to change. + // We use filter_xss to make sure links are allowed + $form['field_bias'][$field_name] = array( + '#access' => ($field->type == 'text' || $field->type == 'text_und'), + '#type' => 'select', + '#options' => $weights, + '#title' => filter_xss(apachesolr_field_name_map($field_name)), + '#default_value' => isset($qf[$field_name]) ? $qf[$field_name] : '0', + ); + } + } + + // Make sure all the default fields are included, even if they have + // no indexed content. + foreach ($defaults as $field_name => $weight) { + $form['field_bias'][$field_name] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => check_plain(apachesolr_field_name_map($field_name)), + '#default_value' => isset($qf[$field_name]) ? $qf[$field_name] : $defaults[$field_name], + ); + } + + ksort($form['field_bias']); + } + return $form; +} + +/** + * Form builder function to set query type weights. + */ +function apachesolr_search_type_boost_form($env_id) { + + $form['type_boost'] = array( + '#type' => 'fieldset', + '#title' => t('Type biasing'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#group' => 'bias_tabs', + ); + $form['type_boost']['apachesolr_search_type_boosts'] = array( + '#type' => 'item', + '#description' => t("Specify here which node types should get a higher relevancy score in searches. Any value except Ignore will increase the score of the given type in search results."), + '#tree' => TRUE, + ); + + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Ignore'); + + // Get the current boost values. + $type_boosts = apachesolr_environment_variable_get($env_id, 'apachesolr_search_type_boosts', array()); + $names = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!empty($entity_info['apachesolr']['indexable'])) { + foreach ($entity_info['bundles'] as $key => $info) { + $names[$key] = $info['label']; + } + } + } + asort($names); + + foreach ($names as $type => $name) { + $form['type_boost']['apachesolr_search_type_boosts'][$type] = array( + '#type' => 'select', + '#title' => t('%type type content bias', array('%type' => $name)), + '#options' => $weights, + '#default_value' => isset($type_boosts[$type]) ? $type_boosts[$type] : 0, + ); + } + + return $form; +} + +/** + * MoreLikeThis administration and utility functions. + */ +function apachesolr_search_mlt_add_block_form() { + $form = apachesolr_search_mlt_block_form(); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => '5', + ); + return $form; +} + +function apachesolr_search_mlt_add_block_form_submit($form, &$form_state) { + apachesolr_search_mlt_save_block($form_state['values']); + $block_message = t('New More like this block created. Configure this block in the Block administration', array('!configure' => url('admin/structure/block'))); + drupal_set_message($block_message); + $form_state['redirect'] = 'admin/config/search/apachesolr/search-pages'; +} + +/** + * Merge supplied settings with the standard defaults.. + */ +function apachesolr_search_mlt_block_defaults($block = array()) { + return $block + array( + 'name' => '', + 'num_results' => '5', + 'mlt_fl' => array( + 'label' => 'label', + 'taxonomy_names' => 'taxonomy_names', + ), + 'mlt_env_id' => 'solr', + 'mlt_mintf' => '1', + 'mlt_mindf' => '1', + 'mlt_minwl' => '3', + 'mlt_maxwl' => '15', + 'mlt_maxqt' => '20', + 'mlt_type_filters' => array(), + 'mlt_custom_filters' => '', + ); +} + +/** + * Constructs a list of field names used on the settings form. + * + * @return array An array containing a the fields in the solr instance. + */ +function apachesolr_search_mlt_get_fields() { + $rows = array(); + + try { + $solr = apachesolr_get_solr(); + $fields = $solr->getFields(); + foreach ($fields as $field_name => $field) { + if ($field->schema{4} == 'V') { + $rows[$field_name] = apachesolr_field_name_map($field_name); + } + } + ksort($rows); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + + return $rows; +} + +/** + * A helper function to save MLT block data. + * + * If passed a block delta, the function will update block settings. If it is + * not passed a block delta, the function will create a new block. + * + * @param array $block_settings An array containing the settings required to form + * a moreLikeThis request. + * + * @param int $delta The id of the block you wish to update. + */ +function apachesolr_search_mlt_save_block($block_settings = array(), $delta = NULL) { + $blocks = variable_get('apachesolr_search_mlt_blocks', array()); + if (is_null($delta)) { + $count = 0; + ksort($blocks); + // Construct a new array key. + if (end($blocks)) { + list(, $count) = explode('-', key($blocks)); + } + $delta = sprintf('mlt-%03d', 1 + $count); + } + $defaults = apachesolr_search_mlt_block_defaults(); + // Remove stray form values. + $blocks[$delta] = array_intersect_key($block_settings, $defaults) + $defaults; + // Eliminate non-selected fields. + $blocks[$delta]['mlt_fl'] = array_filter($blocks[$delta]['mlt_fl']); + $blocks[$delta]['delta'] = $delta; + $blocks[$delta]['mlt_type_filters'] = array_filter($blocks[$delta]['mlt_type_filters']); + $blocks[$delta]['mlt_custom_filters'] = trim($blocks[$delta]['mlt_custom_filters']); + variable_set('apachesolr_search_mlt_blocks', $blocks); +} + +function apachesolr_search_mlt_delete_block_form($form, &$form_state, $block) { + if ($block) { + // Backwards compatibility for the block deltas + if (isset($block['delta'])) { + $delta = $block['delta']; + } + else { + $delta = arg(6); + } + // Add our delta to the delete form + $form['delta'] = array( + '#type' => 'value', + '#value' => $delta, + ); + $question = t('Are you sure you want to delete the "More Like this" block %name?', array('%name' => $block['name'])); + $path = 'admin/structure/block'; + $description = t('The block will be deleted. This action cannot be undone.'); + $yes = t('Delete'); + $no = t('Cancel'); + return confirm_form($form, filter_xss($question), $path, $description, $yes, $no); + } +} + +function apachesolr_search_mlt_delete_block_form_submit($form, &$form_state) { + $blocks = apachesolr_search_load_all_mlt_blocks(); + + unset($blocks[$form_state['values']['delta']]); + variable_set('apachesolr_search_mlt_blocks', $blocks); + drupal_set_message(t('The block has been deleted.')); + $form_state['redirect'] = 'admin/config/search/apachesolr/search-pages'; +} + +/** + * Form to edit moreLikeThis block settings. + * + * @param int $delta If editing, the id of the block to edit. + * + * @return array The form used for editing. + * @todo Add term boost settings. + * @todo Enable the user to specify a query, rather then forcing suggestions + * based on the node id. + */ +function apachesolr_search_mlt_block_form($block_id = NULL) { + if (!empty($block_id)) { + $block = apachesolr_search_mlt_block_load($block_id); + if (!$block) { + return array(); + } + } + else { + $block = apachesolr_search_mlt_block_defaults(); + } + + $form['delta'] = array( + '#type' => 'value', + '#default_value' => isset($block['delta']) ? $block['delta'] : '', + '#weight' => '-2', + ); + + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Block name'), + '#description' => t('The block name displayed to site users.'), + '#required' => TRUE, + '#default_value' => isset($block['name']) ? $block['name'] : '', + '#weight' => '-2', + ); + + $environments = apachesolr_load_all_environments(); + $options = array('' => t('')); + foreach ($environments as $id => $environment) { + $options[$id] = $environment['name']; + } + $form['mlt_env_id'] = array( + '#title' => t('Search environment'), + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($block['mlt_env_id']) ? $block['mlt_env_id'] : apachesolr_default_environment(), + ); + + $form['num_results'] = array( + '#type' => 'select', + '#title' => t('Maximum number of related items to display'), + '#default_value' => isset($block['num_results']) ? $block['num_results'] : '', + '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)), + '#weight' => -1, + + ); + $form['mlt_fl'] = array( + '#type' => 'checkboxes', + '#title' => t('Fields for finding related content'), + '#description' => t('Choose the fields to be used in calculating similarity. The default combination of %taxonomy_names and %title will provide relevant results for typical sites.', array("%taxonomy_names" => apachesolr_field_name_map("taxonomy_names"), "%title" => apachesolr_field_name_map("label"))), + '#options' => apachesolr_search_mlt_get_fields(), + '#required' => TRUE, + '#default_value' => isset($block['mlt_fl']) ? $block['mlt_fl'] : '', + ); + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced configuration'), + '#weight' => '1', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $options = drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7)); + $form['advanced']['mlt_mintf'] = array( + '#type' => 'select', + '#title' => t('Minimum term frequency'), + '#description' => t('A word must appear this many times in any given document before the document is considered relevant for comparison.'), + '#default_value' => isset($block['mlt_mintf']) ? $block['mlt_mintf'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_mindf'] = array( + '#type' => 'select', + '#title' => t('Minimum document frequency'), + '#description' => t('A word must occur in at least this many documents before it will be used for similarity comparison.'), + '#default_value' => isset($block['mlt_mindf']) ? $block['mlt_mindf'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_minwl'] = array( + '#type' => 'select', + '#title' => t('Minimum word length'), + '#description' => 'You can use this to eliminate short words such as "the" and "it" from similarity comparisons. Words must be at least this number of characters or they will be ignored.', + '#default_value' => isset($block['mlt_minwl']) ? $block['mlt_minwl'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_maxwl'] = array( + '#type' => 'select', + '#title' => t('Maximum word length'), + '#description' => t('You can use this to eliminate very long words from similarity comparisons. Words of more than this number of characters will be ignored.'), + '#default_value' => isset($block['mlt_maxwl']) ? $block['mlt_maxwl'] : '', + '#options' => drupal_map_assoc(array(8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)), + ); + $form['advanced']['mlt_maxqt'] = array( + '#type' => 'select', + '#title' => t('Maximum number of query terms'), + '#description' => t('The maximum number of query terms that will be included in any query. Lower numbers will result in fewer recommendations but will get results faster. If a content recommendation is not returning any recommendations, you can either check more "Comparison fields" checkboxes or increase the maximum number of query terms here.'), + '#options' => drupal_map_assoc(array(3, 5, 7, 10, 12, 15, 20, 25, 30, 35, 40)), + '#default_value' => isset($block['mlt_maxqt']) ? $block['mlt_maxqt'] : '', + ); + + $form['restrictions'] = array( + '#type' => 'fieldset', + '#title' => t('Filters'), + '#weight' => '1', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $type_options = array(); + foreach (node_type_get_types() as $key => $type) { + $type_options[$key] = $type->name; + } + + $form['restrictions']['mlt_type_filters'] = array( + '#type' => 'checkboxes', + '#title' => t('Content Types'), + '#default_value' => is_array($block['mlt_type_filters']) ? $block['mlt_type_filters'] : array(), + '#options' => $type_options, + '#description' => t('Select the content types that similarity suggestions should be restricted to. Multiple types are joined with an OR query, so selecting more types results in more recommendations. If none are selected, no filter will be applied.'), + '#weight' => '-2', + ); + + $form['restrictions']['mlt_custom_filters'] = array( + '#type' => 'textfield', + '#title' => t('Additional Query'), + '#description' => t("A query, in Lucene syntax, which will further filter the similarity suggestions. For example, 'label:strategy' will filter related content further to only those with strategy in the title. Here are some more examples:") . + '
    +
  • ss_language:fr
  • +
  • tid:(5 OR 7)
  • +
  • ds_created:[2009-05-01T23:59:59Z TO 2009-07-28T12:30:00Z]
  • +
  • -is_uid:0, -is_uid:1
  • +
', + '#required' => FALSE, + '#default_value' => isset($block['mlt_custom_filters']) ? $block['mlt_custom_filters'] : '', + '#weight' => '-1', + ); + + return $form; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_search.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_search.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,19 @@ +name = Apache Solr search +description = Search with Solr +dependencies[] = search +dependencies[] = apachesolr +package = Search Toolkit +core = 7.x +configure = admin/config/search/apachesolr/search-pages + +files[] = apachesolr_search.install +files[] = apachesolr_search.module +files[] = apachesolr_search.admin.inc +files[] = apachesolr_search.pages.inc + +; Information added by drupal.org packaging script on 2013-03-15 +version = "7.x-1.1+34-dev" +core = "7.x" +project = "apachesolr" +datestamp = "1363307665" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_search.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_search.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,387 @@ + 'tid', + 'apachesolr_search_per_page' => 10, + 'apachesolr_search_browse' => 'results', + 'apachesolr_search_spellcheck' => FALSE, + 'apachesolr_search_search_box' => FALSE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'taxonomy_search', + 'label' => 'Taxonomy Search', + 'description' => 'Search all items with given term', + 'search_path' => 'taxonomy/term/%', + 'env_id' => '', + 'page_title' => '%value', + 'settings' => $settings, + ); + db_insert('apachesolr_search_page')->fields($fields)->execute(); +} + +/** + * Implements hook_enable(). + */ +function apachesolr_search_enable() { + // Make sure the default core search page is installed. + $search_page = apachesolr_search_page_load('core_search'); + if (empty($search_page)) { + // Add Default search page (core search) + // This is a duplication from update_7004 but it is intended + // so future changes are possible without breaking the update + $settings = array( + 'apachesolr_search_search_type' => 'custom', + 'apachesolr_search_per_page' => 10, + 'apachesolr_search_browse' => 'browse', + 'apachesolr_search_spellcheck' => TRUE, + 'apachesolr_search_not_removable' => TRUE, + 'apachesolr_search_search_box' => TRUE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'core_search', + 'label' => 'Core Search', + 'description' => 'Core Search', + 'search_path' => 'search/site', + 'env_id' => 'solr', + 'page_title' => 'Site', + 'settings' => $settings, + ); + db_insert('apachesolr_search_page')->fields($fields)->execute(); + } + + + $active = variable_get('search_active_modules', array('node', 'user')); + $active[] = 'apachesolr_search'; + variable_set('search_active_modules', array_unique($active)); +} + +/** + * Implements hook_schema(). + */ +function apachesolr_search_schema() { + $schema = array(); + + $schema['apachesolr_search_page'] = array( + 'description' => 'Apache Solr Search search page settings.', + 'export' => array( + // Environment machine name. + 'key' => 'page_id', + // Description of key. + 'key name' => 'search page machine name', + // Variable name to use in exported code. + 'identifier' => 'searcher', + // Use the same hook as the API name below. + 'default hook' => 'apachesolr_search_default_searchers', + 'status' => 'apachesolr_search_page_status', + // CTools API implementation. + 'api' => array( + 'owner' => 'apachesolr_search', + 'api' => 'apachesolr_search_defaults', + 'minimum_version' => 3, + 'current_version' => 3, + ), + // Includes all search page specific configurations. + 'export callback' => 'apachesolr_search_page_settings_export', + ), + 'fields' => array( + 'page_id' => array( + 'description' => 'The machine readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'label' => array( + 'description' => 'The human readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'The description of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'search_path' => array( + 'description' => 'The path to the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'page_title' => array( + 'description' => 'The title of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'env_id' => array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'settings' => array( + 'description' => 'Serialized storage of general settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('page_id'), + 'indexes' => array( + 'env_id' => array('env_id'), + ), + ); + + return $schema; +} + +/** + * Implements hook_uninstall(). + */ +function apachesolr_search_uninstall() { + $stored = variable_get('apachesolr_index_last', array()); + unset($stored['apachesolr_search']); + variable_set('apachesolr_index_last', $stored); + + $active = variable_get('search_active_modules', array('node', 'user')); + $idx = array_search('apachesolr_search', $active); + if ($idx !== FALSE) { + unset($active[$idx]); + variable_set('search_active_modules', $active); + } + // Remove variables. + variable_del('apachesolr_search_spellcheck'); + variable_del('apachesolr_search_mlt_blocks'); + variable_del('apachesolr_search_default_search_page'); + // Remove blocks. + db_delete('block')->condition('module', 'apachesolr_search')->execute(); +} + +/** + * Various updates for Drupal 7. + */ +function apachesolr_search_update_7000() { + $taxo_links = variable_get('apachesolr_search_taxonomy_links', 0); + // TODO - enable the new contrib module? + variable_del('apachesolr_search_taxonomy_links'); + // TODO - possibly rename block deltas, etc. + $active = variable_get('search_active_modules', array('node', 'user')); + $active[] = 'apachesolr_search'; + variable_set('search_active_modules', array_unique($active)); + if (variable_get('apachesolr_search_make_default', 0)) { + variable_set('search_default_module', 'apachesolr_search'); + } + variable_del('apachesolr_search_make_default'); +} + +/** + * Add apachesolr_search_page table. + */ +function apachesolr_search_update_7001() { + // Moved to 7002 +} + +/** + * Add apachesolr_search_page table for real. + */ +function apachesolr_search_update_7002() { + + $schema['apachesolr_search_page'] = array( + 'description' => 'Apache Solr Search search page settings.', + 'export' => array( + 'key' => 'page_id', + 'identifier' => 'searcher', + 'default hook' => 'apachesolr_search_default_searchers', + 'status' => 'apachesolr_search_page_status', + 'api' => array( + 'owner' => 'apachesolr_search', + 'api' => 'apachesolr_search_defaults', + 'minimum_version' => 3, + 'current_version' => 3, + ), + 'export callback' => 'apachesolr_search_page_settings_export', + ), + 'fields' => array( + 'page_id' => array( + 'description' => 'The machine readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'label' => array( + 'description' => 'The human readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'The description of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'search_path' => array( + 'description' => 'The path to the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'page_title' => array( + 'description' => 'The title of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'env_id' => array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'settings' => array( + 'description' => 'Serialized storage of general settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('page_id'), + 'indexes' => array( + 'env_id' => array('env_id'), + ), + ); + if (db_table_exists('apachesolr_search_page')) { + // Just in case you are chasing HEAD. + db_drop_table('apachesolr_search_page'); + } + db_create_table('apachesolr_search_page', $schema['apachesolr_search_page']); +} + + +/** + * Delete all Apache Solr Search blocks - they moved to Facet API. + */ +function apachesolr_search_update_7003() { + // Remove blocks. + db_delete('block')->condition('module', 'apachesolr_search')->execute(); +} + +/** + * Add a default search page for core + * Add a taxonomy page if the taxonomy module was ever active + */ +function apachesolr_search_update_7004() { + // Add Default search page (core search) + $settings = array( + 'apachesolr_search_search_type' => 'custom', + 'apachesolr_search_per_page' => variable_get('apachesolr_rows', 10), + 'apachesolr_search_browse' => variable_get('apachesolr_search_browse', 'browse'), + 'apachesolr_search_spellcheck' => variable_get('apachesolr_search_spellcheck', TRUE), + 'apachesolr_search_not_removable' => TRUE, + 'apachesolr_search_search_box' => TRUE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'core_search', + 'label' => 'Core Search', + 'description' => 'Site search', + 'search_path' => 'search/site', + 'env_id' => 'solr', + 'page_title' => 'Site', + 'settings' => $settings, + ); + db_insert('apachesolr_search_page')->fields($fields)->execute(); + // Remove variables. + variable_del('apachesolr_search_spellcheck'); + variable_del('apachesolr_search_browse'); + + // Add this taxonomy search page to the database + $settings = array( + 'apachesolr_search_search_type' => 'tid', + 'apachesolr_search_per_page' => 10, + 'apachesolr_search_browse' => 'results', + 'apachesolr_search_spellcheck' => FALSE, + 'apachesolr_search_search_box' => FALSE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'taxonomy_search', + 'label' => 'Taxonomy Search', + 'description' => 'Search all items with given term', + 'search_path' => 'taxonomy/term/%', + 'env_id' => '', + 'page_title' => '%value', + 'settings' => $settings, + ); + db_insert('apachesolr_search_page')->fields($fields)->execute(); + + // Check if the taxonomy module was ever present + $status = db_query("SELECT 1 FROM {system} WHERE name = 'apachesolr_taxonomy'")->fetchField(); + if ($status) { + $message = t('If you had the apachesolr_taxonomy module enabled please go to the !link and enable the Taxonomy Term page', array('!link' => l('Apache Solr custom pages', 'admin/config/search/apachesolr/search-pages'))); + drupal_set_message($message, 'warning'); + } +} + +/** + * Make the env_id length on the apachesolr_search_page table 64 characters + * to match the length of the env_id on all other tables + */ +function apachesolr_search_update_7005(&$sandbox) { + db_drop_index('apachesolr_search_page', 'env_id'); + db_change_field('apachesolr_search_page', 'env_id', 'env_id', + array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + array( + 'indexes' => array( + 'env_id' => array('env_id'), + ) + ) + ); +} + +/** + * Remove all apachesolr_search env variables for show_facets if it is zero + */ +function apachesolr_search_update_7006() { + module_load_include('module', 'apachesolr'); + $environments = apachesolr_load_all_environments(); + foreach ($environments as $environment) { + $show_facets = apachesolr_environment_variable_get($environment['env_id'], 'apachesolr_search_show_facets', 0); + if ($show_facets === 0) { + apachesolr_environment_variable_del($environment['env_id'], 'apachesolr_search_show_facets'); + } + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_search.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_search.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1726 @@ +fetchField(); + if ($count == 0) { + return NULL; + } + + // Load the default search page, we only support facets to link to this + // search page due to complexity and slow downs + $search_page_id = apachesolr_search_default_search_page(); + $search_page = apachesolr_search_page_load($search_page_id); + // Do not continue if our search page is not valid + if (empty($search_page)) { + return NULL; + } + + $show_facets = apachesolr_environment_variable_get($search_page['env_id'], 'apachesolr_search_show_facets', 0); + if ($show_facets) { + + // Converts current path to lowercase for case insensitive matching. + $paths = array(); + $paths[] = drupal_strtolower(drupal_get_path_alias(current_path())); + $paths[] = drupal_strtolower(current_path()); + + $facet_pages = apachesolr_environment_variable_get($search_page['env_id'], 'apachesolr_search_facet_pages', ''); + + // Iterates over each environment to check if an empty query should be run. + if (!empty($facet_pages)) { + // Compares path with settings, runs query if there is a match. + $patterns = drupal_strtolower($facet_pages); + foreach ($paths as $path) { + if (drupal_match_path($path, $patterns)) { + try { + if (!empty($search_page['search_path'])) { + $solr = apachesolr_get_solr($search_page['env_id']); + // Initializes params for empty query. + $params = array( + 'spellcheck' => 'false', + 'fq' => array(), + 'rows' => 1, + ); + $context['page_id'] = $search_page_id; + $context['search_type'] = 'apachesolr_search_show_facets'; + apachesolr_search_run_empty('apachesolr', $params, $search_page['search_path'], $solr, $context); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + } + } + } +} + +/** + * Implements hook_menu(). + */ +function apachesolr_search_menu() { + $items['admin/config/search/apachesolr/search-pages'] = array( + 'title' => 'Pages/Blocks', + 'description' => 'Configure search pages', + 'page callback' => 'apachesolr_search_page_list_all', + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/add'] = array( + 'title' => 'Add search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_page_settings_form'), + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 1, + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/%apachesolr_search_page/edit'] = array( + 'title' => 'Edit search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_page_settings_form', 5), + 'access arguments' => array('administer search'), + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/%apachesolr_search_page/delete'] = array( + 'title' => 'Delete search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_delete_search_page_confirm', 5), + 'access arguments' => array('administer search'), + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/%apachesolr_search_page/clone'] = array( + 'title' => 'Clone search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_clone_search_page_confirm', 5), + 'access arguments' => array('administer search'), + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/addblock'] = array( + 'title' => 'Add search block "More Like This"', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_mlt_add_block_form'), + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 2, + 'file' => 'apachesolr_search.admin.inc', + ); + $items['admin/config/search/apachesolr/search-pages/block/%apachesolr_search_mlt_block/delete'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('apachesolr_search_mlt_delete_block_form', 6), + 'access arguments' => array('administer search'), + 'file' => 'apachesolr_search.admin.inc', + 'type' => MENU_CALLBACK, + ); + + // Environment specific settings + $settings_path = 'admin/config/search/apachesolr/settings/'; + $items[$settings_path . '%apachesolr_environment/bias'] = array( + 'title' => 'Bias', + 'page callback' => 'apachesolr_bias_settings_page', + 'page arguments' => array(5), + 'access arguments' => array('administer search'), + 'weight' => 4, + 'type' => MENU_LOCAL_TASK, + 'file' => 'apachesolr_search.admin.inc', + ); + return $items; +} + +function apachesolr_search_menu_alter(&$items) { + // Gets default search information. + $default_info = search_get_default_module_info(); + $search_types = apachesolr_search_load_all_search_types(); + $search_pages = apachesolr_search_load_all_search_pages(); + + // Iterates over search pages, builds menu items. + foreach ($search_pages as $search_page) { + // Validate the environemnt ID in case of import or missed deletion. + $environment = apachesolr_environment_load($search_page['env_id']); + if (!$environment) { + continue; + } + + // Parses search path into it's various parts, builds menu items dependent + // on whether %keys is in the path. + $parts = explode('/', $search_page['search_path']); + $keys_pos = count($parts); + // Tests whether we are simulating a core search tab. + $core_search = ($parts[0] == 'search'); + $position = array_search('%', $parts); + $page_title = isset($search_page['page_title']) ? $search_page['page_title'] : 'Search Results'; + + // If we have a taxonomy search, remove existing menu paths + if ($search_page['search_path'] == 'taxonomy/term/%') { + unset($items['taxonomy/term/%taxonomy_term']); + unset($items['taxonomy/term/%taxonomy_term/view']); + } + + // Replace possible tokens [term:tid], [node:nid], [user:uid] with their + // menu-specific variant + $items[$search_page['search_path']] = array( + 'title' => $page_title, + 'page callback' => 'apachesolr_search_custom_page', + 'page arguments' => array($search_page['page_id'], '', $position), + 'access arguments' => array('search content'), + 'type' => ($core_search) ? MENU_LOCAL_TASK : MENU_SUGGESTED_ITEM, + 'file' => 'apachesolr_search.pages.inc', + 'file path' => drupal_get_path('module', 'apachesolr_search'), + ); + + // Not using menu tail because of inflexibility with clean urls + $items[$search_page['search_path'] . '/%'] = array( + 'title' => $page_title, + 'page callback' => 'apachesolr_search_custom_page', + 'page arguments' => array($search_page['page_id'], $keys_pos, $position), + 'access arguments' => array('search content'), + 'type' => !($core_search) ? MENU_CALLBACK : MENU_LOCAL_TASK, + 'file' => 'apachesolr_search.pages.inc', + 'file path' => drupal_get_path('module', 'apachesolr_search'), + ); + + // If title has a certain callback for the selected type we use it + $search_type_id = !empty($search_page['settings']['apachesolr_search_search_type']) ? $search_page['settings']['apachesolr_search_search_type'] : FALSE; + $search_type = !empty($search_types[$search_type_id]) ? $search_types[$search_type_id] : FALSE; + + if ($search_type && !empty($position)) { + $title_callback = $search_type['title callback']; + $items[$search_page['search_path']]['title callback'] = $title_callback; + $items[$search_page['search_path']]['title arguments'] = array($search_page['page_id'], $position); + $items[$search_page['search_path'] . '/%']['title callback'] = $title_callback; + $items[$search_page['search_path'] . '/%']['title arguments'] = array($search_page['page_id'], $position); + } + // If we have additional searches in the search/* path + if ($core_search) { + $items[$search_page['search_path'] . '/%']['tab_root'] = 'search/' . $default_info['path'] . '/%'; + $items[$search_page['search_path'] . '/%']['tab_parent'] = 'search/' . $default_info['path']; + } + } +} + +/** + * Function that loads all the search types + * + * @return array $search_types + */ +function apachesolr_search_load_all_search_types() { + $search_types = &drupal_static(__FUNCTION__); + + if (isset($search_types)) { + return $search_types; + } + // Use cache_get to avoid DB when using memcache, etc. + $cache = cache_get('apachesolr_search:search_types', 'cache_apachesolr'); + if (isset($cache->data)) { + $search_types = $cache->data; + } + else { + $search_types = array( + 'tid' => array( + 'name' => apachesolr_field_name_map('tid'), + 'default menu' => 'taxonomy/term/%', + 'title callback' => 'apachesolr_search_get_taxonomy_term_title', + ), + 'is_uid' => array( + 'name' => apachesolr_field_name_map('is_uid'), + 'default menu' => 'user/%/search', + 'title callback' => 'apachesolr_search_get_user_title', + ), + 'bundle' => array( + 'name' => apachesolr_field_name_map('bundle'), + 'default menu' => 'search/type/%', + 'title callback' => 'apachesolr_search_get_value_title', + ), + 'ss_language' => array( + 'name' => apachesolr_field_name_map('ss_language'), + 'default menu' => 'search/language/%', + 'title callback' => 'apachesolr_search_get_value_title', + ), + ); + drupal_alter('apachesolr_search_types', $search_types); + cache_set('apachesolr_search:search_types', $search_types, 'cache_apachesolr'); + } + return $search_types; +} + +/** + * Used as a callback function to generate a title for the taxonomy term + * depending on the input in the configuration screen + * @param integer $search_page_id + * @param integer $value + * @return String + */ +function apachesolr_search_get_taxonomy_term_title($search_page_id = NULL, $value = NULL) { + $page_title = 'Search results for %value'; + if (isset($value) && isset($search_page_id)) { + $search_page = apachesolr_search_page_load($search_page_id); + $page_title = str_replace('%value', '!value', $search_page['page_title']); + $term = taxonomy_term_load($value); + if (!$term) { + return NULL; + } + $title = $term->name; + } + return t($page_title, array('!value' => $title)); +} + +/** + * Used as a callback function to generate a title for a user name depending + * on the input in the configuration screen + * @param integer $search_page_id + * @param integer $value + * @return String + */ +function apachesolr_search_get_user_title($search_page_id = NULL, $value = NULL) { + $page_title = 'Search results for %value'; + $title = ''; + if (isset($value) && isset($search_page_id)) { + $search_page = apachesolr_search_page_load($search_page_id); + $page_title = str_replace('%value', '!value', $search_page['page_title']); + $user = user_load($value); + if (!$user) { + return NULL; + } + $title = $user->name; + } + return t($page_title, array('!value' => $title)); +} + +/** + * Used as a callback function to generate a title for a node/page depending + * on the input in the configuration screen + * @param integer $search_page_id + * @param integer $value + * @return String + */ +function apachesolr_search_get_value_title($search_page_id = NULL, $value = NULL) { + $page_title = 'Search results for %value'; + if (isset($value) && isset($search_page_id)) { + $search_page = apachesolr_search_page_load($search_page_id); + $page_title = str_replace('%value', '!value', $search_page['page_title']); + $title = $value; + } + return t($page_title, array('!value' => $title)); +} + +/** + * Get or set the default search page id for the current page. + */ +function apachesolr_search_default_search_page($page_id = NULL) { + $default_page_id = &drupal_static(__FUNCTION__, NULL); + + if (isset($page_id)) { + $default_page_id = $page_id; + } + if (empty($default_page_id)) { + $default_page_id = variable_get('apachesolr_search_default_search_page', 'core_search'); + } + return $default_page_id; +} + +/** + * Implements hook_apachesolr_default_environment() + * + * Make sure the core search page is using the default environment. + */ +function apachesolr_search_apachesolr_default_environment($env_id, $old_env_id) { + $page = apachesolr_search_page_load('core_search'); + if ($page && $page['env_id'] != $env_id) { + $page['env_id'] = $env_id; + apachesolr_search_page_save($page); + } +} + +/** + * Load a search page + * @param string $page_id + * @return array + */ +function apachesolr_search_page_load($page_id) { + $search_pages = apachesolr_search_load_all_search_pages(); + if (!empty($search_pages[$page_id])) { + return $search_pages[$page_id]; + } + return FALSE; +} + +function apachesolr_search_page_save($search_page) { + if (!empty($search_page)) { + db_merge('apachesolr_search_page') + ->key(array('page_id' => $search_page['page_id'])) + ->fields(array( + 'label' => $search_page['label'], + 'page_id' => $search_page['page_id'], + 'description' => $search_page['description'], + 'env_id' => $search_page['env_id'], + 'search_path' => $search_page['search_path'], + 'page_title' => $search_page['page_title'], + 'settings' => serialize($search_page['settings']), + )) + ->execute(); + } +} + + /** + * Function that clones a search page + * + * @param $page_id + * The page identifier it needs to clone. + * + */ +function apachesolr_search_page_clone($page_id) { + $search_page = apachesolr_search_page_load($page_id); + // Get all search_pages + $search_pages = apachesolr_search_load_all_search_pages(); + // Create an unique ID + $new_search_page_id = apachesolr_create_unique_id($search_pages, $search_page['page_id']); + // Set this id to the new search page + $search_page['page_id'] = $new_search_page_id; + $search_page['label'] = $search_page['label'] . ' [cloned]'; + // All cloned search pages should be removable + if (isset($search_page['settings']['apachesolr_search_not_removable'])) { + unset($search_page['settings']['apachesolr_search_not_removable']); + } + // Save our new search page in the database + apachesolr_search_page_save($search_page); +} + +/** + * Implements hook_block_info(). + */ +function apachesolr_search_block_info() { + // Get all of the moreLikeThis blocks that the user has created + $blocks = apachesolr_search_load_all_mlt_blocks(); + foreach ($blocks as $delta => $settings) { + $blocks[$delta] += array('info' => t('Apache Solr recommendations: !name', array('!name' => $settings['name'])) , 'cache' => DRUPAL_CACHE_PER_PAGE); + } + // Add the sort block. + $blocks['sort'] = array( + 'info' => t('Apache Solr Core: Sorting'), + 'cache' => DRUPAL_CACHE_CUSTOM, + ); + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function apachesolr_search_block_view($delta = '') { + if ($delta == 'sort') { + $environments = apachesolr_load_all_environments(); + foreach ($environments as $env_id => $environment) { + if (apachesolr_has_searched($env_id) && !apachesolr_suppress_blocks($env_id) && $delta == 'sort') { + $response = NULL; + $query = apachesolr_current_query($env_id); + $solrsort = NULL; + if ($query) { + // Get the query and response. Without these no blocks make sense. + $response = apachesolr_static_response_cache($query->getSearcher()); + } + + // If there are less than two results, do not return the sort block + if (empty($response) || ($response->response->numFound < 2)) { + return NULL; + } + + // Check if we have to return a cached version of this block + if ($query) { + // Get the URI without any query parameter. + $uri = parse_url(request_uri()); + // Get the current sort as an array. + $solrsort = $query->getSolrsort(); + $cache_id = $uri['path'] . ':' . implode(':', $solrsort); + // Do we have something in cache ? + if ($cache = cache_get($cache_id, 'cache_block')) { + $block = $cache->data; + return $block; + } + } + + $sorts = $query->getAvailableSorts(); + $sort_links = array(); + $path = $query->getPath(); + $new_query = clone $query; + $toggle = array('asc' => 'desc', 'desc' => 'asc'); + foreach ($sorts as $name => $sort) { + $active = $solrsort['#name'] == $name; + if ($name == 'score') { + $direction = ''; + $new_direction = 'desc'; // We only sort by descending score. + } + elseif ($active) { + $direction = $toggle[$solrsort['#direction']]; + $new_direction = $toggle[$solrsort['#direction']]; + } + else { + $direction = ''; + $new_direction = $sort['default']; + } + $new_query->setSolrsort($name, $new_direction); + $sort_links[$name] = array( + 'text' => $sort['title'], + 'path' => $path, + 'options' => array('query' => $new_query->getSolrsortUrlQuery()), + 'active' => $active, + 'direction' => $direction, + ); + } + foreach ($sort_links as $name => $link) { + $themed_links[$name] = theme('apachesolr_sort_link', $link); + } + $block = array( + 'subject' => t('Sort by'), + 'content' => theme('apachesolr_sort_list', array('items' => $themed_links)) + ); + // Cache the block + cache_set($cache_id, $block, 'cache_block'); + return $block; + } + } + } + elseif (($node = menu_get_object()) && (!arg(2) || arg(2) == 'view')) { + $suggestions = array(); + // Determine whether the user can view the current node. Probably not necessary. + $block = apachesolr_search_mlt_block_load($delta); + if ($block && node_access('view', $node)) { + // Get our specific environment for the MLT block + $env_id = (!empty($block['mlt_env_id'])) ? $block['mlt_env_id'] : ''; + try { + $solr = apachesolr_get_solr($env_id); + $context['search_type'] = 'apachesolr_search_mlt'; + $context['block_id'] = $delta; + $docs = apachesolr_search_mlt_suggestions($block, apachesolr_document_id($node->nid), $solr, $context); + if (!empty($docs)) { + $suggestions['subject'] = check_plain($block['name']); + $suggestions['content'] = array( + '#theme' => 'apachesolr_search_mlt_recommendation_block', + '#docs' => $docs, + '#delta' => $delta + ); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + return $suggestions; + } +} + +/** + * Implements hook_form_[form_id]_alter(). + */ +function apachesolr_search_form_block_admin_display_form_alter(&$form) { + foreach ($form['blocks'] as $key => $block) { + if ((strpos($key, "apachesolr_search_mlt-") === 0) && $block['module']['#value'] == 'apachesolr_search') { + $form['blocks'][$key]['delete'] = array( + '#type' => 'link', + '#title' => 'delete', + '#href' => 'admin/config/search/apachesolr/search-pages/block/' . $block['delta']['#value'] . '/delete', + ); + } + } +} + +/** + * Implements hook_block_configure(). + */ +function apachesolr_search_block_configure($delta = '') { + if ($delta != 'sort') { + require_once(drupal_get_path('module', 'apachesolr') . '/apachesolr_search.admin.inc'); + return apachesolr_search_mlt_block_form($delta); + } +} + +/** + * Implements hook_block_save(). + */ +function apachesolr_search_block_save($delta = '', $edit = array()) { + if ($delta != 'sort') { + require_once(drupal_get_path('module', 'apachesolr') . '/apachesolr_search.admin.inc'); + apachesolr_search_mlt_save_block($edit, $delta); + } +} + + +/** + * Return all the saved search pages + * @return array $search_pages + * Array of all search pages + */ +function apachesolr_search_load_all_search_pages() { + $search_pages = &drupal_static(__FUNCTION__, array()); + + if (!empty($search_pages)) { + return $search_pages; + } + + // If ctools module is enabled, add search pages from code, e.g. from a + // feature module. + if (module_exists('ctools')) { + ctools_include('export'); + $defaults = ctools_export_load_object('apachesolr_search_page', 'all'); + foreach ($defaults as $page_id => $default) { + $search_pages[$page_id] = (array) $default; + } + } + + // Get all search_pages and their id + $search_pages_db = db_query('SELECT * FROM {apachesolr_search_page}')->fetchAllAssoc('page_id', PDO::FETCH_ASSOC); + + $search_pages = $search_pages + $search_pages_db; + + // Ensure that the core search page uses the default environment. In some + // instances, for example when unit testing, this search page isn't defined. + if (isset($search_pages['core_search'])) { + $search_pages['core_search']['env_id'] = apachesolr_default_environment(); + } + + // convert settings to an array + foreach ($search_pages as $id => $search_page) { + if (is_string($search_pages[$id]['settings'])) { + $search_pages[$id]['settings'] = unserialize($search_pages[$id]['settings']); + // Prevent false outcomes for the following search page + $settings = 0; + } + } + return $search_pages; +} + +function apachesolr_search_load_all_mlt_blocks() { + $search_blocks = variable_get('apachesolr_search_mlt_blocks', array()); + return $search_blocks; +} + +function apachesolr_search_mlt_block_load($block_id) { + $search_blocks = variable_get('apachesolr_search_mlt_blocks', array()); + return isset($search_blocks[$block_id]) ? $search_blocks[$block_id] : FALSE; +} + +/** + * Performs a moreLikeThis query using the settings and retrieves documents. + * + * @param $settings + * An array of settings. + * @param $id + * The Solr ID of the document for which you want related content. + * For a node that is apachesolr_document_id($node->nid) + * @param $solr + * The solr environment you want to query against + * + * @return An array of response documents, or NULL + */ +function apachesolr_search_mlt_suggestions($settings, $id, $solr = NULL, $context = array()) { + + try { + $fields = array( + 'mlt_mintf' => 'mlt.mintf', + 'mlt_mindf' => 'mlt.mindf', + 'mlt_minwl' => 'mlt.minwl', + 'mlt_maxwl' => 'mlt.maxwl', + 'mlt_maxqt' => 'mlt.maxqt', + 'mlt_boost' => 'mlt.boost', + 'mlt_qf' => 'mlt.qf', + ); + $params = array( + 'q' => 'id:' . $id, + 'qt' => 'mlt', + 'fl' => array('entity_id', 'entity_type', 'label', 'path', 'url'), + 'mlt.fl' => $settings['mlt_fl'], + 'start' => 0, + 'rows' => $settings['num_results'], + ); + // We can optionally specify a Solr object. + $query = apachesolr_drupal_query('apachesolr_mlt', $params, '', '', $solr, $context); + + foreach ($fields as $form_key => $name) { + if (!empty($settings[$form_key])) { + $query->addParam($name, $settings[$form_key]); + } + } + + $type_filters = array(); + if (is_array($settings['mlt_type_filters']) && !empty($settings['mlt_type_filters'])) { + $query->addFilter('bundle', '(' . implode(' OR ', $settings['mlt_type_filters']) . ') '); + } + + if ($custom_filters = $settings['mlt_custom_filters']) { + // @todo - fix the settings form to take a comma-delimited set of filters. + $query->addFilter('', $custom_filters); + } + + // This hook allows modules to modify the query object. + drupal_alter('apachesolr_query', $query); + if ($query->abort_search) { + return NULL; + } + + $response = $query->search(); + + if (isset($response->response->docs)) { + return (array) $response->response->docs; + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +function theme_apachesolr_search_mlt_recommendation_block($vars) { + $docs = $vars['docs']; + $links = array(); + foreach ($docs as $result) { + // Suitable for single-site mode. Label is already safe. + $links[] = l($result->label, $result->path, array('html' => TRUE)); + } + $links = array( + '#theme' => 'item_list', + '#items' => $links, + ); + return render($links); +} + +/** + * Implements hook_search_info(). + */ +function apachesolr_search_search_info() { + // Load our core search page + // This core search page is assumed to always be there. It cannot be deleted. + $search_page = apachesolr_search_page_load('core_search'); + + // This can happen during install, or if the DB was manually changed. + if (empty($search_page)) { + $search_page = array(); + $search_page['page_title'] = 'Site'; + $search_page['search_path'] = 'search/site'; + } + + return array( + 'title' => $search_page['page_title'], + 'path' => str_replace('search/', '', $search_page['search_path']), + 'conditions_callback' => variable_get('apachesolr_search_conditions_callback', 'apachesolr_search_conditions'), + ); +} + +/** + * Implements hook_search_reset(). + */ +function apachesolr_search_search_reset() { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $env_id = apachesolr_default_environment(); + apachesolr_index_mark_for_reindex($env_id); +} + +/** + * Implements hook_search_status(). + */ +function apachesolr_search_search_status() { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $env_id = apachesolr_default_environment(); + return apachesolr_index_status($env_id); +} + +/** + * Implements hook_search_execute(). + * @param $keys + * The keys that are available after the path that is defined in + * hook_search_info + * @param $conditions + * Conditions that are coming from apachesolr_search_conditions + */ +function apachesolr_search_search_execute($keys = NULL, $conditions = NULL) { + $search_page = apachesolr_search_page_load('core_search'); + $results = apachesolr_search_search_results($keys, $conditions, $search_page); + return $results; +} + +/** + * Implementation of a search_view() conditions callback. + */ +function apachesolr_search_conditions() { + //get default conditions from the core_search + $search_page = apachesolr_search_page_load('core_search'); + $conditions = apachesolr_search_conditions_default($search_page); + return $conditions; +} + +/** + * Implements hook_search_page(). + * @param $results + * The results that came from apache solr + */ +function apachesolr_search_search_page($results) { + $search_page = apachesolr_search_page_load('core_search'); + $build = apachesolr_search_search_page_custom($results, $search_page); + return $build; +} + +/** + * Mimics apachesolr_search_search_page() but is used for custom search pages + * We prefer to keep them seperate so we are not dependent from core search + * when someone tries to disable the core search + * @param $results + * The results that came from apache solr + * @param $build + * the build array from where this function was called. Good to append output + * to the build array + * @param $search_page + * the search page that is requesting an output + */ +function apachesolr_search_search_page_custom($results, $search_page, $build = array()) { + if (!empty($search_page['settings']['apachesolr_search_spellcheck'])) { + // Retrieve suggestion + $suggestions = apachesolr_search_get_search_suggestions($search_page['env_id']); + if ($search_page && !empty($suggestions)) { + $build['suggestions'] = array( + '#theme' => 'apachesolr_search_suggestions', + '#links' => array(l($suggestions[0], $search_page['search_path'] . '/' . $suggestions[0])), + ); + } + } + // Retrieve expected results from searching + if (!empty($results['apachesolr_search_browse'])) { + // Show facet browsing blocks. + $build['search_results'] = apachesolr_search_page_browse($results['apachesolr_search_browse'], $search_page['env_id']); + } + elseif ($results) { + $build['search_results'] = array( + '#theme' => 'search_results', + '#results' => $results, + '#module' => 'apachesolr_search', + '#search_page' => $search_page, + ); + } + else { + // Give the user some custom help text. + $build['search_results'] = array('#markup' => theme('apachesolr_search_noresults')); + } + + // Allows modules to alter the render array before returning. + drupal_alter('apachesolr_search_page', $build, $search_page); + + return $build; +} + +/** + * Executes search depending on the conditions given. + * See apachesolr_search.pages.inc for another use of this function + */ +function apachesolr_search_search_results($keys = NULL, $conditions = NULL, $search_page = NULL) { + $params = array(); + $results = array(); + // Process the search form. Note that if there is $_POST data, + // search_form_submit() will cause a redirect to search/[module path]/[keys], + // which will get us back to this page callback. In other words, the search + // form submits with POST but redirects to GET. This way we can keep + // the search query URL clean as a whistle. + if (empty($_POST['form_id']) + || ($_POST['form_id'] != 'apachesolr_search_custom_page_search_form') + && ($_POST['form_id'] != 'search_form') + && ($_POST['form_id'] != 'search_block_form') ) { + // Check input variables + if (empty($search_page)) { + $search_page = apachesolr_search_page_load('core_search'); + // Verify if it actually loaded + if (empty($search_page)) { + // Something must have been really messed up. + apachesolr_failure(t('Solr search'), $keys); + return array(); + } + } + if (empty($conditions)) { + $conditions = apachesolr_search_conditions_default($search_page); + } + + // Sort options from the conditions array. + // @see apachesolr_search_conditions_default() + // See This condition callback to find out how. + $solrsort = isset($conditions['apachesolr_search_sort']) ? $conditions['apachesolr_search_sort'] : ''; + // What to do when we have an initial empty search + $empty_search_behavior = isset($search_page['settings']['apachesolr_search_browse']) ? $search_page['settings']['apachesolr_search_browse'] : ''; + + try { + + $solr = apachesolr_get_solr($search_page['env_id']); + // Default parameters + $params['fq'] = isset($conditions['fq']) ? $conditions['fq'] : array(); + $params['rows'] = $search_page['settings']['apachesolr_search_per_page']; + + if (empty($search_page['settings']['apachesolr_search_spellcheck'])) { + // Spellcheck needs to have a string as false/true + $params['spellcheck'] = 'false'; + } + else { + $params['spellcheck'] = 'true'; + } + + // Empty text Behavior + if (!$keys && !isset($conditions['f']) && ($empty_search_behavior == 'browse' || $empty_search_behavior == 'blocks')) { + // Pass empty search behavior as string on to apachesolr_search_search_page() + // Hardcoded apachesolr name since we rely on this for the facets + $context['page_id'] = $search_page['page_id']; + $context['search_type'] = 'apachesolr_search_browse'; + apachesolr_search_run_empty('apachesolr', $params, $search_page['search_path'], $solr, $context); + $results['apachesolr_search_browse'] = $empty_search_behavior; + + if ($empty_search_behavior == 'browse') { + // Hide sidebar blocks for content-area browsing instead. + apachesolr_suppress_blocks($search_page['env_id'], TRUE); + } + } + // Full text behavior. Triggers with a text search or a facet + elseif (($keys || isset($conditions['f'])) || ($empty_search_behavior == 'results')) { + $params['q'] = $keys; + // Hardcoded apachesolr name since we rely on this for the facets + $context['page_id'] = $search_page['page_id']; + $context['search_type'] = 'apachesolr_search_results'; + $results = apachesolr_search_run('apachesolr', $params, $solrsort, $search_page['search_path'], pager_find_page(), $solr, $context); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + apachesolr_failure(t('Solr search'), $keys); + } + } + return $results; +} + +function apachesolr_search_conditions_default($search_page) { + $conditions = array(); + $search_type = isset($search_page['settings']['apachesolr_search_search_type']) ? $search_page['settings']['apachesolr_search_search_type'] : ''; + $allow_user_input = isset($search_page['settings']['apachesolr_search_allow_user_input']) ? $search_page['settings']['apachesolr_search_allow_user_input'] : FALSE; + $path_replacer = isset($search_page['settings']['apachesolr_search_path_replacer']) ? $search_page['settings']['apachesolr_search_path_replacer'] : ''; + $set_custom_filter = isset($search_page['settings']['apachesolr_search_custom_enable']) ? $search_page['settings']['apachesolr_search_custom_enable'] : ''; + $search_page_fq = !empty($search_page['settings']['fq']) ? $search_page['settings']['fq'] : ''; + + $conditions['fq'] = array(); + // We only allow this to happen if the search page explicitely allows it + if ($allow_user_input) { + // Get the filterQueries from the url + if (!empty($_GET['fq']) && is_array($_GET['fq'])) { + // Reset the array so that we have one level lower to go through + $conditions['fq'] = $_GET['fq']; + } + foreach($conditions['fq'] as $condition_id => $condition) { + // If the user input does not pass our validation we do not allow + // it to query solr + $test_query = apachesolr_drupal_subquery('Test'); + if (!$test_query->validFilterValue($condition)) { + unset($conditions['fq'][$condition_id]); + } + } + } + + // Custom filters added in search pages + if (!empty($search_page_fq) && !empty($set_custom_filter)) { + if (!empty($path_replacer)) { + // If the manual filter has a % in it, replace it with $value + $conditions['fq'][] = str_replace('%', $path_replacer, $search_page_fq); + } + else { + // Put the complete filter in the filter query + $conditions['fq'][] = $search_page_fq; + } + } + + // Search type filters (such as taxonomy) + if (!empty($path_replacer) && !empty($search_type) && $search_type != 'custom') { + $conditions['fq'][] = $search_type . ':' . $path_replacer; + } + + // We may also have filters added by facet API module. The 'f' + // is determined by variable FacetapiUrlProcessor::$filterKey. Hard + // coded here to avoid extra class loading. + if (!empty($_GET['f']) && is_array($_GET['f'])) { + if (module_exists('facetapi')) { + $conditions['f'] = $_GET['f']; + } + } + // Add the sort from the page to our conditions + $sort = isset($_GET['solrsort']) ? $_GET['solrsort'] : ''; + $conditions['apachesolr_search_sort'] = $sort; + return $conditions; +} + +/** + * Handle browse results for empty searches. + */ +function apachesolr_search_page_browse($empty_search_behavior, $env_id) { + $build = array(); + // Switch in case we come up with new flags. + switch ($empty_search_behavior) { + case 'browse': + if (module_exists('facetapi') && $query = apachesolr_current_query($env_id)) { + module_load_include('inc', 'facetapi', 'facetapi.block'); + // Get facet render elements. + $searcher = $query->getSearcher(); + $elements = facetapi_build_realm($searcher, 'block'); + $build = array(); + + foreach (element_children($elements) as $key) { + $delta = "facetapi_{$key}"; + // @todo: order/filter these pseudo-blocks according to block.module weight, visibility (see 7.x-1beta4) + $block = new stdClass(); + $block->visibility = TRUE; + $block->enabled = TRUE; + $block->module = 'facetapi'; + $block->subject = theme('facetapi_title', array('title' => $elements[$key]['#title'])); + $build[$delta] = $elements[$key]; + $block->region = NULL; + $block->delta = 'apachesolr-' . $key; + // @todo: the final themed block's div id attribute does not coincide with "real" block's id (see facetapi_get_delta_map()) + $build[$delta]['#block'] = $block; + $build[$delta]['#theme_wrappers'][] = 'block'; + $build['#sorted'] = TRUE; + } + $build['#theme_wrappers'][] = 'apachesolr_search_browse_blocks'; + } + break; + } + return $build; +} + +/** + * Shows a groups of blocks for starting a search from a filter. + */ +function theme_apachesolr_search_browse_blocks($vars) { + $result = ''; + if ($vars['content']['#children']) { + $result .= "
\n

" . t('Browse available categories') . "

\n"; + $result .= '

' . t('Pick a category to launch a search.') . "

\n"; + $result .= $vars['content']['#children'] . "\n
\n"; + } + + return $result; +} + +/** + * Execute a search with zero results rows so as to populate facets. + */ +function apachesolr_search_run_empty($name, array $params = array(), $base_path = '', $solr = NULL, $context = array()) { + $query = apachesolr_drupal_query($name, $params, '', $base_path, $solr, $context); + $query->addParam('rows', '0'); + $solr_id = $query->solr('getId'); + list($final_query, $response) = apachesolr_do_query($query); + apachesolr_has_searched($solr_id, TRUE); +} + +/** + * Execute a search results based on keyword, filter, and sort strings. + * + * @param $name + * @param $params + * Array - 'q' is the keywords to search. + * @param $solrsort + * @param $base_path + * For constructing filter and sort links. Leave empty unless the links need to point somewhere + * other than the base path of the current request. + * @param integer $page + * For pagination. + * @param DrupalApacheSolrServiceInterface $solr + * The solr server resource to execute the search on. + * + * @return stdClass $response + * + * @throws Exception + */ +function apachesolr_search_run($name, array $params = array(), $solrsort = '', $base_path = '', $page = 0, DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) { + // Merge the default params into the params sent in. + $params += apachesolr_search_basic_params(); + // This is the object that knows about the query coming from the user. + $query = apachesolr_drupal_query($name, $params, $solrsort, $base_path, $solr, $context); + + if ($query->getParam('q')) { + apachesolr_search_add_spellcheck_params($query); + } + + // Add the paging parameters + $query->page = $page; + + apachesolr_search_add_boost_params($query); + if ($query->getParam('q')) { + apachesolr_search_highlighting_params($query); + if (!$query->getParam('hl.fl')) { + $qf = array(); + foreach ($query->getParam('qf') as $field) { + // Truncate off any boost so we get the simple field name. + $parts = explode('^', $field, 2); + $qf[$parts[0]] = TRUE; + } + foreach (array('content', 'ts_comments') as $field) { + if (isset($qf[$field])) { + $query->addParam('hl.fl', $field); + } + } + } + } + else { + // No highlighting, use the teaser as a snippet. + $query->addParam('fl', 'teaser'); + } + + list($final_query, $response) = apachesolr_do_query($query); + $env_id = $query->solr('getId'); + apachesolr_has_searched($env_id, TRUE); + $process_response_callback = apachesolr_environment_variable_get($env_id, 'process_response_callback', 'apachesolr_search_process_response'); + if (function_exists($process_response_callback)) { + return call_user_func($process_response_callback, $response, $final_query); + } + else { + return apachesolr_search_process_response($response, $final_query); + } +} + +function apachesolr_search_basic_params(DrupalSolrQueryInterface $query = NULL) { + $params = array( + 'fl' => array( + 'id', + 'entity_id', + 'entity_type', + 'bundle', + 'bundle_name', + 'label', + 'is_comment_count', + 'ds_created', + 'ds_changed', + 'score', + 'path', + 'url', + 'is_uid', + 'tos_name', + ), + 'mm' => 1, + 'rows' => 10, + 'pf' => 'content^2.0', + 'ps' => 15, + 'hl' => 'true', + 'hl.fl' => 'content', + 'hl.snippets' => 3, + 'hl.mergeContigious' => 'true', + 'f.content.hl.alternateField' => 'teaser', + 'f.content.hl.maxAlternateFieldLength' => 256, + ); + if ($query) { + $query->addParams($params); + } + return $params; +} + +/** + * Add highlighting settings to the search params. + * + * These settings are set in solrconfig.xml. + * See the defaults there. + * If you wish to override them, you can via settings.php or drush + */ +function apachesolr_search_highlighting_params(DrupalSolrQueryInterface $query = NULL) { + $params['hl'] = variable_get('apachesolr_hl_active', NULL); + $params['hl.fragsize']= variable_get('apachesolr_hl_textsnippetlength', NULL); + $params['hl.simple.pre'] = variable_get('apachesolr_hl_pretag', NULL); + $params['hl.simple.post'] = variable_get('apachesolr_hl_posttag', NULL); + $params['hl.snippets'] = variable_get('apachesolr_hl_numsnippets', NULL); + // This should be an array of possible field names. + $params['hl.fl'] = variable_get('apachesolr_hl_fieldtohighlight', NULL); + $params = array_filter($params); + if ($query) { + $query->addParams($params); + } + return $params; +} + +function apachesolr_search_add_spellcheck_params(DrupalSolrQueryInterface $query) { + $params = array(); + + // Add new parameter to the search request + $params['spellcheck.q'] = $query->getParam('q'); + $params['spellcheck'] = 'true'; + $query->addParams($params); +} + +function apachesolr_search_add_boost_params(DrupalSolrQueryInterface $query) { + $env_id = $query->solr('getId'); + $params = array(); + + $defaults = array( + 'content' => '1.0', + 'ts_comments' => '0.5', + 'tos_content_extra' => '0.1', + 'label' => '5.0', + 'tos_name' => '3.0', + 'taxonomy_names' => '2.0', + 'tags_h1' => '5.0', + 'tags_h2_h3' => '3.0', + 'tags_h4_h5_h6' => '2.0', + 'tags_inline' => '1.0', + 'tags_a' => '0', + ); + $qf = apachesolr_environment_variable_get($env_id, 'field_bias', $defaults); + $fields = $query->solr('getFields'); + if ($qf && $fields) { + foreach ($fields as $field_name => $field) { + if (!empty($qf[$field_name])) { + $prefix = substr($field_name, 0, 3); + if ($field_name == 'content' || $prefix == 'ts_' || $prefix == 'tm_') { + // Normed fields tend to have a lower score. Multiplying by 40 is + // a rough attempt to bring the score in line with fields that are + // not normed. + $qf[$field_name] *= 40.0; + } + $params['qf'][$field_name] = $field_name . '^' . $qf[$field_name]; + } + } + } + + $date_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_date_boost', '0:0'); + $comment_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_comment_boost', '0:0'); + $changed_settings = apachesolr_environment_variable_get($env_id, 'apachesolr_search_changed_boost', '0:0'); + $sticky_boost = apachesolr_environment_variable_get($env_id, 'apachesolr_search_sticky_boost', '0'); + $promote_boost = apachesolr_environment_variable_get($env_id, 'apachesolr_search_promote_boost', '0'); + // For the boost functions for the created timestamp, etc we use the + // standard date-biasing function, as suggested (but steeper) at + // http://wiki.apache.org/solr/SolrRelevancyFAQ#How_can_I_boost_the_score_of_newer_documents + // ms() returns the time difference in ms between now and the date + // The function is thus: $ab/(ms(NOW,date)*$steepness + $ab). + list($date_steepness, $date_boost) = explode(':', $date_settings); + if ($date_boost) { + $ab = 4 / $date_steepness; + $params['bf'][] = "recip(ms(NOW,ds_created),3.16e-11,$ab,$ab)^$date_boost"; + } + // Boost on comment count. + list($comment_steepness, $comment_boost) = explode(':', $comment_settings); + if ($comment_boost) { + $params['bf'][] = "recip(div(1,max(is_comment_count,1)),$comment_steepness,10,10)^$comment_boost"; + } + // Boost for a more recent comment or node edit. + list($changed_steepness, $changed_boost) = explode(':', $changed_settings); + if ($changed_boost) { + $ab = 4 / $changed_steepness; + $params['bf'][] = "recip(ms(NOW,ds_created),3.16e-11,$ab,$ab)^$changed_boost"; + } + // Boost for nodes with sticky bit set. + if ($sticky_boost) { + $params['bq'][] = "bs_sticky:true^$sticky_boost"; + } + // Boost for nodes with promoted bit set. + if ($promote_boost) { + $params['bq'][] = "bs_promote:true^$promote_boost"; + } + // Modify the weight of results according to the node types. + $type_boosts = apachesolr_environment_variable_get($env_id, 'apachesolr_search_type_boosts', array()); + if (!empty($type_boosts)) { + foreach ($type_boosts as $type => $boost) { + // Only add a param if the boost is != 0 (i.e. > "Normal"). + if ($boost) { + $params['bq'][] = "bundle:$type^$boost"; + } + } + } + $query->addParams($params); +} + +function apachesolr_search_process_response($response, DrupalSolrQueryInterface $query) { + $results = array(); + // We default to getting snippets from the body content and comments. + $hl_fl = $query->getParam('hl.fl'); + if (!$hl_fl) { + $hl_fl = array('content', 'ts_comments'); + } + $total = $response->response->numFound; + pager_default_initialize($total, $query->getParam('rows')); + if ($total > 0) { + $fl = $query->getParam('fl'); + // 'id' and 'entity_type' are the only required fields in the schema, and + // 'score' is generated by solr. + foreach ($response->response->docs as $doc) { + $extra = array(); + // Allow modules to alter each document and its extra information. + drupal_alter('apachesolr_search_result', $doc, $extra, $query); + + // Start with an empty snippets array. + $snippets = array(); + + // Find the nicest available snippet. + foreach ($hl_fl as $hl_param) { + if (isset($response->highlighting->{$doc->id}->$hl_param)) { + // Merge arrays preserving keys. + foreach ($response->highlighting->{$doc->id}->$hl_param as $value) { + $snippets[$hl_param][] = $value; + } + } + } + // If there's no snippet at this point, add the teaser. + if (!$snippets) { + if (isset($doc->teaser)) { + $snippets[] = truncate_utf8($doc->teaser, 256, TRUE); + } + } + + $hook = 'apachesolr_search_snippets__' . $doc->entity_type; + $bundle = !empty($doc->bundle) ? $doc->bundle : NULL; + if ($bundle) { + $hook .= '__' . $bundle; + } + $snippet = theme($hook, array('doc' => $doc, 'snippets' => $snippets)); + + if (!isset($doc->content)) { + $doc->content = $snippet; + } + + // Normalize common dates so that we can use Drupal's normal date and + // time handling. + if (isset($doc->ds_created)) { + $doc->created = strtotime($doc->ds_created); + } + else { + $doc->created = NULL; + } + + if (isset($doc->ds_changed)) { + $doc->changed = strtotime($doc->ds_changed); + } + else { + $doc->changed = NULL; + } + + if (isset($doc->tos_name)) { + $doc->name = $doc->tos_name; + } + else { + $doc->name = NULL; + } + + // Set all expected fields from fl to NULL if they are missing so + // as to prevent Notice: Undefined property. + $fl = array_merge($fl, array('path', 'label', 'score')); + foreach ($fl as $field) { + if (!isset($doc->{$field})) { + $doc->{$field} = NULL; + } + } + + $fields = (array) $doc; + + // a path is not a requirement of entity (see entity_uri() ), so we check if we + // can show it and fallback to the main page of the site if we don't + // have it. + if (!isset($doc->url)) { + $path = ''; + } + else { + $path = $doc->url; + } + + $result = array( + // link is a required field, so handle it centrally. + 'link' => $path, + // template_preprocess_search_result() runs check_plain() on the title + // again. Decode to correct the display. + 'title' => htmlspecialchars_decode($doc->label, ENT_QUOTES), + // These values are not required by the search module but are provided + // to give entity callbacks and themers more flexibility. + 'score' => $doc->score, + 'snippets' => $snippets, + 'snippet' => $snippet, + 'fields' => $fields, + 'entity_type' => $doc->entity_type, + 'bundle' => $bundle, + ); + + // Call entity-type-specific callbacks for extra handling. + $function = apachesolr_entity_get_callback($doc->entity_type, 'result callback', $bundle); + if (is_callable($function)) { + $function($doc, $result, $extra); + } + + $result['extra'] = $extra; + + $results[] = $result; + } + } + // Hook to allow modifications of the retrieved results + foreach (module_implements('apachesolr_process_results') as $module) { + $function = $module . '_apachesolr_process_results'; + $function($results, $query); + } + return $results; +} + +/** + * Retrieve all of the suggestions that were given after a certain search + * @return array() + */ +function apachesolr_search_get_search_suggestions($env_id) { + $suggestions_output = array(); + if (apachesolr_has_searched($env_id)) { + $query = apachesolr_current_query($env_id); + $keyword = $query->getParam('q'); + $searcher = $query->getSearcher(); + $response = apachesolr_static_response_cache($searcher); + // Get spellchecker suggestions into an array. + if (!empty($response->spellcheck->suggestions)) { + $suggestions = get_object_vars($response->spellcheck->suggestions); + if ($suggestions) { + $replacements = array(); + // Get the original query and retrieve all words with suggestions. + foreach ($suggestions as $word => $value) { + $replacements[$word] = $value->suggestion[0]; + } + // Replace the keyword with the suggested keyword. + $suggested_keyword = strtr($keyword, $replacements); + // Show only if suggestion is different than current query. + if ($keyword != $suggested_keyword) { + $suggestions_output[] = $suggested_keyword; + } + } + } + } + return $suggestions_output; +} + +/** + * Implements hook_apachesolr_entity_info_alter(). + */ +function apachesolr_search_apachesolr_entity_info_alter(&$entity_info) { + // First set defaults so that we don't need to worry about NULL keys. + foreach (array_keys($entity_info) as $type) { + $entity_info[$type]['result callback'] = ''; + } + // Now set those values that we know. Other modules can do so + // for their own entities if they want. + $entity_info['node']['result callback'] = 'apachesolr_search_node_result'; +} + +/** + * Callback function for node search results. + * + * @param stdClass $doc + * The result document from Apache Solr. + * @param array $result + * The result array for this record to which to add. + */ +function apachesolr_search_node_result($doc, &$result, &$extra) { + $doc->uid = $doc->is_uid; + $result += array( + 'type' => node_type_get_name($doc->bundle), + 'user' => theme('username', array('account' => $doc)), + 'date' => isset($doc->changed) ? $doc->changed : 0, + 'node' => $doc, + 'uid' => $doc->is_uid, + ); + + if (isset($doc->is_comment_count)) { + $extra['comments'] = format_plural($doc->is_comment_count, '1 comment', '@count comments'); + } +} + +/** + * Returns whether a search page exists. + */ +function apachesolr_search_page_exists($search_page_id) { + return db_query('SELECT 1 FROM {apachesolr_search_page} WHERE page_id = :page_id', array(':page_id' => $search_page_id))->fetchField(); +} + +/** + * Template preprocess for apachesolr search results. + * + * We need to add additional entity/bundle-based templates + */ +function apachesolr_search_preprocess_search_result(&$variables) { + // If this search result is coming from our module, we want to improve the + // template potential to make life easier for themers. + if ($variables['module'] == 'apachesolr_search') { + $result = $variables['result']; + if (!empty($result['entity_type'])) { + $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'] . '__' . $result['entity_type']; + if (!empty($result['bundle'])) { + $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'] . '__' . $result['entity_type'] . '__' . $result['bundle']; + } + } + } +} + +function apachesolr_search_preprocess_search_results(&$variables) { + // Initialize variables + $env_id = NULL; + + // If this is a solr search, expose more data to themes to play with. + if ($variables['module'] == 'apachesolr_search') { + // Fetch our current query + if (!empty($variables['search_page']['env_id'])) { + $env_id = $variables['search_page']['env_id']; + } + $query = apachesolr_current_query($env_id); + + if ($query) { + $variables['query'] = $query; + $variables['response'] = apachesolr_static_response_cache($query->getSearcher()); + } + if (empty($variables['response'])) { + $variables['description'] = ''; + return NULL; + } + $total = $variables['response']->response->numFound; + $params = $variables['query']->getParams(); + + $variables['description'] = t('Showing items @start through @end of @total.', array( + '@start' => $params['start'] + 1, + '@end' => $params['start'] + $params['rows'] - 1, + '@total' => $total, + )); + // Redefine the pager if it was missing + pager_default_initialize($total, $params['rows']); + $variables['pager'] = theme('pager', array('tags' => NULL)); + + // Add template hints for environments + if (!empty($env_id)) { + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $env_id; + // Add template hints for search pages + if (!empty($variables['search_page']['page_id'])) { + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $variables['search_page']['page_id']; + // Add template hints for both + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $env_id . '__' . $variables['search_page']['page_id']; + } + } + } +} + +/** + * Implements hook_apachesolr_environment_delete(). + */ +function apachesolr_search_apachesolr_environment_delete($server) { + db_update('apachesolr_search_page') + ->fields(array( + 'env_id' => '', + )) + ->condition('env_id', $server['env_id']) + ->execute(); + apachesolr_environment_variable_del($server['env_id'], 'apachesolr_search_show_facets'); + apachesolr_environment_variable_del($server['env_id'], 'apachesolr_search_facet_pages'); + menu_rebuild(); +} + +function apachesolr_search_form_search_block_form_alter(&$form, $form_state) { + if (variable_get('search_default_module') == 'apachesolr_search') { + $form['#submit'][] = 'apachesolr_search_form_search_submit'; + } +} + +/** + * Default theme function for spelling suggestions. + */ +function theme_apachesolr_search_suggestions($variables) { + $output = '
'; + $output .= '
' . t('Did you mean') . '
'; + foreach ((array) $variables['links'] as $link) { + $output .= '
' . $link . '
'; + } + $output .= '
'; + return $output; +} + +/** + * Added form submit function to retain filters. + * + * @see apachesolr_search_form_search_form_alter() + */ +function apachesolr_search_form_search_submit($form, &$form_state) { + $fv = $form_state['values']; + // Replace keys with their rawurlencoded value + if (isset($fv['search_block_form'])) { + $raw_keys = str_replace("/","%2f",$fv['search_block_form']); + $form_state['redirect'] = str_replace($fv['search_block_form'], $raw_keys, $form_state['redirect']); + } +} + +/** + * Implements hook_form_[form_id]_alter(). + * + * Rebuild (empty) the spellcheck dictionary when the index is deleted.. + */ +function apachesolr_search_form_apachesolr_delete_index_confirm_alter(&$form, $form_state) { + $form['submit']['#submit'][] = 'apachesolr_search_build_spellcheck'; +} + +/** + * submit function for the delete_index form. + * + */ +function apachesolr_search_build_spellcheck($form, &$form_state) { + try { + $solr = apachesolr_get_solr(); + $params['spellcheck'] = 'true'; + $params['spellcheck.build'] = 'true'; + $response = $solr->search('solr', 0, 0, $params); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +/** + * Implements hook_form_[form_id]_alter(). + * + * Adds settings to show facet blocks on non-search pages. + */ +function apachesolr_search_form_facetapi_realm_settings_form_alter(&$form, &$form_state) { + if ('apachesolr' == $form['#facetapi']['adapter']->getId() && 'block' == $form['#facetapi']['realm']['name']) { + // Gets the environment ID from the searcher, stores in #facetapi property. + $env_id = ltrim(strstr($form['#facetapi']['adapter']->getSearcher(), '@'), '@'); + + $show_facets = apachesolr_environment_variable_get($env_id, 'apachesolr_search_show_facets', 0); + $facet_pages = apachesolr_environment_variable_get($env_id, 'apachesolr_search_facet_pages', ''); + + $form['#facetapi']['env_id'] = $env_id; + + $form['apachesolr_search_show_facets'] = array( + '#type' => 'checkbox', + '#title' => t('Show facets on non-search pages.'), + '#default_value' => $show_facets, + '#weight' => '-10', + ); + + $form['apachesolr_search_facet_pages'] = array( + '#title' => t('Non-search paths'), + '#type' => 'textarea', + '#default_value' => $facet_pages, + '#weight' => '-10', + '#dependency' => array( + 'edit-apachesolr-search-show-facets' => array(1), + ), + ); + + $form['#submit'][] = 'apachesolr_search_facetapi_realm_settings_form_submit'; + } +} + +/** + * Form submission handler for facetapi_realm_settings_form(). + */ +function apachesolr_search_facetapi_realm_settings_form_submit(&$form, &$form_state) { + $env_id = $form['#facetapi']['env_id']; + + // Adds the settings to the array keyed by environment ID, saves variables. + $show_facets = $form_state['values']['apachesolr_search_show_facets']; + $facet_pages = $form_state['values']['apachesolr_search_facet_pages']; + if ($show_facets) { + apachesolr_environment_variable_set($env_id, 'apachesolr_search_show_facets', $show_facets); + } + else { + // Due to performance reasons, we delete it from the vars so that our init + // process can react on environments that hae it set and not unset. + // See apachesolr_search_init(). + apachesolr_environment_variable_del($env_id, 'apachesolr_search_show_facets'); + } + apachesolr_environment_variable_set($env_id, 'apachesolr_search_facet_pages', $facet_pages); +} + +/** + * Implements hook_theme(). + */ +function apachesolr_search_theme() { + return array( + /** + * Shows the facets in blocks in the search result area + */ + 'apachesolr_search_browse_blocks' => array( + 'render element' => 'content', + ), + /** + * Shows the search snippet + */ + 'apachesolr_search_snippets' => array( + 'variables' => array('doc' => NULL, 'snippets' => array()), + ), + /** + * Shows a message when the search does not return any result + */ + 'apachesolr_search_noresults' => array( + 'variables' => array(), + ), + /** + * Shows a list of suggestions + */ + 'apachesolr_search_suggestions' => array( + 'variables' => array('links' => NULL), + ), + /** + * Shows a list of results (docs) in content recommendation block + */ + 'apachesolr_search_mlt_recommendation_block' => array( + 'variables' => array('docs' => NULL, 'delta' => NULL), + ), + ); +} + +/** + * Implements hook_theme_registry_alter(). + */ +function apachesolr_search_theme_registry_alter(&$theme_registry) { + if (isset($theme_registry['search_results'])) { + $theme_registry['search_results']['variables']['search_page'] = NULL; + } +} + +/** + * Theme the highlighted snippet text for a search entry. + * + * @param array $vars + * + */ +function theme_apachesolr_search_snippets($vars) { + $result = ''; + if (is_array($vars['snippets'])) { + $snippets = $vars['snippets']; + if (isset($snippets['content'])) { + $result .= implode(' ... ', $snippets['content']); + unset($snippets['content']); + } + if (isset($snippets['teaser'])) { + $result .= (strlen($result) > 0) ? ' ... ' : ''; + $result .= implode(' ... ', $snippets['teaser']); + unset($snippets['teaser']); + } + if (count($snippets)) { + $result .= (strlen($result) > 0) ? ' ... ' : ''; + foreach ($snippets as $snippet) { + $result .= implode(' ... ', $snippet); + } + } + } + return $result . ' ...'; +} + +/** + * Brief message to display when no results match the query. + * + * @see search_help() + */ +function theme_apachesolr_search_noresults() { + return t('
    +
  • Check if your spelling is correct, or try removing filters.
  • +
  • Remove quotes around phrases to match each word individually: "blue drop" will match less than blue drop.
  • +
  • You can require or exclude terms using + and -: big +blue drop will require a match on blue while big blue -drop will exclude results that contain drop.
  • +
'); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/apachesolr_search.pages.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/apachesolr_search.pages.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,128 @@ + 'container', + '#attributes' => array('class' => array('container-inline')), + ); + $form['basic']['keys'] = array( + '#type' => 'textfield', + '#title' => t('Enter terms'), + '#default_value' => $keys, + '#size' => 20, + '#maxlength' => 255, + ); + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => t('Search'), + ); + + $form['basic']['get'] = array( + '#type' => 'hidden', + '#default_value' => json_encode(array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1, 'retain-filters' => 1))), + ); + + $fq = NULL; + + if (apachesolr_has_searched($search_page['env_id'])) { + $query = apachesolr_current_query($search_page['env_id']); + // We use the presence of filter query params as a flag for the retain filters checkbox. + $fq = $query->getParam('fq'); + } + + if ($fq || isset($form_state['input']['retain-filters'])) { + $form['basic']['retain-filters'] = array( + '#type' => 'checkbox', + '#title' => t('Retain current filters'), + '#default_value' => (int) !empty($_GET['retain-filters']), + ); + } + + return $form; +} + +/** + * Processes apachesolr_search_custom_page_search_form submissions. + */ +function apachesolr_search_custom_page_search_form_submit(&$form, &$form_state) { + $search_page = $form['#search_page']; + $redirect = $search_page['search_path']; + + // Also encode slashes so we don't get akward situations when obtaining the + // search key. We can't use drupal_encode_path because for "aestetic" reasons + // they don't encode slashes... + $redirect_value = rawurlencode($form_state['values']['keys']); + + if (strlen($form_state['values']['keys'])) { + $redirect .= '/' . $redirect_value; + } + + $get = array(); + if (isset($form_state['values']['get'])) { + $get = json_decode($form_state['values']['get'], TRUE); + } + if (!empty($form_state['values']['retain-filters'])) { + // Add our saved values + $get['retain-filters'] = '1'; + } + else { + // Remove all filters + if (!empty($search_page['settings']['apachesolr_search_allow_user_input'])) { + unset($get['fq']); + } + if (module_exists('facetapi')) { + unset($get['f']); + } + } + + // Add the query values into the redirect. + $form_state['redirect'] = array($redirect, array('query' => $get)); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/drush/apachesolr.drush.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/drush/apachesolr.drush.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,642 @@ + 'apachesolr_drush_solr_delete_index', + 'description' => dt('Deletes the content from the index. Can take content types as parameters.'), + 'arguments' => array( + 'types' => dt('Optional. A space delimited list of content types to be deleted from the index.'), + ), + 'options' => array( + 'environment-id' => 'The environment ID', + ), + 'examples' => array( + 'drush solr-delete-index node' => 'Delete all node content from the index.', + 'drush solr-delete-index node:article' => 'Delete all content of the article content type from the index.', + 'drush solr-delete-index node:article node:blog' => 'Delete all content of the article and blog content types from the index.', + ), + ); + $items['solr-mark-all'] = array( + 'callback' => 'apachesolr_drush_solr_mark_for_reindex', + 'description' => dt('Marks content for reindexing. Can take content types as parameters.'), + 'arguments' => array( + 'types' => dt('Optional. A space delimited list of content types to be marked for reindexing.'), + ), + 'options' => array( + 'environment-id' => 'The environment ID', + ), + ); + $items['solr-index'] = array( + 'callback' => 'apachesolr_drush_solr_index', + 'description' => dt('Reindexes content marked for (re)indexing.'), + 'options' => array( + 'environment-id' => 'The environment ID', + 'limit' => 'The total number of documents to index', + ), + ); + $items['solr-get-last-indexed'] = array( + 'callback' => 'apachesolr_drush_solr_get_last_indexed', + 'description' => dt('Get the ID of the last document indexed.'), + 'arguments' => array( + 'environment-id' => dt('Optional. The environment ID, uses the default if not passed.'), + 'entity-type' => dt('Optional. The machine name of the entity, defaults to "node".'), + ), + ); + $items['solr-get-next-indexed'] = array( + 'callback' => 'apachesolr_drush_solr_get_next_indexed', + 'description' => dt('Get the ID of the next document to be indexed.'), + 'arguments' => array( + 'environment-id' => dt('Optional. The environment ID, uses the default if not passed.'), + 'entity-type' => dt('Optional. The machine name of the entity, defaults to "node".'), + ), + ); + $items['solr-search'] = array( + 'callback' => 'apachesolr_drush_solr_search', + 'description' => dt('Search the site for keywords using Apache Solr'), + 'arguments' => array( + 'keywords' => dt('One or more keywords, separated by spaces.'), + ), + ); + $items['solr-get-env-id'] = array( + 'callback' => 'apachesolr_drush_solr_get_env_id', + 'description' => dt('Get the default Apache Solr environment ID, or all IDs and names'), + 'arguments' => array(), + 'options' => array( + 'all' => array( + 'description' => 'List all environment IDs', + ), + ), + ); + $items['solr-get-env-name'] = array( + 'callback' => 'apachesolr_drush_solr_get_env_name', + 'description' => dt('Get the Apache Solr environment name.'), + 'options' => array( + 'id' => array( + 'description' => 'Apache Solr environment ID to use (uses the default environment if not specified)', + ), + ), + ); + $items['solr-get-env-url'] = array( + 'callback' => 'apachesolr_drush_solr_get_env_url', + 'description' => dt('Get the Apache Solr environment url.'), + 'options' => array( + 'id' => array( + 'description' => 'Apache Solr environment ID to use (uses the default environment if not specified)', + ), + ), + ); + $items['solr-set-env-url'] = array( + 'callback' => 'apachesolr_drush_solr_set_env_url', + 'description' => dt('Set the url for an Apache Solr environment.'), + 'arguments' => array( + 'url' => dt('Apache Solr server url string.'), + ), + 'required-arguments' => TRUE, + 'options' => array( + 'id' => array( + 'description' => 'Apache Solr environment ID to use (uses the default environment if not specified)', + ), + ), + ); + $items['solr-variable-get'] = array( + 'description' => 'Get a list of Apache Solr environment variable names and values.', + 'arguments' => array( + 'name' => 'A string to filter the variables by. Variables that have any part of their name matching the string will b listed.', + ), + 'examples' => array( + 'drush solr-vget' => 'List all variables and values.', + 'drush solr-vget user' => 'List all variables containing the string "user".', + ), + 'options' => array( + 'id' => 'Apache Solr environment ID to use (uses the default environment if not specified)', + 'format' => 'Format to output the object. Use "print_r" for print_r (default), "export" for var_export, and "json" for JSON.', + 'pipe' => 'A synonym for --format=export. Useful for pasting into code.', + ), + 'aliases' => array('solr-vget'), + ); + $items['solr-variable-set'] = array( + 'description' => "Set an Apache Solr environment variable.", + 'arguments' => array( + 'name' => 'The name of a variable or the first few letters of its name.', + 'value' => 'The value to assign to the variable. Use \'-\' to read the object from STDIN.', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'id' => 'Apache Solr environment ID to use (uses the default environment if not specified)', + 'yes' => 'Skip confirmation if only one variable name matches.', + 'always-set' => 'Always skip confirmation.', + 'format' => 'Format to parse the object. Use "auto" to detect format from value (default), "string", "integer" or "boolean" for corresponding primitive type, and "json" for JSON.', + ), + 'examples' => array( + 'drush solr-vset --yes apachesolr_read_only 1' => 'Set the apachesolr_read_only variable to 1. Skip confirmation if variable already exists.', + 'drush solr-vset pr TRUE' => 'Choose from a list of variables beginning with "pr" to set to (bool)true.', + 'php -r "print json_encode(array(\'drupal\', \'simpletest\'));" | drush solr-vset --format=json project_dependency_excluded_dependencies -'=> 'Set a variable to a complex value (e.g. array)', + ), + 'aliases' => array('solr-vset'), + ); + $items['solr-variable-delete'] = array( + 'description' => "Delete an Apache Solr environment variable.", + 'arguments' => array( + 'name' => 'The name of a variable or the first few letters of its name.', + ), + 'required-arguments' => TRUE, + 'options' => array( + 'id' => array( + 'description' => 'Apaches Solr environment ID to use (uses the default environment if not specified)', + ), + 'yes' => 'Skip confirmation if only one variable name matches.', + 'exact' => 'Only delete the one variable that exactly matches the specified name.', + ), + 'examples' => array( + 'drush solr-vdel apachesolr_read_only --id=solr2' => 'Delete the apachesolr_read_only variable for the solr2 environment.', + 'drush solr-vdel apa' => 'Choose from a list of variables beginning with "u" to delete.', + 'drush solr-vdel -y --exact apachesolr_read_only' => 'Delete variable, skipping confirmation.', + ), + 'aliases' => array('solr-vdel'), + ); + return $items; +} + +/** + * Implements hook_drush_help(). + * + * This function is called whenever a drush user calls + * 'drush help ' + * + * @param string $section + * A string with the help section (prepend with 'drush:') + * + * @return string + * A string with the help text for your command. + */ +function apachesolr_drush_help($section) { + switch ($section) { + case 'drush:solr-delete-index': + return dt("Used without parameters, this command deletes the entire Solr index. + Used with parameters for content type, it deletes just the content types that are specified. + After the index has been deleted, all content will be indexed again on future cron runs."); + case 'drush:solr-mark-all': + return dt("Used without parameters, this command marks all of the content in the Solr index for + reindexing. Used with parameters for content type, it marks just the content types that are specified. + Reindexing is different than deleting as the content is still searchable while it is in queue to be reindexed. + Reindexing is done on future cron runs."); + case 'drush:solr-index': + return dt("Reindexes content marked for (re)indexing. If you want to reindex all content or content + of a specific type, use solr-reindex first to mark that content."); + case 'drush:solr-search': + return dt('Executes a search against the site\'s Apache Solr search index and returns the results.'); + case 'error:APACHESOLR_ENV_ID_ERROR': + return dt('Not a valid environment ID.'); + } + return ''; +} + +/** + * Selectively delete content from the apachesolr index. + * + * Each argument is a filter on what to delete from the index. + * They are of the form entity (to delete all content of that + * entity) or entity:bundle (to delete all content of that + * bundle). + */ +function apachesolr_drush_solr_delete_index() { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $args = func_get_args(); + $env_id = drush_get_option('environment-id'); + if (empty($env_id)) { + $env_id = apachesolr_default_environment(); + } + + if (count($args) > 0) { + foreach ($args as $type) { + $parts = explode(':', $type); + if (count($parts) === 1) { + apachesolr_index_delete_index($env_id, $type); + } + elseif (count($parts) == 2) { + apachesolr_index_delete_index($env_id, $parts[0], $parts[1]); + } + else { + drush_set_error('The syntax for each type is either entity or entity:bundle'); + } + } + } + else { + apachesolr_index_delete_index($env_id); + } + + drush_print(t('Deleted the Solr index')); +} + +/** + * Mark all of a specific environment id for reindexing + */ +function apachesolr_drush_solr_mark_for_reindex() { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $args = func_get_args(); + $env_id = drush_get_option('environment-id'); + if (empty($env_id)) { + $env_id = apachesolr_default_environment(); + } + if (count($args) > 0) { + foreach ($args as $type) { + apachesolr_index_mark_for_reindex($env_id, $type); + } + } + else { + apachesolr_index_mark_for_reindex($env_id); + } + drush_print(t('Marked content for reindexing')); +} + +/** + * Index all the items in the queue using a batch command + */ +function apachesolr_drush_solr_index() { + module_load_include('inc', 'apachesolr', 'apachesolr.admin'); + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $env_id = drush_get_option('environment-id'); + if (empty($env_id)) { + $env_id = apachesolr_default_environment(); + } + $total_limit = intval(drush_get_option('limit')); + apachesolr_index_batch_index_remaining($env_id, $total_limit); + drush_backend_batch_process(); +} + +/** + * Get the last indexed document + * + * @param string $env_id + * @param string $entity_type + */ +function apachesolr_drush_solr_get_last_indexed($env_id = NULL, $entity_type = 'node') { + if (NULL === $env_id) { + $env_id = apachesolr_default_environment(); + } + $return = apachesolr_get_last_index_position($env_id, $entity_type); + drush_print($return['last_entity_id']); +} + +function apachesolr_drush_solr_get_next_indexed($env_id = NULL, $entity_type = 'node') { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + if (NULL === $env_id) { + $env_id = apachesolr_default_environment(); + } + $return = apachesolr_index_get_entities_to_index($env_id, $entity_type, 1); + $output = (isset($return[0]->entity_id)) ? $return[0]->entity_id : '0'; + drush_print($output); +} + +/** + * Search the solr index using Drush + */ +function apachesolr_drush_solr_search() { + $args = func_get_args(); + $keys = implode(' ', $args); + foreach (apachesolr_search_search_execute($keys) as $result) { + $output = $result['fields']['path']; + if(isset($result['user']) && isset($result['node']->is_uid)) { + $output .= ' ' . dt('by @name (user/@uid)', array('@name' => strip_tags($result['user']), '@uid' => $result['node']->is_uid)); + } + $output .= "\n"; + $output .= dt('title: ') . $result['title'] . "\n"; + $output .= trim(preg_replace('/\s+/', ' ', strip_tags($result['snippet']))) . "\n\n"; + drush_print($output); + } +} + +/** + * Get all the environments (using option all) or get the default environment id + */ +function apachesolr_drush_solr_get_env_id() { + $all = drush_get_option('all'); + + if ($all) { + foreach (apachesolr_load_all_environments() as $id => $env) { + drush_print(drush_format($env['name'], $id)); + } + } + else { + $solr_env_id = apachesolr_default_environment(); + drush_print($solr_env_id); + } +} + +/** + * Get the environment name based on the environment ID + * + * @print The environment name + * + * @return mixed APACHESOLR_ENV_ID_ERROR + * Only return error if the environment can't be found + */ +function apachesolr_drush_solr_get_env_name() { + $env_id = drush_get_option('id', apachesolr_default_environment()); + try { + $environment = _apachesolr_drush_environment_load_and_validate($env_id); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + drush_print($environment['name']); +} + +/** + * Get the environment url based on the environment ID + * + * @print The environment url + * + * @return mixed APACHESOLR_ENV_ID_ERROR + * Only return error if the environment can't be found + */ +function apachesolr_drush_solr_get_env_url() { + $env_id = drush_get_option('id', apachesolr_default_environment()); + try { + $environment = _apachesolr_drush_environment_load_and_validate($env_id); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + drush_print($environment['url']); +} + +/** + * Set the environment url based on the environment ID + * + * @param $url + * + * @return mixed APACHESOLR_ENV_ID_ERROR + * Only return error if the environment can't be found + */ +function apachesolr_drush_solr_set_env_url($url) { + $env_id = drush_get_option('id', apachesolr_default_environment()); + try { + $environment = _apachesolr_drush_environment_load_and_validate($env_id); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + $environment['url'] = $url; + apachesolr_environment_save($environment); +} + +/** + * Command callback. + * + * List your site's variables. + * much of it copied from drush core + * + * @param string $arg_name + * + * @return array|mixed Could be the variable or a drush error + */ +function drush_apachesolr_solr_variable_get($arg_name = NULL) { + $output = NULL; + + $found = array(); + + $env_id = drush_get_option('id', apachesolr_default_environment()); + try { + $found = _apachesolr_drush_variable_like($env_id, $arg_name); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + + foreach ($found as $name => $value) { + drush_print_pipe(drush_format($value, $name, 'export')); + drush_print(drush_format($value, $name)); + } + + if (empty($found)) { + return drush_set_error('DRUSH_VARIABLE_ERROR', 'No matching variable found.'); + } + else { + return $found; + } +} + +/** + * Command callback. + * Set a variable. + * + * @param string $arg_name + * @param mixed $value + * + * @return mixed + */ +function drush_apachesolr_solr_variable_set($arg_name, $value) { + $args = func_get_args(); + + if (!isset($value)) { + return drush_set_error('DRUSH_VARIABLE_ERROR', dt('No value specified.')); + } + + $env_id = drush_get_option('id', apachesolr_default_environment()); + try { + $found = _apachesolr_drush_variable_like($env_id, $arg_name, TRUE); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + + $options[] = "$arg_name ". dt('(new variable)'); + $match = isset($found[$arg_name]); + if (!$match && $found) { + $options = array_merge($options, array_keys($found)); + } + + if ($value == '-') { + $value = stream_get_contents(STDIN); + } + + // If the value is a string (usual case, unless we are called from code), + // then format the input + if (is_string($value)) { + $value = _apachesolr_drush_variable_format($value, drush_get_option('format', 'auto')); + } + + // Format the output for display + if (is_array($value)) { + $display = "\n" . var_export($value, TRUE); + } + elseif (is_integer($value)) { + $display = $value; + } + elseif (is_bool($value)) { + $display = $value ? "TRUE" : "FALSE"; + } + else { + $display = '"' . $value . '"'; + } + + $name = NULL; + if (drush_get_option('always-set', FALSE) || $match) { + $name = $arg_name; + } + else { + $choice = drush_choice($options, 'Enter a number to choose which variable to set.'); + if ($choice !== FALSE) { + $name = ($choice == 0) ? $arg_name : $options[$choice]; + } + } + if ($name) { + drush_op('apachesolr_environment_variable_set', $env_id, $name, $value); + drush_log(dt('!name was set to !value', array('!name' => $name, '!value' => $display)), 'success'); + } +} + +/** + * + * Format a specific variable + * + * @param $value + * @param $format + * + * @return bool|int|string + */ +function _apachesolr_drush_variable_format($value, $format) { + if ($format == 'auto') { + if (is_numeric($value)) { + $format = 'integer'; + } + elseif (($value == 'TRUE') || ($value == 'FALSE')) { + $format = 'bool'; + } + } + + // Now, we parse the object. + switch ($format) { + case 'integer': + $value = (integer)$value; + break; + + case 'bool': + case 'boolean': + if ($value == 'TRUE') { + $value = TRUE; + } + elseif ($value == 'FALSE') { + $value = FALSE; + } + else { + $value = (bool)$value; + } + break; + + case 'json': + $value = drush_json_decode($value); + break; + } + return $value; +} + +/** + * Command callback. + * Delete a variable. + * @param $arg_name + * + * @return string + */ +function drush_apachesolr_solr_variable_delete($arg_name) { + + $env_id = drush_get_option('id', apachesolr_default_environment()); + // Look for similar variable names. + try { + $found = _apachesolr_drush_variable_like($env_id, $arg_name, TRUE); + } + catch (Exception $e) { + return drush_set_error('APACHESOLR_ENV_ID_ERROR', $e->getMessage()); + } + drush_log(dt('Using environment ID "!env_id"', array('!env_id' => $env_id)), 'success'); + $options = array_keys($found); + + if (drush_get_option('exact', FALSE)) { + $options = isset($found[$arg_name]) ? array($arg_name) : array(); + } + + if (empty($options)) { + drush_print(dt('!name not found.', array('!name' => $arg_name))); + return ''; + } + + $name = NULL; + if ((count($options) == 1) && drush_get_context('DRUSH_AFFIRMATIVE')) { + $name = $arg_name; + } + else { + $choice = drush_choice($options, 'Enter a number to choose which variable to delete.'); + if ($choice !== FALSE) { + $name = $options[$choice]; + } + } + if ($name) { + drush_op('apachesolr_environment_variable_del', $env_id, $name); + drush_log(dt('!choice was deleted.', array('!choice' => $name)), 'success'); + } +} + +/** + * Load an environment from an id and validate the result. + * + * @param string $env_id + * + * @return array $environment + * @throws Exception + */ +function _apachesolr_drush_environment_load_and_validate($env_id) { + $environment = apachesolr_environment_load($env_id); + if (!$environment) { + throw new Exception(dt('!env_id is not a valid environment ID.', array('!env_id' => $env_id))); + } + drush_log(dt('Using environment ID: "!env_id"', array('!env_id' => $env_id)), 'success'); + return $environment; +} + +/** + * Search for similar variable names. + * + * @param string $env_id + * @param string $arg + * @param bool|string $starts_with + * + * @throws Exception + * + * @return array $variable + * Only return it if found + */ +function _apachesolr_drush_variable_like($env_id, $arg = NULL, $starts_with = FALSE) { + $found = array(); + $environment = _apachesolr_drush_environment_load_and_validate($env_id); + if (!isset($arg)) { + return $environment['conf']; + } + if ($starts_with) { + $pattern = "/^{$arg}/i"; + } + else { + $pattern = "/{$arg}/i"; + } + foreach ($environment['conf'] as $name => $value) { + // Find all variable that start with $arg. + if (preg_match($pattern, $name)) { + $found[$name] = $value; + } + } + return $found; +} + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/plugins/facetapi/adapter.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/plugins/facetapi/adapter.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,151 @@ +getSearcher(), '@'), '@'); + $path .= '/' . $env_id . '/facets'; + // Add the realm name to the path if it is not the first one in the list. + if (key(facetapi_get_realm_info()) != $realm_name) { + $path .= '/' . $realm_name; + } + } + return $path; + } + + /** + * Allows the backend to initialize its query object before adding the facet + * filters. + * + * @param mixed $query + * The backend's native object. + */ + function initActiveFilters($query) { + $enabled_facets = facetapi_get_enabled_facets($this->info['name']); + if ($enabled_facets) { + $query->addParam('facet', 'true'); + $query->addParam('facet.sort', 'count'); + $query->addParam('facet.mincount', '1'); + } + } + + /** + * Returns a boolean flagging whether $this->_searcher executed a search. + */ + public function searchExecuted() { + // Initial check - has ANY solr query run in our environment. + $env_id = $this->info['instance']; + $this_has_searched = apachesolr_has_searched($env_id); + // Secondary check - do we have results for this searcher? + $this_has_searched = $this_has_searched && apachesolr_static_response_cache($this->getSearcher()); + return $this_has_searched; + } + + /** + * Suppress output of the realm + * + * @param string $realm_name + * + * @return bool $flag + * Returns if it was suppressed or not + */ + public function suppressOutput($realm_name) { + $flag = FALSE; + if ($realm_name == 'block') { + $env_id = $this->info['instance']; + $flag = apachesolr_suppress_blocks($env_id); + } + return $flag || !$this->searchExecuted(); + } + + /** + * Returns the search keys. + * + * @return string + */ + public function getSearchKeys() { + if (NULL === $this->keys) { + $env_id = $this->info['instance']; + if ($query = apachesolr_current_query($env_id)) { + return $query->getParam('q'); + } + } + else { + return $this->keys; + } + return FALSE; + } + + /** + * Returns the search path. + * + * @return string + * A string containing the search path. + * + * @todo D8 should provide an API function for this. + */ + public function getSearchPath() { + $env_id = $this->info['instance']; + $query = apachesolr_current_query($env_id); + if (!$query || (NULL === $this->searchPath && NULL === $query->getPath())) { + if ($path = module_invoke($this->info['module'] . '_search', 'search_info')) { + $this->searchPath = 'search/' . $path['path']; + if (!isset($_GET['keys']) && ($keys = $this->getSearchKeys())) { + $this->searchPath .= '/' . $keys; + } + } + } + if (!$query || NULL === $query->getPath()) { + return $this->searchPath; + } + else { + return $query->getPath(); + } + + } + + /** + * Returns the number of total results found for the current search. + * + * @return bool|int + * Number of results or false if no search response was found + */ + public function getResultCount() { + $response = apachesolr_static_response_cache($this->getSearcher()); + if ($response) { + return $response->response->numFound; + } + return FALSE; + } + + /** + * Allows for backend specific overrides to the settings form. + * + * @param array $form + * @param array $form_state + */ + public function settingsForm(&$form, &$form_state) { + $form['#validate'][] = 'apachesolr_facet_form_validate'; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_date.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_date.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,185 @@ +getDateRange($query); + if (empty($date_range)) { + return NULL; + } + list($start, $end, $gap) = $date_range; + $query->addParam('facet.date', $this->facet['field']); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.start', $start); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.end', $end); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.gap', $gap); + + // Adds "hard limit" parameter to prevent too many return values. + $settings = $this->adapter->getFacet($this->facet)->getSettings(); + $limit = empty($settings->settings['hard_limit']) ? 20 : (int) $settings->settings['hard_limit']; + $query->addParam('f.' . $this->facet['field'] . '.facet.limit', $limit); + + $active = $this->adapter->getActiveItems($this->facet); + // Date filters don't support OR operator. + foreach ($active as $value => $item) { + $query->addFilter($this->facet['field'], $value); + } + } + + /** + * Gets the range of dates we are using. + * + * @param DrupalSolrQueryInterface $query + * A SolrBaseQuery object. + * + * @return bool|array + * An array containing the gap and range information or false if not present + */ + function getDateRange(DrupalSolrQueryInterface $query) { + $return = NULL; + $gap = NULL; + + // Attempts to get next gap from passed date filters. + foreach ($this->adapter->getActiveItems($this->facet) as $item) { + if ($gap = facetapi_get_date_gap($item['start'], $item['end'])) { + $next_gap = facetapi_get_next_date_gap($gap, FACETAPI_DATE_SECOND); + if ($next_gap == $gap) { + $next_gap = NULL; + return NULL; + } + $return = array( + "{$item['start']}/$next_gap", + "{$item['end']}+1$next_gap/$next_gap", + "+1$next_gap", + ); + } + } + + // If no filters were passed, get default range. + if (NULL === $return) { + + // Builds SQL that gets minimum and maximum values from node table. + $minimum = $maximum = FALSE; + if ($this->facet['min callback'] && is_callable($this->facet['min callback'])) { + $minimum = $this->facet['min callback']($this->facet); + } + if ($this->facet['max callback'] && is_callable($this->facet['max callback'])) { + $maximum = $this->facet['max callback']($this->facet); + } + + // Gets the default gap. + //$gap = FACETAPI_DATE_YEAR; + if ($minimum && $maximum) { + $gap = facetapi_get_timestamp_gap($minimum, $maximum); + $minimum = facetapi_isodate($minimum, $gap); + $maximum = facetapi_isodate($maximum, $gap); + $return = array( + "$minimum/$gap", + "$maximum+1$gap/$gap", + "+1$gap", + ); + } + } + // Returns the range information. + return $return; + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + + // Initializes build and gets static response. + if (!$response = apachesolr_static_response_cache($this->adapter->getSearcher())) { + return array(); + } + $build = array(); + + // Gets total number of documents matched in search. + $total = $response->response->numFound; + + // Gets the active date facets, starts to builds the "parent - child" + // relationships. + $parent = NULL; + foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) { + // Builds the raw facet "value", the count for selected items will be the + // total number of rows returned in the query. + $build[$value] = array('#count' => $total); + + // If there is a previous item, there is a parent, uses a reference so the + // arrays are populated when they are updated. + if (NULL !== $parent) { + $build[$parent]['#item_children'][$value] = &$build[$value]; + $build[$value]['#item_parents'][$parent] = $parent; + } + + // Stores the last value iterated over. + $parent = $value; + } + + // Gets raw facet data from the Solr server. + if (isset($response->facet_counts->facet_dates) && isset($response->facet_counts->facet_dates->{$this->facet['field']})) { + $raw_data = (array) $response->facet_counts->facet_dates->{$this->facet['field']}; + } + else { + $raw_data = array(); + } + //$end = (!empty($raw_data['end'])) ? $raw_data['end'] : ''; + //$start = (!empty($raw_data['start'])) ? $raw_data['start'] : ''; + $gap = (!empty($raw_data['gap'])) ? $raw_data['gap'] : ''; + + // We cannot list anything below a minute (range of 00 seconds till 59 + // seconds. Milliseconds are not possible) + if ($gap != "+1SECOND") { + unset($raw_data['start']); + unset($raw_data['end']); + unset($raw_data['gap']); + + // Treat each date facet as a range start, and use the next date facet + // as range end. Use 'end' for the final end. + $previous = NULL; + + // Builds facet counts object used by the server. + foreach ($raw_data as $value => $count) { + if ($count) { + $from = $value; + $to = facetapi_isodate(strtotime($value . $gap)); + $new_value = '[' . $from . ' TO ' . $to . ']'; + $build[$new_value] = array('#count' => $count, '#active' => 0); + if (NULL !== $parent) { + $build[$parent]['#item_children'][$new_value] = &$build[$new_value]; + $build[$new_value]['#item_parents'][$parent] = $parent; + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_geo.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_geo.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,84 @@ +adapter->getFacet($this->facet)->getSettings(); + + $facet_distances = explode(',', $this->facet_options); + + $active_items = $this->adapter->getActiveItems($this->facet); + + if (empty($active_items)) { + $distance = $this->default_radius; + } + else { + $active_item = array_pop($active_items); + $distance = substr($active_item['value'], 1); + // Add current selected distance to have possibility to unselect it. + $facet_distances[] = 1; + } + + // Search center point. + $query->addParam('pt', $this->center_point); + + // Set location field name. + $query->addParam('sfield', $this->facet['field']); + $query->addParam('fq', '{!geofilt sfield=' . $this->facet['field'] . '}'); + + // Set search radius. + $query->addParam('d', $distance); + + // Set facets. + foreach ($facet_distances as $facet_option) { + $facet_distance = $distance * $facet_option; + $query->addParam('facet.query', '{!geofilt d=' . $facet_distance . ' key=d' . $facet_distance . '}'); + } + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + $build = array(); + if ($response = apachesolr_static_response_cache($this->adapter->getSearcher())) { + if (isset($response->facet_counts->facet_queries)) { + foreach ($response->facet_counts->facet_queries as $value => $count) { + // Skip zero results values. + if ($count > 0) { + $build[$value] = array('#count' => $count); + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_numeric_range.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_numeric_range.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,101 @@ +adapter->getFacet($this->facet)->getSettings(); + $active = $this->adapter->getActiveItems($this->facet); + + $singular_field_info = $this->facet['map options']; + $singular_field_info['multiple'] = FALSE; + $this->single_key = apachesolr_index_key($singular_field_info); + // See: http://wiki.apache.org/solr/StatsComponent + $query->addParam('stats', 'true'); + $query->addParam('stats.field', $this->single_key); + $query->addParam('stats.facet', $this->single_key); + // Range filters don't support OR operator. + foreach ($active as $value => $item) { + $query->addFilter($this->single_key, $value); + } + } + + /** + * Initializes the facet's build array. + * + * Any calls to this method need to be wrapped in a try-catch block. + * + * @return array + * The initialized render array. + */ + public function build() { + $build = array(); + if (!isset($this->single_key)) { + return $build; + } + + // Per key we save our statistics result + $cache = cache_get('stats_' . $this->single_key, 'cache_apachesolr'); + $stats_minmax = array(); + + if (!isset($cache->data)) { + // we need an additional query for the statistics of the field + // We can optionally specify a Solr object. + $solr = apachesolr_get_solr(); + + // We possibly need some caching for this query + $query_stats = apachesolr_drupal_query('apachesolr_stats', array(), '', '', $solr); + $query_stats->addParam('stats', 'true'); + $query_stats->addParam('stats.field', $this->single_key); + $query_stats->addParam('stats.facet', $this->single_key); + $response_stats = $query_stats->search(); + + if ($response_stats->response) { + $stats_minmax = $response_stats->stats->stats_fields->{$this->single_key}; + cache_set('stats_' . $this->single_key, $stats_minmax, 'cache_apachesolr'); + } + } + else { + // Set our statistics from the cache + $stats_minmax = $cache->data; + } + + if ($response = apachesolr_static_response_cache($this->adapter->getSearcher())) { + if (isset($response->stats->stats_fields->{$this->single_key})) { + $stats = (array) $response->stats->stats_fields->{$this->single_key}; + foreach ($stats as $key => $val) { + $build[$this->facet['field']]['#range_' . $key] = $val; + $build[$this->facet['field']]['#global_range_' . $key] = $stats_minmax->$key; + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_term.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/plugins/facetapi/query_type_term.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,101 @@ +adapter->getFacet($this->facet)->getSettings(); + // Adds the operator parameter. + $operator = $settings->settings['operator']; + $ex = (FACETAPI_OPERATOR_OR != $operator) ? '' : "{!ex={$this->facet['field']}}"; + $query->addParam('facet.field', $ex . $this->facet['field']); + + if (!empty($settings->settings['facet_missing'])) { + $query->addParam('f.' . $this->facet['field'] . '.facet.missing', 'true'); + } + // Adds "hard limit" parameter to prevent too many return values. + $limit = empty($settings->settings['hard_limit']) ? 20 : (int) $settings->settings['hard_limit']; + $query->addParam('f.' . $this->facet['field'] . '.facet.limit', $limit); + + // Adds "facet mincount" parameter to limit the number of facets. + if (isset($settings->settings['facet_mincount'])) { + $count = $settings->settings['facet_mincount']; + $query->addParam('f.' . $this->facet['field'] . '.facet.mincount', $count); + } + + $active = $this->adapter->getActiveItems($this->facet); + + // Adds filter based on the operator. + if (FACETAPI_OPERATOR_OR != $operator) { + foreach ($active as $value => $item) { + // Handle facet missing: + if ($value == '_empty_' && !empty($settings->settings['facet_missing'])) { + $query->addFilter($this->facet['field'], '[* TO *]', TRUE); + } + else { + $query->addFilter($this->facet['field'], $value); + } + } + } + else { + // OR facet. + $local = "tag={$this->facet['field']}"; + $values = array_keys($active); + if ($values) { + // Quote any values that have white space or colons. + foreach ($values as &$v) { + if (preg_match('/[:\s]/', $v)) { + $v = '"' . $v . '"'; + } + } + $query->addFilter($this->facet['field'], '(' . implode(' OR ', $values) . ')', FALSE, $local); + } + } + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + $build = array(); + if ($response = apachesolr_static_response_cache($this->adapter->getSearcher())) { + $settings = $this->adapter->getFacet($this->facet)->getSettings(); + if (isset($response->facet_counts->facet_fields->{$this->facet['field']})) { + $values = (array) $response->facet_counts->facet_fields->{$this->facet['field']}; + foreach ($values as $value => $count) { + // Facet missing may return 0 even if mincount is 1. + if (empty($settings->settings['facet_mincount']) || $count) { + $build[$value] = array('#count' => $count); + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/protwords.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/protwords.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,8 @@ +#----------------------------------------------------------------------- +# This file blocks words from being operated on by the stemmer and word delimiter. +& +< +> +' +" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/schema.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/schema.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + content + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/solrconfig.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-1.4/solrconfig.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,736 @@ + + + + + + ${solr.abortOnConfigurationError:true} + + + + + + + false + + 10 + + + + 32 + 2147483647 + 20000 + 1000 + 10000 + + + + + + + + + + + single + + + + + false + 32 + 4 + 2147483647 + 20000 + + + false + + + + + false + + 1 + + + + + + + + + + + + + + + + 2000 + 120000 + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + true + + + + + + + + 50 + + + 200 + + + + + + + + + solr 0 10 + rocks 0 10 + static newSearcher warming query from solrconfig.xml + + + + + + + fast_warm 0 10 + static firstSearcher warming query from solrconfig.xml + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + explicit + true + + + + + + + + + + + + + dismax + explicit + true + + + + + + + dismax + explicit + true + 0.01 + + content^2.0 + + 15 + + + 1 + *:* + + + true + content + 3 + true + + teaser + 256 + + + + + false + + true + false + + 1 + + + spellcheck + + + + + + + 1 + 1 + 3 + 15 + 20 + false + + + + + + + + + + + + textSpell + + + default + spell + ./spellchecker1 + true + + + jarowinkler + spell + + org.apache.lucene.search.spell.JaroWinklerDistance + ./spellchecker2 + true + + + + + + + + + + string + elevate.xml + + + + + + explicit + + + elevator + + + + + + + + + + + + + + + + + + + + + + standard + solrpingquery + all + + + + + + + admin-extra.html + scripts.conf + xslt/example.xsl + xslt/example_atom.xsl + xslt/example_rss.xsl + xslt/luke.xsl + + + + + + + explicit + true + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + 5 + + + + + + + + + + solr + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/protwords.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/protwords.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,8 @@ +#----------------------------------------------------------------------- +# This file blocks words from being operated on by the stemmer and word delimiter. +& +< +> +' +" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + content + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema_extra_fields.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema_extra_fields.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,23 @@ + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema_extra_types.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/schema_extra_types.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,30 @@ + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrconfig.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrconfig.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1583 @@ + + + + + + + + + ${solr.abortOnConfigurationError:true} + + + ${luceneVersion:LUCENE_35} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + 4 + + 32 + + + + 2147483647 + 100000 + 1000 + + + + + + + + + single + + + + + + + + + false + 32 + 10 + + + false + + + true + + + + + 1 + + 0 + + + + + + false + + + + + + + + + + + + + + + + 10000 + 120000 + + + + + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + solr rocks010 + + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + edismax + explicit + true + 0.01 + + ${pinkPony.timeAllowed:-1} + *:* + + + false + + true + false + + 1 + + + spellcheck + + + + + + + 1 + 1 + 3 + 15 + 20 + false + + ${mlt.timeAllowed:2000} + + + + + + + + + + + + + + + + + + + + + + text + true + ignored_ + + + true + links + ignored_ + + + + + + + + + + + + + + + + + + + + + + + + pinkPony + solrpingquery + + + all + + + + + + + explicit + true + + + + + + + ${enable.master:false} + commit + startup + ${confFiles} + + + ${enable.slave:false} + ${masterCoreUrl}/replication + ${pollTime:00:00:60} + + + + + + + + + false + false + 1 + + + spellcheck + + + + + + + + + + true + + + tvComponent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + + + elevator + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + + + + + + 5 + + + + + + + + + + + + + *:* + + + + + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrconfig_extra.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrconfig_extra.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,79 @@ + + + +textSpell + + + + + + default + content + spellchecker + + + + + + + + + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrcore.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-3.x/solrcore.properties Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,10 @@ +#solrcore.properties for this specific core +enable.master=false +enable.slave=false +pollTime=00:00:60 +masterCoreUrl=http://localhost:8983/solr +confFiles=schema.xml,mapping-ISOLatin1Accent.txt,protwords.txt,stopwords.txt,synonyms.txt,elevate.xml +mlt.timeAllowed=2000 +# You should not set your luceneVersion to anything lower then your Solr Version +luceneVersion=LUCENE_35 +pinkPony.timeAllowed=-1 diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/protwords.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/protwords.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,8 @@ +#----------------------------------------------------------------------- +# This file blocks words from being operated on by the stemmer and word delimiter. +& +< +> +' +" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id + + + content + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema_extra_fields.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema_extra_fields.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,23 @@ + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema_extra_types.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/schema_extra_types.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,30 @@ + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrconfig.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrconfig.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1575 @@ + + + + + + + + + ${solr.abortOnConfigurationError:true} + + + ${luceneVersion:LUCENE_35} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + 4 + + 32 + + + + 2147483647 + 100000 + 1000 + + + + + + + + + single + + + + + false + 32 + 10 + + + false + + + true + + + + + 1 + + 0 + + + + + + false + + + + + + + + + + + + + + + + 10000 + 120000 + + + + + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + solr rocks010 + + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + edismax + explicit + true + 0.01 + + ${pinkPony.timeAllowed:-1} + *:* + + + false + + true + false + + 1 + + + spellcheck + + + + + + + 1 + 1 + 3 + 15 + 20 + false + + ${mlt.timeAllowed:2000} + + + + + + + + + + + + + + + + + + + + + + text + true + ignored_ + + + true + links + ignored_ + + + + + + + + + + + + + + + + + + + + + + + + pinkPony + solrpingquery + + + all + + + + + + + explicit + true + + + + + + + ${enable.master:false} + commit + startup + ${confFiles} + + + ${enable.slave:false} + ${masterCoreUrl}/replication + ${pollTime:00:00:60} + + + + + + + + + false + false + 1 + + + spellcheck + + + + + + + + + + true + + + tvComponent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + + + elevator + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + + + + + + 5 + + + + + + + + + + + + + *:* + + + + + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrconfig_extra.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrconfig_extra.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,79 @@ + + + +textSpell + + + + + + default + content + spellchecker + + + + + + + + + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrcore.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/solr-conf/solr-4.x/solrcore.properties Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,10 @@ +#solrcore.properties for this specific core +enable.master=false +enable.slave=false +pollTime=00:00:60 +masterCoreUrl=http://localhost:8983/solr +confFiles=schema.xml,mapping-ISOLatin1Accent.txt,protwords.txt,stopwords.txt,synonyms.txt,elevate.xml +mlt.timeAllowed=2000 +# You should not set your luceneVersion to anything lower then your Solr Version +luceneVersion=LUCENE_40 +pinkPony.timeAllowed=-1 diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/task-check.png Binary file sites/all/modules/custom/solrconnect/task-check.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/Dummy_Solr.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/Dummy_Solr.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,474 @@ + + (object) array( + 'type' => 'long', + 'schema' => 'I-S----OF-----', + ), + 'content' => + (object) array( + 'type' => 'text', + 'schema' => 'ITS-V---------', + ), + 'im_3_field_tags' => + (object) array( + 'type' => 'long', + 'schema' => 'I-SM---OF-----', + 'dynamicBase' => 'im_*', + ), + 'entity_type' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'ds_last_comment_or_change' => + (object) array( + 'type' => 'tdate', + 'schema' => 'ITS----OF-----', + ), + 'nodeaccess_ari4jj_node_access_example_view' => + (object) array( + 'type' => 'integer', + 'schema' => 'I--M---OF-----', + 'dynamicBase' => 'nodeaccess*', + ), + 'entity_id' => + (object) array( + 'type' => 'tlong', + 'schema' => 'ITS----OF-----', + ), + 'ds_changed' => + (object) array( + 'type' => 'tdate', + 'schema' => 'ITS----OF-----', + ), + 'id' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'timestamp' => + (object) array( + 'type' => 'date', + 'schema' => 'I-S----OF----l', + ), + 'label' => + (object) array( + 'type' => 'text', + 'schema' => 'ITS-V--O------', + ), + 'nodeaccess_ari4jj_node_access_example_edit' => + (object) array( + 'type' => 'integer', + 'schema' => 'I--M---OF-----', + 'dynamicBase' => 'nodeaccess*', + ), + 'ds_created' => + (object) array( + 'type' => 'tdate', + 'schema' => 'ITS----OF-----', + ), + 'ss_name' => + (object) array( + 'type' => 'text', + 'schema' => 'ITS-V---------', + ), + 'path' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'taxonomy_names' => + (object) array( + 'type' => 'text', + 'schema' => 'IT-MV--O------', + ), + 'bundle' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'tid' => + (object) array( + 'type' => 'long', + 'schema' => 'I-SM---OF-----', + ), + 'is_tnid' => + (object) array( + 'type' => 'long', + 'schema' => 'I-S----OF-----', + ), + 'nodeaccess_ari4jj_node_access_example_author' => + (object) array( + 'type' => 'integer', + 'schema' => 'I--M---OF-----', + 'dynamicBase' => 'nodeaccess*', + ), + 'tm_vid_1_names' => + (object) array( + 'type' => 'text', + 'schema' => 'ITSMV---------', + 'dynamicBase' => 'tm_*', + ), + 'spell' => + (object) array( + 'type' => 'textSpell', + 'schema' => 'ITSM----------', + ), + 'site' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'is_comment_count' => + (object) array( + 'type' => 'tint', + 'schema' => 'ITS----OF-----', + ), + 'bundle_name' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'hash' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'bs_status' => + (object) array( + 'type' => 'boolean', + 'schema' => 'I-S----OF----l', + ), + 'entity_id' => + (object) array( + 'type' => 'long', + 'schema' => 'I-S----OF-----', + ), + 'url' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'nodeaccess_all' => + (object) array( + 'type' => 'integer', + 'schema' => 'I--M---OF-----', + 'dynamicBase' => 'nodeaccess*', + ), + 'sort_name' => + (object) array( + 'type' => 'sortString', + 'schema' => 'IT-----O-----l', + ), + 'tags_a' => + (object) array( + 'type' => 'text', + 'schema' => 'IT-----O------', + 'dynamicBase' => 'tags_*', + ), + 'bs_sticky' => + (object) array( + 'type' => 'boolean', + 'schema' => 'I-S----OF----l', + ), + 'bs_promote' => + (object) array( + 'type' => 'boolean', + 'schema' => 'I-S----OF----l', + ), + 'teaser' => + (object) array( + 'type' => 'text', + 'schema' => '-TS-----------', + ), + 'im_vid_1' => + (object) array( + 'type' => 'long', + 'schema' => 'I-SM---OF-----', + 'dynamicBase' => 'im_*', + ), + 'bs_translate' => + (object) array( + 'type' => 'boolean', + 'schema' => 'I-S----OF----l', + ), + 'sort_label' => + (object) array( + 'type' => 'sortString', + 'schema' => 'IT-----O-----l', + ), + 'ss_language' => + (object) array( + 'type' => 'string', + 'schema' => 'I-S----OF----l', + ), + 'sm_vid_Tags' => + (object) array( + 'type' => 'string', + 'schema' => 'I-SM---OF----l', + 'dynamicBase' => 'sm_*', + ), + ); + } + + protected $last_search = array(); + + public function search($query = '', array $params = array(), $method = 'GET') { + $this->last_search = array('query' => $query, 'params' => $params, 'method' => $method); + $response = new stdClass(); + $response->response = new stdClass(); + $response->response->numFound = 0; + $response->response->docs = array(); + + return $response; + } + + public function getLastSearch() { + return $this->last_search; + } + + /** + * Call the /admin/ping servlet, to test the connection to the server. + * + * @param $timeout + * maximum time to wait for ping in seconds, -1 for unlimited (default 2). + * @return + * (float) seconds taken to ping the server, FALSE if timeout occurs. + */ + function ping($timeout = 2) { + } + + /** + * Get information about the Solr Core. + * + * @return + * (string) system info encoded in json + */ + function getSystemInfo() { + } + + /** + * Get meta-data about the index. + */ + function getLuke($num_terms = 0) { + } + + /** + * Get information about the Solr Core. + * + * Returns a Simple XMl document + */ + function getStats() { + } + + /** + * Get summary information about the Solr Core. + */ + function getStatsSummary() { + } + + /** + * Clear cached Solr data. + */ + function clearCache() { + } + + /** + * Constructor + * + * @param $url + * The URL to the Solr server, possibly including a core name. E.g. http://localhost:8983/solr/ + * or https://search.example.com/solr/core99/ + * @param $env_id + * The machine name of a corresponding saved configuration used for loading + * data like which facets are enabled. + */ + function __construct($url, $env_id = NULL) { + } + + /** + * Make a request to a servlet (a path) that's not a standard path. + * + * @param string $servlet + * A path to be added to the base Solr path. e.g. 'extract/tika' + * + * @param array $params + * Any request parameters when constructing the URL. + * + * @param array $options + * @see drupal_http_request() $options. + * + * @return + * response object + * + * @thows Exception + */ + function makeServletRequest($servlet, $params = array(), $options = array()) { + } + + /** + * Get the Solr url + * + * @return string + */ + function getUrl() { + } + + /** + * Set the Solr url. + * + * @param $url + * + * @return $this + */ + function setUrl($url) { + } + + /** + * Raw update Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed xml document. + * + * @param string $rawPost + * @param float $timeout Maximum expected duration (in seconds) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function update($rawPost, $timeout = FALSE) { + } + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of ApacheSolrDocument instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * + * @return response objecte + * + * @throws Exception If an error occurs during the service call + */ + function addDocuments($documents, $overwrite = NULL, $commitWithin = NULL) { + } + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $optimize Defaults to true + * @param boolean $waitFlush Defaults to true + * @param boolean $waitSearcher Defaults to true + * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour + * @param boolean $softCommit optimize by using a softCommit + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600, $softCommit = FALSE) { + } + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteById($id, $timeout = 3600) { + } + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByMultipleIds($ids, $timeout = 3600) { + } + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return stdClass response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByQuery($rawQuery, $timeout = 3600) { + } + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * @param boolean $waitSearcher + * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * @param boolean $softCommit optimize by using a softCommit + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function optimize($waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600, $softCommit = FALSE) { + } + + /** + * Get the current solr version. This could be 1, 3 or 4 + * + * @return int + * 1, 3 or 4. Does not give a more details version, for that you need + * to get the system info. + */ + function getSolrVersion() { + } + + /** + * Get query name. + */ + function getName() { + } + + /** + * Get query searcher name (for facetapi, views, pages, etc). + */ + function getSearcher() { + } + + /** + * Get context values. + */ + function getContext() { + } + + /** + * Set context value. + */ + function addContext(array $context) { + } +} + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/apachesolr_base.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/apachesolr_base.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,547 @@ + $v) { + if (!isset($a2[$k])) { + debug("\$a2[$k] is not set"); + return FALSE; + } + if (is_array($a1[$k]) && is_array($a2[$k])) { + if (!$this->_nestedCompare($a1[$k], $a2[$k])) { + debug("_nestedCompare(\$a1[$k], \$a2[$k]) is false"); + return FALSE; + } + } + elseif ($a1[$k] !== $a2[$k]) { + debug("\$a1[$k] !== \$a2[$k] : " . var_export($a1[$k], TRUE) . " " . var_export($a2[$k], TRUE)); + return FALSE; + } + } + return TRUE; + } +} + +/** + * @file + * Unit test class that provides tests for base functionality of the Apachesolr + * Module without having the need of a Solr Server + */ +class DrupalSolrOfflineEnvironmentWebTestCase extends DrupalSolrOfflineWebTestCase { + /** + * A global basic user who can search. + */ + var $basic_user; + + /** + * A global administrative user who can administer search. + */ + var $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Solr Search Environments', + 'description' => 'Tests search environments functionality of the Solr module', + 'group' => 'ApacheSolr', + ); + } + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('apachesolr', 'search', 'apachesolr_test'); + // Create a basic user, which is subject to moderation. + $permissions = array( + 'access content', + 'search content', + ); + $basic_user = $this->drupalCreateUser($permissions); + + // Create an admin user that can bypass revision moderation. + $permissions = array( + 'access content', + 'search content', + 'administer nodes', + 'administer search', + ); + $admin_user = $this->drupalCreateUser($permissions); + + // Assign users to their test suite-wide properties. + $this->basic_user = $basic_user; + $this->admin_user = $admin_user; + } + + /** + * Asserts that the module was installed and that a notice appears that the server is offline + */ + function testServerOffline() { + // Load the default server. + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + $environment['url'] = 'http://localhost/solr/core_that_should_not_exist'; + apachesolr_environment_save($environment); + $status = apachesolr_server_status($environment['url']); + $this->assertFalse($status, t('A false URL could not be loaded and is offline')); + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr'); + $text = t('The server seems to be unavailable. Please verify the server settings'); + $this->assertText($text, t('When checking the status of the server it gives the correct message to inform the user that the server is not reachable')); + } + + /** + * Asserts that the module was installed and that a notice appears that the server is offline + */ + function testIndexFileIncluded() { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + $environment['url'] = 'http://localhost/solr/core_that_should_not_exist'; + apachesolr_environment_save($environment); + + $paths = array( + 'user', + 'node', + 'admin/config/search/apachesolr', + 'admin/config/search/apachesolr/search-pages', + 'admin/config/search/apachesolr/search-pages/core_search/edit', + 'admin/structure/block/manage/apachesolr_search/mlt-001/configure', + 'admin/config/search/apachesolr/settings/solr/bias', + 'admin/config/search/apachesolr/settings/solr/index', + 'admin/config/search/apachesolr/settings/solr/edit', + 'admin/reports/apachesolr', + 'admin/reports/apachesolr/conf', + 'search/site', + ); + $this->drupalLogin($this->admin_user); + foreach ($paths as $path) { + $this->drupalGet($path); + $text = 'apachesolr.index.inc was included'; + $this->assertNoText($text, t('Apachesolr.index.inc was not included')); + } + } + + /** + * Asserts that we can edit a search environment + */ + function testEditSearchEnvironment() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->clickLink(t('Edit')); + $this->assertText(t('Example: http://localhost:8983/solr'), t('Edit page was succesfully loaded')); + $edit = array('name' => 'new description foo bar', 'url' => 'http://localhost:8983/solr/core_does_not_exists'); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('new description foo bar'), t('Search environment description was succesfully edited')); + $this->assertText('http://localhost:8983/solr/core_does_not_exists', t('Search environment url was succesfully edited')); + } + + /** + * Asserts that we can use various url forms for the search environment + */ + function testEditSearchEnvironmentURLs() { + // Set the various url schemes that will be tested + $urls = array( + 'http://user@localhost:8983/solr/core_does_not_exists', + 'http://user:pass@localhost:8983/solr/core_does_not_exists', + 'http://user:pass@localhost/solr/core_does_not_exists', + 'https://localhost:8983/solr/core_does_not_exists' + ); + $this->drupalLogin($this->admin_user); + foreach ($urls as $url) { + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->clickLink(t('Edit')); + $this->assertText(t('Example: http://localhost:8983/solr'), t('Edit page was succesfully loaded')); + $edit = array('url' => $url); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText($url, t('Search environment url was succesfully set to !url', array('!url' => $url))); + } + } + + /** + * Asserts that we can clone a search environment + */ + function testCloneSearchEnvironment() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('Clone'), t('Clone button is available')); + $this->drupalGet('admin/config/search/apachesolr/settings/solr/clone'); + $this->assertText(t('Are you sure you want to clone search environment localhost server'), t('Clone confirmation page was succesfully loaded')); + $this->drupalPost($this->getUrl(), array(), t('Clone')); + $this->assertResponse(200); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('localhost server [cloned]'), t('Search Environment was succesfully cloned')); + // Check if the bundles and configurations are exactly the same + // after we clear the caches. + apachesolr_environments_clear_cache(); + $envs = apachesolr_load_all_environments(); + $this->assertEqual(count($envs), 2, 'Now we have 2 environments'); + $orig_env = $envs['solr']; + unset($envs['solr']); + $cloned_env = array_pop($envs); + $this->assertTrue($this->_nestedCompare($orig_env['index_bundles'], $cloned_env['index_bundles'])); + $this->assertTrue($this->_nestedCompare($orig_env['conf'], $cloned_env['conf'])); + } + + /** + * Asserts that we can edit a search environment + */ + function testCreateNewSearchEnvironment() { + // Create a new environment + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('Add search environment'), t('Create new environment link is available')); + $this->clickLink(t('Add search environment')); + $this->assertText(t('Make this Solr search environment the default'), t('Environment creation page succesfully added')); + $edit = array('url' => 'http://localhost:8983/solr', 'name' => 'my test description', 'env_id' => 'solr_test'); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('my test description'), t('Search Environment was succesfully created')); + + // Make this new search environment the default + $this->drupalGet('admin/config/search/apachesolr/settings'); + // Click on the second environment edit link + $this->clickLink(t('Edit'), 1); + $this->assertText(t('Example: http://localhost:8983/solr'), t('Edit page was succesfully loaded')); + $edit = array('make_default' => 1, 'conf[apachesolr_read_only]' => APACHESOLR_READ_ONLY); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + $this->drupalGet('admin/config/search/apachesolr/settings'); + $this->assertText(t('my test description (Default)'), t('New Search environment was succesfully changed to default environment')); + // Clear our cache. + apachesolr_environments_clear_cache(); + $mode = apachesolr_environment_variable_get('solr_test', 'apachesolr_read_only', APACHESOLR_READ_WRITE); + $this->assertEqual($mode, APACHESOLR_READ_ONLY, 'Environment succesfully changed to read only'); + } +} + +/** + * @file + * Unit test class that provides tests for base functionality of the Apachesolr + * Module without having the need of a Solr Server + */ +class DrupalSolrOfflineSearchPagesWebTestCase extends DrupalSolrOfflineWebTestCase { + /** + * A global basic user who can search. + */ + var $basic_user; + + /** + * A global administrative user who can administer search. + */ + var $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Solr Search Pages', + 'description' => 'Tests search pages functionality of the Solr module', + 'group' => 'ApacheSolr', + ); + } + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('apachesolr', 'apachesolr_search', 'search', 'apachesolr_test'); + // Create a basic user, which is subject to moderation. + $permissions = array( + 'access content', + 'search content', + ); + $basic_user = $this->drupalCreateUser($permissions); + + // Create an admin user that can bypass revision moderation. + $permissions = array( + 'access content', + 'search content', + 'administer nodes', + 'administer search', + ); + $admin_user = $this->drupalCreateUser($permissions); + + // Assign users to their test suite-wide properties. + $this->basic_user = $basic_user; + $this->admin_user = $admin_user; + + // Make sure our environment does not exists + $env_id = apachesolr_default_environment(NULL, TRUE); + $environment = apachesolr_environment_load($env_id, TRUE); + $environment['url'] = 'http://localhost/solr/core_that_should_not_exist'; + apachesolr_environment_save($environment); + // Reset all caches + apachesolr_load_all_environments(TRUE); + } + + /** + * Asserts that we can edit a search environment + */ + function testCheckCoreSearchPage() { + // Create a new environment + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Core Search'), t('Core Search page is available')); + } + + /** + * Asserts that we can edit a search environment + */ + function testEditSearchPage() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->clickLink(t('Edit')); + $this->assertText(t('The human-readable name of the search page configuration'), t('Edit page was succesfully loaded')); + $edit = array( + 'label' => 'Test Search Page', + 'description' => 'Test Description', + 'page_title' => 'Test Title', + 'search_path' => 'search/searchdifferentpath', + ); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + // Make sure the menu is recognized + drupal_static_reset('apachesolr_search_load_all_search_pages'); + menu_cache_clear_all(); + menu_rebuild(); + + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Test Search Page'), t('Search page was succesfully edited')); + $this->assertText('search/searchdifferentpath', t('Search path was updated')); + $this->drupalGet('search/searchdifferentpath'); + $this->assertText(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), t('Search path was successfully created and is accessible')); + } + + /** + * Asserts that we can clone a search page + */ + function testCloneSearchPage() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Clone'), t('Clone button is available')); + $this->drupalGet('admin/config/search/apachesolr/search-pages/core_search/clone'); + $this->assertText(t('Are you sure you want to clone search page Core Search?'), t('Clone confirmation page was succesfully loaded')); + $this->drupalPost($this->getUrl(), array(), t('Clone')); + $this->assertResponse(200); + drupal_static_reset('apachesolr_search_load_all_search_pages'); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Core Search [cloned]'), 'Search page was succesfully cloned'); + } + + /** + * Asserts that we can edit a search environment + */ + function testNewAndRemoveSearchPage() { + // Create a new search page + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Add search page'), t('Create new search page link is available')); + $this->clickLink(t('Add search page')); + $this->assertText(t('The human-readable name of the search page configuration.'), t('Search page creation page succesfully added')); + $edit = array( + 'page_id' => 'solr_testingsuite', + 'env_id' => 'solr', + 'label' => 'Test Search Page', + 'description' => 'Test Description', + 'page_title' => 'Test Title', + 'search_path' => 'search/searchdifferentpath', + ); + $this->drupalPost($this->getUrl(), $edit, t('Save')); + $this->assertResponse(200); + // Make sure the menu is recognized + drupal_static_reset('apachesolr_search_load_all_search_pages'); + menu_cache_clear_all(); + menu_rebuild(); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertText(t('Test Search Page'), t('Search Page was succesfully created')); + + // Remove the same environment + $this->clickLink(t('Delete')); + $this->assertText(t('search page configuration will be deleted.This action cannot be undone.'), t('Delete confirmation page was succesfully loaded')); + $this->drupalPost($this->getUrl(), array(), t('Delete')); + $this->assertResponse(200); + drupal_static_reset('apachesolr_search_load_all_search_pages'); + $this->drupalGet('admin/config/search/apachesolr/search-pages'); + $this->assertNoText(t('Test Search Page'), t('Search Environment was succesfully deleted')); + apachesolr_environment_save(array('env_id' => 'DummySolr', 'service_class' => 'DummySolr', 'name' => 'dummy server', 'url' => 'http://localhost:8983/solr')); + $solr = new DummySolr($url = NULL, $env_id = 'DummySolr'); + $params = array( + 'rows' => 5, + ); + $results = apachesolr_search_run('apachesolr_test', $params, '', '', 0, $solr); + $query = apachesolr_current_query('DummySolr'); + $this->assertEqual($query->getParam('rows'), 5, 'Passed in rows param overrode default'); + } +} + +class DrupalSolrNodeTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Solr Node add and deletion tests', + 'description' => 'Tests if we can succesfully add and delete nodes', + 'group' => 'ApacheSolr', + ); + } + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('apachesolr', 'apachesolr_search', 'search', 'apachesolr_test'); + } + + function testApacheSolrNodeAddDelete() { + // Login as basic user to perform initial content creation. + // Create an admin user that can bypass revision moderation. + $permissions = array( + 'access content', + 'search content', + 'administer nodes', + 'administer search', + 'access content overview', + 'bypass node access', + ); + $admin_user = $this->drupalCreateUser($permissions); + + $this->drupalLogin($admin_user); + + // enable our bundles to be indexed, and clear caches + apachesolr_index_set_bundles('solr', 'node', array('page', 'article')); + entity_info_cache_clear(); + apachesolr_environments_clear_cache(); + + // Define types of node bundles that we want to index + $types = array('page', 'article'); + + foreach ($types as $type) { + $edit = array(); + // Create a node of the type $type. + $edit['uid'] = $admin_user->uid; + $edit['type'] = $type; + $edit['title'] = $this->randomName(16); + $node = $this->drupalCreateNode($edit); + $this->assertTrue(is_object($node) && isset($node->nid), t('Article type @type has been created.', array('@type' => $type))); + + // Check that the node has been created. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertTrue($node, t('Created article @type found in database.', array('@type' => $type))); + + // Check that the node has status 1 + $indexer_table = apachesolr_get_indexer_table('node'); + $query = db_select($indexer_table, 'aien') + ->condition('entity_id', $node->nid) + ->fields('aien', array('entity_id', 'status')); + $db_node = $query->execute()->fetchObject(); + $this->assertEqual($db_node->status, 1, t('Node @entity_id has status 1', array('@entity_id' => $db_node->entity_id))); + + // Delete the node + $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete')); + + // check if the entity delete does its work. It should have set the + // status to 0 so it will be deleted when solr comes online + $indexer_table = apachesolr_get_indexer_table('node'); + $query = db_select($indexer_table, 'aien') + ->condition('entity_id', $node->nid) + ->fields('aien', array('entity_id', 'status')); + $db_node = $query->execute()->fetchObject(); + + // Check that all of the nodes (should only have 1) have status 0, it + // is set as 0 because it is pending to be deleted + $this->assertEqual($db_node->status, 0, t('Node @entity_id has status 0', array('@entity_id' => $db_node->entity_id))); + + // Check that all the nodes have been deleted. + $count = db_select('node', 'n') + ->condition('n.nid', $node->nid) + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($count, 0, t('No more nodes left in the node table.')); + } + } +} + +class DrupalSolrOfflineUnitTestCase extends DrupalUnitTestCase { + public static function getInfo() { + return array( + 'name' => 'Solr Base Framework Tests Unit Test', + 'description' => 'Unit test functionality of the Solr module', + 'group' => 'ApacheSolr', + ); + } + + protected function setUp() { + parent::setUp(); + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.module'; + + $this->script_content = <<GOOD_CONTENT

+ + +EOF; + + $this->embed_content = <<GOOD_CONTENT

+ +OTHER_CONTENT + +EOF; + + $this->iframe_content = << +

GOOD_CONTENT

+ +EOF; + + $this->comment_content = <<GOOD_CONTENT

+OTHER_CONTENT + +EOF; + } + + /** + * Test ordering of parsed filter positions. + * + * Regression test for http://drupal.org/node/891962 + */ + function testContentFilters() { + $cleaned = apachesolr_clean_text($this->script_content); + $this->assertFalse(strpos($cleaned, 'script'), 'Script tags removed'); + $this->assertFalse(strpos($cleaned, 'accordion_teachers'), 'Script tags conent removed'); + $this->assertTrue(strpos(trim($cleaned), 'GOOD_CONTENT') === 0, 'Real content retained'); + + $cleaned = apachesolr_clean_text($this->embed_content); + $this->assertFalse(strpos($cleaned, 'object'), 'object tags removed'); + $this->assertFalse(strpos($cleaned, 'embed'), 'embed tags removed'); + $this->assertFalse(strpos($cleaned, '8Vmnq5dBF7Y'), 'object tags conent removed'); + $this->assertFalse(strpos($cleaned, 'shockwave-flash'), 'embed tags conent removed'); + $this->assertTrue(strpos(trim($cleaned), 'GOOD_CONTENT') === 0, 'Real content retained'); + $this->assertTrue(strpos($cleaned, 'OTHER_CONTENT') > 0, 'Other content retained'); + + $cleaned = apachesolr_clean_text($this->iframe_content); + $this->assertFalse(strpos($cleaned, 'iframe'), 'iframe tags removed'); + $this->assertFalse(strpos($cleaned, '8Vmnq5dBF7Y'), 'iframe tags conent removed'); + $this->assertTrue(strpos(trim($cleaned), 'GOOD_CONTENT') === 0, 'Real content retained'); + + $cleaned = apachesolr_clean_text($this->comment_content); + $this->assertFalse(strpos($cleaned, 'COMMENT'), 'html comment content removed '); + $this->assertTrue(strpos(trim($cleaned), 'GOOD_CONTENT') === 0, 'Real content retained'); + } +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/apachesolr_test/apachesolr_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/apachesolr_test/apachesolr_test.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,13 @@ +name = Apache Solr helper module for tests +description = Support module for apachesolr module testing. +package = Search Toolkit +core = 7.x +hidden = TRUE +dependencies[] = apachesolr + +; Information added by drupal.org packaging script on 2013-03-15 +version = "7.x-1.1+34-dev" +core = "7.x" +project = "apachesolr" +datestamp = "1363307665" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/apachesolr_test/apachesolr_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/apachesolr_test/apachesolr_test.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,16 @@ + 'markup', + '#markup' => 'apachesolr.index.inc was included', + ); + } +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/README.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,7 @@ + +The conf files in this directory are complete in addition to schema.xml and solrconfig.xml +in order to provide what a core needs to run. However, these are only for testing. For +production start from the ones from the example/solr directory of a Solr 1.4.x +release tarball. + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/elevate.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/elevate.xml Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,4 @@ + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/mapping-ISOLatin1Accent.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/mapping-ISOLatin1Accent.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,20 @@ +# Incomplete example for testing + +# à => a +"\u00E0" => "a" + +# á => a +"\u00E1" => "a" + +# â => a +"\u00E2" => "a" + +# ã => a +"\u00E3" => "a" + +# ä => a +"\u00E4" => "a" + +# å => a +"\u00E5" => "a" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/protwords.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/protwords.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,2 @@ +# Incomplete example for testing + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/stopwords.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/stopwords.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,6 @@ +# Incomplete example set for testing +a +an +of +the +to diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/conf/synonyms.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/conf/synonyms.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,3 @@ +# Example synonmym mappings for testing +druplicon => drupal icon +love,hate,spam diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/solr_base_query.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/solr_base_query.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,214 @@ + 'SolrBaseQuery Unit tests', + 'description' => 'Unit Tests for queries.', + 'group' => 'ApacheSolr', + ); + } + + function setUp() { + parent::setUp(); + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.module'; + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.interface.inc'; + require_once dirname(dirname(realpath(__FILE__))) . '/Solr_Base_Query.php'; + require_once dirname(dirname(realpath(__FILE__))) . '/Drupal_Apache_Solr_Service.php'; + require_once dirname(dirname(realpath(__FILE__))) . '/tests/Dummy_Solr.php'; + } + + /** + * Helper function to simulate the auto loading and other non-needed functions + * that otherwise require a database + * @see apachesolr_drupal_query(). + * @return SolrBaseQuery + */ + private function _apachesolr_drupal_query($name, $params = array(), $solrsort = '', $base_path = '', $solr = NULL) { + if (empty($solr)) { + $solr = new DummySolr(NULL); + } + return new SolrBaseQuery($name, $solr, $params, $solrsort, $base_path); + } + + private function _apachesolr_drupal_subquery($operator = 'OR') { + return new SolrFilterSubQuery($operator); + } + + /** + * Test ordering of parsed filter positions. + * + * Regression test for http://drupal.org/node/891962 + */ + function testParseFilters() { + $fq = array('tid:3', 'sort_label:hello', 'tid:11', 'tid:1', 'tid:12', 'label:hello'); + // Setup dummy Solr object. + $query = $this->_apachesolr_drupal_query("apachesolr_tests", array('q' => 'mykeys', 'fq' => $fq), 'sort_label asc', 'search/test'); + // Check sortsort + $this->assertEqual(array('solrsort' => 'sort_label asc'), $query->getSolrsortUrlQuery()); + $query->setSolrsort('sort_name', 'desc'); + $this->assertEqual(array('solrsort' => 'sort_name desc'), $query->getSolrsortUrlQuery()); + $query->setSolrsort('score', 'desc'); + $this->assertEqual(array(), $query->getSolrsortUrlQuery()); + // Check getPath() functionality + $this->assertEqual('search/test/mykeys', $query->getPath()); + $this->assertEqual('search/test/newkeys', $query->getPath('newkeys')); + // Check hasFilter functionality + $this->assertFalse($query->hasFilter('label', 'Jello'), "hasFilter('label', 'Jello') is FALSE"); + $this->assertTrue($query->hasFilter('label', 'hello'), "hasFilter('label', 'hello') is TRUE"); + $this->assertTrue($query->hasFilter('label', 'hello', FALSE), "hasFilter('label', 'hello', FALSE) is TRUE"); + $this->assertFalse($query->hasFilter('label', 'hello', TRUE), "hasFilter('label', 'hello', TRUE) is FALSE"); + $filters = $query->getFilters(); + $this->assertEqual(count($filters), 6, count($filters) . ' filters found, expected 6 filters'); + // Check positions of filters + foreach ($fq as $idx => $filter) { + $this->assertEqual($filter, $query->makeFilterQuery($filters[$idx])); + } + // Check that the query string is re-assembled correctly + $this->assertEqual($fq, $query->getParam('fq')); + $this->assertEqual('mykeys', $query->getParam('q')); + $query->removeFilter('tid', '11'); + $filters = $query->getFilters(); + $this->assertEqual(count($filters), 5, count($filters) . ' filters found, expected 5 filters'); + $this->assertEqual(array('tid:3', 'sort_label:hello', 'tid:1', 'tid:12', 'label:hello'), $query->getParam('fq')); + $query->removeFilter('tid'); + $filters = $query->getFilters(); + $this->assertEqual(count($filters), 2, count($filters) . ' filters found, expected 2 filters'); + $this->assertEqual(array('sort_label:hello', 'label:hello'), $query->getParam('fq')); + + $subquery = $this->_apachesolr_drupal_subquery(); + $subquery->addFilter('access__all', 0); + $subquery->addFilter('hash', 'randomhash'); + $query->addFilterSubQuery($subquery); + $this->assertEqual(count($query->getParam('fq')), 3, count($query->getParam('fq')) . ' fq params found, expected 3 after adding subquery'); + $this->assertEqual(array('sort_label:hello', 'label:hello', '(access__all:0 OR hash:randomhash' . ')'), $query->getParam('fq')); + } + + function testAddParams() { + $examples = array(); + $examples['{!cache=false}inStock:true'] = array( + '#local' => 'cache=false', + '#exclude' => FALSE, + '#name' => 'inStock', + '#value' => 'true', + ); + $examples['{!frange l=1 u=4 cache=false}sqrt(popularity)'] = array( + '#local' => 'frange l=1 u=4 cache=false', + '#exclude' => FALSE, + '#name' => NULL, + '#value' => 'sqrt(popularity)', + ); + $examples['{!cache=false cost=5}inStock:true'] = array( + '#local' => 'cache=false cost=5', + '#exclude' => FALSE, + '#name' => 'inStock', + '#value' => 'true', + ); + $examples['{!tag=impala}model:Impala'] = array( + '#local' => 'tag=impala', + '#exclude' => FALSE, + '#name' => 'model', + '#value' => 'Impala', + ); + $examples['{!anything that appears to be local}'] = array( + '#local' => 'anything that appears to be local', + '#exclude' => FALSE, + '#name' => NULL, + '#value' => NULL, + ); + $examples['bundle:(article OR page)'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'bundle', + '#value' => '(article OR page)', + ); + $examples['-bundle:(article OR page)'] = array( + '#local' => NULL, + '#exclude' => TRUE, + '#name' => 'bundle', + '#value' => '(article OR page)', + ); + $examples['-{!anything that appears to be local}'] = array( + '#local' => 'anything that appears to be local', + '#exclude' => TRUE, + '#name' => NULL, + '#value' => NULL, + ); + $examples['title:"double words"'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'title', + '#value' => '"double words"', + ); + $examples['field_date:[1970-12-31T23:59:59Z TO NOW]'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'field_date', + '#value' => '[1970-12-31T23:59:59Z TO NOW]', + ); + + $query = $this->_apachesolr_drupal_query("apachesolr_tests"); + foreach ($examples as $fq => $example) { + $name = (!empty($example['#name'])) ? $example['#name'] : '_QUERY_'; + $value = (!empty($example['#value'])) ? $example['#value'] : '_VALUE_'; + $filter = $name . ':' . $value; + // Check if filter is seen as a valid one + $message = t('Filter (@fq) is Valid', array('@fq' => $filter)); + $this->assertTrue($query->validFilterValue($filter), $message); + + $query->addParam('fq', $fq); + // Check if filter was added + $message = t('hasFilter(@name, @value) is True', array('@name' => $example['#name'], '@value' => $example['#value'])); + $this->assertTrue($query->hasFilter($example['#name'], $example['#value'], $example['#exclude']), $message); + $filters = $query->getFilters(); + $filter = reset($filters); + $message = t('The filter "@fq" was added with all the given properties', array('@fq' => $fq)); + $this->assertTrue(!array_diff($example, $filter), $message); + $query->removeFilter($example['#name']); + $message = t('The filter "@fq" was correctly removed', array('@fq' => $fq)); + $this->assertFalse($query->hasFilter($example['#name'], $example['#value'], $example['#exclude']), $message); + // Since we cannot remove filters without names yet we have to clear the whole fq array + $query->removeParam('fq'); + // Check the ones without the name also + } + + // Check for invalid combinations + $bad_examples['wrong name:"double words"'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'wrong name', + '#value' => '"double words"', + ); + $bad_examples['field_date:[1970-12-31 TO NOW]'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'field_date', + '#value' => '[1970-12-31 TO NOW]', + ); + $bad_examples['bundle:((article OR page)]'] = array( + '#local' => NULL, + '#exclude' => FALSE, + '#name' => 'bundle', + '#value' => '((article OR page)]', + ); + + foreach ($bad_examples as $fq => $example) { + $name = (!empty($example['#name'])) ? $example['#name'] : '_QUERY_'; + $value = (!empty($example['#value'])) ? $example['#value'] : '_VALUE_'; + $filter = $name . ':' . $value; + // Check if filter is seen as a valid one + $message = t('Filter (@fq) is not Valid', array('@fq' => $filter)); + $this->assertFalse($query->validFilterValue($filter), $message); + } + // Check parameter normalization. + $query->addParam('spellcheck', TRUE); + $this->assertTrue($query->getParam('spellcheck') === 'true', "TRUE normalized to string 'true'"); + $query->replaceParam('spellcheck', FALSE); + $this->assertTrue($query->getParam('spellcheck') === 'false', "FALSE normalized to string 'false'"); + $query->addParam('fl', array(' x ', TRUE)); + $this->assertTrue($query->getParam('fl') === array('x', 'true'), "Array of params all normalized"); + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/solr_base_subquery.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/solr_base_subquery.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,109 @@ + 'SolrFilterSubQuery Unit tests', + 'description' => 'Unit Tests for subqueries.', + 'group' => 'ApacheSolr', + ); + } + + function setUp() { + parent::setUp(); + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.module'; + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.interface.inc'; + require_once dirname(dirname(realpath(__FILE__))) . '/Solr_Base_Query.php'; + require_once dirname(dirname(realpath(__FILE__))) . '/Drupal_Apache_Solr_Service.php'; + require_once dirname(dirname(realpath(__FILE__))) . '/tests/Dummy_Solr.php'; + } + + /** + * Helper function to simulate the auto loading and other non-needed functions + * that otherwise require a database + * @see apachesolr_drupal_query(). + * @return SolrBaseQuery + */ + private function _apachesolr_drupal_query($name, $params = array(), $solrsort = '', $base_path = '', $solr = NULL) { + if (empty($solr)) { + $solr = new DummySolr(NULL); + } + return new SolrBaseQuery($name, $solr, $params, $solrsort, $base_path); + } + + private function _apachesolr_drupal_subquery($operator = 'OR') { + return new SolrFilterSubQuery($operator); + } + + function testSubQueriesQuery() { + $query1 = $this->_apachesolr_drupal_query('DrupalTest'); + $query1->addFilter('label', 'foo'); + + $query2 = $this->_apachesolr_drupal_subquery(); + $query2->addFilter('label', 'bar'); + + $query3 = $this->_apachesolr_drupal_subquery(); + $query3->addFilter('label', 'baz'); + + $query1->addFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], 'label:foo', t('First field should be label:foo')); + $this->assertEqual($params[1], '(label:bar)', t('Second field should be label:bar')); + + $query1->removeFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertFalse(isset($params[1]), t('Second field should be empty')); + + $query1->addFilterSubQuery($query2); + $query1->addFilterSubQuery($query2); + $query1->addFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], 'label:foo', t('First field should be label:foo')); + $this->assertEqual($params[1], '(label:bar)', t('Second field should be label:bar')); + $this->assertEqual(count($params), 2, t('Add bar several times; should only appear once.')); + + // Empty out query1 + $query1 = $this->_apachesolr_drupal_query('DrupalTest'); + $query2 = $this->_apachesolr_drupal_subquery('DrupalTest'); + $query2->operator = 'OR'; + $query2->addFilter('label', 'bar'); + $query2->addFilter('label', 'baz'); + $query1->addFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], '(label:bar OR label:baz)', '(label:bar OR label:baz)'); + + // Empty out query1 + $query1 = $this->_apachesolr_drupal_query('DrupalTest'); + $query2 = $this->_apachesolr_drupal_subquery('DrupalTest'); + $query2->operator = 'AND'; + $query2->addFilter('label', 'bar'); + $query2->addFilter('label', 'baz'); + $query1->addFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], '(label:bar AND label:baz)', '(label:bar AND label:baz)'); + + // Test with multiple filters in first query + $query1 = $this->_apachesolr_drupal_query('DrupalTest'); + $query1->addFilter('is_uid', '10'); + + $query2 = $this->_apachesolr_drupal_subquery(); + $query2->addFilter('is_uid', '1'); + $query2->addFilter('tid', '5'); + $query1->addFilterSubQuery($query2); + + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], 'is_uid:10', 'First field value is is_uid:10'); + $this->assertEqual($params[1], '(is_uid:1 OR tid:5)', 'Second field value is (is_uid:1 OR tid:5)'); + + $query2->operator = 'AND'; + $query1->addFilterSubQuery($query2); + $params = $query1->getParam('fq'); + $this->assertEqual($params[0], 'is_uid:10', 'First field value is is_uid:10'); + $this->assertEqual($params[1], '(is_uid:1 AND tid:5)', 'Second field value is (is_uid:1 AND tid:5)'); + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/solr_document.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/solr_document.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,80 @@ + 'ApacheSolrDocument Unit tests', + 'description' => 'Unit test of ApacheSolrDocument', + 'group' => 'ApacheSolr', + ); + } + + protected function setUp() { + parent::setUp(); + require_once dirname(dirname(realpath(__FILE__))) . '/apachesolr.module'; + require_once dirname(dirname(realpath(__FILE__))) . '/Apache_Solr_Document.php'; + } + + function testSolrDocument() { + $document = new ApacheSolrDocument(); + + $document->addField('ss_testing', 'testingvalue'); + $field_value = $document->getField('ss_testing'); + $this->assertEqual($field_value['value'][0], 'testingvalue', t('The field was correctly added and verified')); + $document->clear(); + + $document->addField('ss_testing', 'testingvalue', 10); + $field_value = $document->getField('ss_testing'); + $this->assertEqual($field_value['value'][0], 'testingvalue', t('The field and boost were correctly added and verified')); + $field_boost = $document->getFieldBoost('ss_testing'); + $this->assertEqual($field_boost, 10, t('The field boost was correctly added and verified')); + $document->clear(); + + $document->setMultiValue('sm_testing', 'testingvalue1'); + $document->setMultiValue('sm_testing', 'testingvalue2'); + $field_value = $document->getField('sm_testing'); + $this->assertTrue(in_array('testingvalue1', $field_value['value']), t('The multivalued field value was correctly added and verified')); + $this->assertTrue(in_array('testingvalue2', $field_value['value']), t('The second multivalued field value was correctly added and verified')); + $document->clear(); + + $document->setMultiValue('sm_testing', 'testingvalue1', 10); + $document->setMultiValue('sm_testing', 'testingvalue2', 20); + $field_value = $document->getField('sm_testing'); + $this->assertTrue(in_array('testingvalue1', $field_value['value']), t('The multivalued field value and boost were correctly added and verified')); + $this->assertTrue(in_array('testingvalue2', $field_value['value']), t('The second multivalued field value and boost were correctly added and verified')); + $field_boost = $document->getFieldBoost('sm_testing'); + $this->assertEqual($field_boost, 200, t('The field boost was correctly multiplied and retrieved')); + + $document_field_names = $document->getFieldNames(); + $this->assertTrue(in_array('sm_testing', $document_field_names), t('The field name was found in the document')); + + $document_field_names = $document->getFieldValues(); + $this->assertTrue(in_array('testingvalue1', $document_field_names[0]), t('The field value was found in the document')); + + // Clear the complete document + $document->clear(); + + // Set and Get the document boost + $document->setBoost('10'); + $document_boost = $document->getBoost(); + $this->assertEqual($document_boost, 10, t('The document boost was correctly added and verified')); + + $document->clear(); + $document_boost = $document->getBoost(); + $document_fields = $document->getFieldNames(); + $document_field_boosts = $document->getFieldBoosts(); + $this->assertFalse($document_boost, t('Document boost was succesfully emptied')); + $this->assertFalse($document_fields, t('Document fields were succesfully emptied')); + $this->assertFalse($document_field_boosts, t('Document field boosts were succesfully emptied')); + } + + function tearDown() { + parent::tearDown(); + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrconnect/tests/solr_index_and_search.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrconnect/tests/solr_index_and_search.test Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,354 @@ +base_solr_url = $environment['url']; + + // Because we are in a clean environment, this will always be the default + // http://localhost:8983/solr + $this->core_admin_url = "{$this->base_solr_url}/admin/cores"; + + // The core admin url will give a valid response if the + // Solr server is running locally. + if ($this->coreAdminAvailable()) { + + // We will use a core named after the simpletest prefix. + $environment['url'] .= '/' . $this->databasePrefix; + + $filesdir = file_directory_temp(); + // Create our Solr core directory. + drupal_mkdir("{$filesdir}/solr", 0777, TRUE); + // Our temporary core is located here. + $instancedir = realpath($filesdir . "/solr"); + + // use the Solr version confs where appropriate. + $version = $this->getSolrVersion(); + if (isset($version) && $version == 3) { + $conf_path = dirname(__FILE__) . '/../solr-conf/solr-3.x/*'; + } + elseif (isset($version) && $version == 4) { + $conf_path = dirname(__FILE__) . '/../solr-conf/solr-4.x/*'; + } + else { + $conf_path = dirname(__FILE__) . '/../solr-conf/solr-1.4/*'; + } + + $patterns = array( + $conf_path, + dirname(__FILE__) . '/conf/*', + ); + + // Copy all files in solr-conf dir to our temporary solr core. + drupal_mkdir("{$instancedir}/conf", 0777, TRUE); + foreach ($patterns as $pattern) { + foreach (glob($pattern) as $conf_file) { + copy($conf_file, "$instancedir/conf/" . basename($conf_file)); + } + } + + $contents = file_get_contents("$instancedir/conf/solrconfig.xml"); + + // Change the autoCommit time down to 1 second. + // @todo - use solrcore.properties file for 3.x. + file_put_contents("$instancedir/conf/solrconfig.xml", preg_replace('@[0-9]+@', '1000', $contents)); + + // hard chmod -R because it seems drupal dirs are too restricted in a + // testing environment + system("chmod -R 777 {$instancedir}"); + + $query['name'] = $this->databasePrefix; + $query['instanceDir'] = $instancedir; + $created = $this->coreAdmin('CREATE', $query); + + if ($created && apachesolr_server_status($environment['url'])) { + $this->instancedir = $instancedir; + $this->solr_url = $environment['url']; + apachesolr_environment_save($environment); + $this->solr = apachesolr_get_solr($env_id); + $this->solr_available = TRUE; + $this->checkCoreStatus($this->databasePrefix); + } + } + // Workaround for drupal.org test bot. + // The tests succeed but further tests will not run because $this->solr_available is FALSE. + if (!$this->solr_available) { + $this->pass(t('Warning : The solr instance could not be found. Please enable a multicore one on http://localhost:8983/solr')); + } + } + + protected function coreAdminAvailable() { + $url = url($this->core_admin_url, array('query' => array('action' => 'STATUS'))); + $options['timeout'] = 2; + $result = drupal_http_request($url, $options); + return ($result->code == 200 && empty($result->error)); + } + + protected function getSolrVersion() { + $status = $this->coreAdmin('STATUS'); + foreach($status['status'] as $core_id => $core) { + $solr = new DrupalApacheSolrService($this->base_solr_url . '/' . $core_id); + $version = $solr->getSolrVersion(); + if (!empty($version)) { + return $version; + } + else { + return "1"; + } + } + } + + /** + * Helper function to invoke core admin actions. + */ + protected function coreAdmin($action, $query = array()) { + $query['action'] = $action; + $query['wt'] = 'json'; + $url = url($this->core_admin_url, array('query' => $query)); + $options['timeout'] = 2; + $result = drupal_http_request($url, $options); + + if ($result->code == 200) { + return json_decode($result->data, TRUE); + } + else { + return FALSE; + } + } + + /** + * Helper function to verify that the expected core exists. + */ + protected function checkCoreStatus($core_name) { + $response = $this->coreAdmin('STATUS', array('core' => $core_name)); + $this->assertTrue(isset($response['status'][$core_name]['index']), 'Found Solr test core index status'); + } + + function tearDown() { + // Workaround for drupal.org test bot + if ($this->solr_available) { + // Unload the Solr core & delete all files + $query = array( + 'core' => $this->databasePrefix, + 'deleteIndex' => 'true', + 'deleteDataDir' => 'true', + 'deleteInstanceDir' => 'true' + ); + // This is currently broken due to + // https://issues.apache.org/jira/browse/SOLR-3586 + $this->coreAdmin('UNLOAD', $query); + + } + parent::tearDown(); + } +} + + +class DrupalSolrOnlineWebTestCase extends AbstractDrupalSolrOnlineWebTestCase { + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp(); + parent::setUpSolr(); + } +} + + +class DrupalSolrMatchTestCase extends DrupalSolrOnlineWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Solr Index Data and test live queries', + 'description' => 'Indexes content and queries it.', + 'group' => 'ApacheSolr', + ); + } + + /** + * Test search indexing. + */ + function testMatching() { + if ($this->solr_available) { // workaround for drupal.org test bot + $this->assertTrue($this->solr->ping(), "The Server could be Pinged"); + $response = $this->solr->search("*:*", array()); + $response = $response->response; + $this->assertEqual($response->numFound, 0, "There should not be any documents in the index"); + $this->populateIndex(7); + $response = $this->solr->search("*:*", array()); + $response = $response->response; + $this->assertEqual($response->numFound, 7, "There should be 7 documents in the index"); + $this->_testQueries(); + } + } + + /** + * Set up a small index of items to test against. + */ + protected function populateIndex($count) { + + variable_set('minimum_word_size', 3); + for ($i = 1; $i <= $count; ++$i) { + $documents[] = $this->buildDocument(array('entity_id' => $i, 'content' => $this->getText($i))); + } + $this->solr->addDocuments($documents); + $this->solr->commit(); + } + + protected function buildDocument($values = array()) { + $document = new ApacheSolrDocument(); + if (!isset($values['entity_type'])) { + $values['entity_type'] = 'fake.'; + } + $document->id = apachesolr_document_id($values['entity_id'], $values['entity_type']); + foreach ($values as $key => $value) { + $document->$key = $value; + } + return $document; + } + + /** + * Helper method for generating snippets of content. + * + * Generated items to test against: + * 1 ipsum + * 2 dolore sit + * 3 sit am ut + * 4 am ut enim am + * 5 ut enim am minim veniam + * 6 enim am minim veniam es cillum + * 7 am minim veniam es cillum dolore eu + */ + function getText($n) { + // Start over after 7. + $n = $n % 7; + $words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu."); + return implode(' ', array_slice($words, $n - 1, $n)); + } + + /** + * Run predefine queries looking for indexed terms. + */ + function _testQueries() { + /* + Note: OR queries that include short words in OR groups are only accepted + if the ORed terms are ANDed with at least one long word in the rest of the query. + + e.g. enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) -> good + e.g. dolore OR ut = (dolore) OR (ut) -> bad + + This is a design limitation to avoid full table scans. + + APACHESOLR NOTE: These are not all in lucene syntax... @TODO. Still works for text searching + */ + $queries = array( + // Simple AND queries. + 'ipsum' => array(1), + 'enim' => array(4, 5, 6), + 'xxxxx' => array(), + // Mixed queries. + '"minim am veniam es" OR "dolore sit"' => array(2), + '"minim am veniam es" OR "sit dolore"' => array(), + '"am minim veniam es" -eu' => array(6), + '"am minim veniam" -"cillum dolore"' => array(5, 6), + ); + $broken = array( + 'enim minim' => array(5, 6), + 'enim xxxxx' => array(), + 'dolore eu' => array(7), + 'dolore xx' => array(), + 'ut minim' => array(5), + 'xx minim' => array(), + 'enim veniam am minim ut' => array(5), + // Simple OR queries. + 'dolore OR ipsum' => array(1, 2, 7), + 'dolore OR xxxxx' => array(2, 7), + 'dolore OR ipsum OR enim' => array(1, 2, 4, 5, 6, 7), + 'minim dolore OR ipsum OR enim' => array(5, 6, 7), + 'xxxxx dolore OR ipsum' => array(), + // Negative queries. + 'dolore -sit' => array(7), + 'dolore -eu' => array(2), + 'dolore -xxxxx' => array(2, 7), + 'dolore -xx' => array(2, 7), + // Phrase queries. + '"dolore sit"' => array(2), + '"sit dolore"' => array(), + '"am minim veniam es"' => array(6, 7), + '"minim am veniam es"' => array(), + 'xxxxx "minim am veniam es" OR dolore' => array(), + 'xx "minim am veniam es" OR dolore' => array(), + // Mixed queries. + '"am minim veniam es" OR dolore' => array(2, 6, 7), + '"am minim veniam" -"dolore cillum"' => array(5, 6, 7), + ); + foreach ($queries as $query => $results) { + $response = $this->solr->search($query, array()); + $this->_testQueryMatching($query, $response->response->docs, $results); + //@TODO: We might get to this later + #$this->_testQueryScores($query, $response->responses->docs, $results); + } + } + + /** + * Test the matching abilities of the engine. + * + * Verify if a query produces the correct results. + */ + function _testQueryMatching($query, $set, $results) { + // Get result IDs. + $found = array(); + foreach ($set as $item) { + $found[] = $item->entity_id; + } + // Compare $results and $found. + sort($found); + sort($results); + $this->assertEqual($found, $results, strtr("Query matching '$query' found: @found expected: @expected", array('@found' => implode(',', $found), '@expected' => implode(',', $results)))); + } + + /** + * Test the scoring abilities of the engine. + * + * Verify if a query produces normalized, monotonous scores. + */ + function _testQueryScores($query, $set, $results) { + // Get result scores. + $scores = array(); + foreach ($set as $item) { + $scores[] = $item->score; + } + + // Check order. + $sorted = $scores; + sort($sorted); + $this->assertEqual($scores, array_reverse($sorted), "Query order '$query'"); + + // Check range. + $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'"); + } + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrext/solrext.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrext/solrext.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,11 @@ +p.solrext-help, +div.solrext { +display: block; +color: white; +padding: 5px; +background-color: black; +border: 4px solid white; +-webkit-border-radius: 8px; +-moz-border-radius: 8px; +border-radius: 8px; +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrext/solrext.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrext/solrext.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,4 @@ +name = solrext +description = search an solr index +core = 7.x +stylesheets[all][] = solrext.css diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrext/solrext.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrext/solrext.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,261 @@ + 'item', + '#title' => t('Form ID'), + '#markup' => $form_id, + '#theme_wrappers' => array('container__solrext__form'), + '#attributes' => array('class' => array('solrext')), + '#weight' => -100, + ); + //debug($form, $form_id, TRUE); +} + + +/** + * Implements hook_help(). + */ +function solrext_help($path, $arg) { + switch ($path) { + case 'admin/structure/block': + return t('This site has stuff!'); + case 'admin/appearance':{ + return _solrext_help_admin_appearance(); + }} +} + + + + + +/** + * Help text for the admin/appearance page. + */ +function _solrext_help_admin_appearance() { + $output = ''; + $data = solrext_stats_enabled_themes(); + $output .= format_plural( + $data['num_hidden'], + 'There is one hidden theme.', + 'There are @count hidden themes.' + ); + return theme('solrext_help', array('text' => $output)); +} + +/** + * Implements hook_menu(). + */ +function solrext_menu() { + $items['admin/reports/solrtext'] = array( + 'title' => 'X-ray technical site overview', + 'description' => 'See the internal structure of this site.', + 'page callback' => 'solrtext_overview_page', + 'access callback' => TRUE, + 'access arguments' => array('access site reports'), + ); + + $items['admin/reports/solrtext/overview'] = array( + 'title' => 'Overview', + 'description' => "Technical overview of the site's internals.", + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + + $items['admin/reports/solrtext/overview2'] = array( + 'title' => 'Overview2', + 'description' => "Technical overview of the site's internals.", + 'type' => MENU_LOCAL_TASK, + 'weight' => -1, + 'page callback' => 'solrext_permission_names_page', + 'access arguments' => array('access site reports'), + ); + + + return $items; +} + + +function solrtext_overview_page(){ + return "XX"; +} + + + +/** + * Display the X-ray permission names page. + */ +function solrext_permission_names_page_OLD() { + + $names = solrext_permission_names(); + //debug($names); + return theme('solrext_permission_names', array('names' => $names)); +} +/** + * Collect permission names. + */ +function solrext_permission_names() { + + $names = array(); + $permissions = module_invoke_all('permission'); + // Extract just the permission title from each permission array. + + foreach ($permissions as $machine_name => $permission) { + $names[$machine_name] = $permission['title']; + } + // Put permission names in alphabetical order by title. + asort($names); + + return $names; +} +/** + * Returns HTML of permission machine and display names in a table. + * + * @param $variables + * An associative array containing: + * - names: Array of human-readable names keyed by machine names. + * + * @ingroup themeable + */ +function theme_solrext_permission_names($variables) { + $names = $variables['names']; + $output = ''; + $header = array(t('Permission title'), t('Permission machine name')); + $rows = array(); + foreach ($names as $machine_name => $title) { + $rows[] = array($title, $machine_name); + } + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => + array('id' => 'solrext-permission-names'))); + return $output; + + + + /** + * Fetch information about themes. + */ + function solrext_stats_enabled_themes() { + $themes = list_themes(); + $num_themes = count($themes); + // Initialize variables for the data you will collect. + $num_hidden = 0; // Number of hidden themes. + $num_enabled = 0; + $summaries = array(); + // Iterate through each theme, gathering data that you care about. + foreach ($themes as $themename => $theme) { + // Do not gather statistics for hidden themes, but keep a count of them. + if (isset($theme->info['hidden']) && $theme->info['hidden']) { + $num_hidden++; + } + else { // This is a visible theme. + if ($theme->status) { + $num_enabled++; + // This is an enabled theme, provide more stats. + $summaries[$theme->info['name']] = array( + 'regions' => count($theme->info['regions']), + 'overlay_regions' => count($theme->info['overlay_regions']), + 'regions_hidden' => count($theme->info['regions_hidden']), + 'features' => count($theme->info['features']), + 'kindsofstylesheets' => count($theme->info['stylesheets']), + 'allstylesheets' => isset($theme->info['stylesheets']['all']) ? count($theme->info['stylesheets']['all']) : 0, + ); + } + } + } + return compact('num_themes', 'num_hidden', 'num_enabled', 'summaries'); + } +} + + +/** + * Implements hook_theme(). + */ +function solrext_theme() { + + return array( + 'solrext_permission_names' => array( + 'render element' => 'names', + ), + 'solrext_show_page_callback' => array( + 'variables' => array( + 'page_callback' => NULL, + 'include_file' => NULL, + 'page_arguments' => NULL, + ), + ), + ); +} + + +/** + * Display permission machine and display names in a table. + * + * @return + * An array as expected by drupal_render(). + */ +function solrext_permission_names_page() { + $build = array(); + // Gather data, an array of human-readable names keyed by machine names. + $names = solrext_permission_names(); + // Format the data as a table. + $header = array(t('Permission title'), t('Permission machine name')); + $rows = array(); + foreach ($names as $machine_name => $title) { + $rows[] = array($title, $machine_name); + } + $build['names_table'] = array( + '#theme' => 'table__solrext__permission_names', + '#header' => $header, + '#rows' => $rows, + '#attributes' => array('id' => 'solrext-permission-names') + ); + return $build; +} + + +/** + * Provide the page callback function (and other router item information). + */ +function solrext_show_page_callback() { + debug("XX"); + // Do not hand in the path; menu_get_item() finds dynamic paths on its own + // but fails if handed help's $path variable which is node/% for node/1. + $router_item = menu_get_item(); + // menu_get_item() can return null when called via drush command line. + if ($router_item) { + return theme('solrext_show_page_callback', $router_item); + } +} +/** + * Theme the page callback and optionally other elements of a router item. + */ +function theme_solrext_show_page_callback($variables) { + extract($variables, EXTR_SKIP); + $output = ''; + $output .= '

'; + $output .= t('This page is brought to you by '); + if ($page_arguments) { + foreach ($page_arguments as $key => $value) { + $page_arguments[$key] = drupal_placeholder($value); + } + $output .= format_plural(count($page_arguments), + 'the argument !arg handed to ', + 'the arguments !arg handed to ', + array('!arg' => solrext_oxford_comma_list($page_arguments)) + ); + } + $output .= t('the function %func', + array('%func' => $page_callback . '()')); + if ($include_file) { + $output .= t(' and the included file %file', + array('%file' => $include_file)); + } + $output .= '.

'; + return $output; +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/Apache_Solr_Document.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/Apache_Solr_Document.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,410 @@ + + */ + +/** + * Additional code Copyright (c) 2011 by Peter Wolanin, and + * additional contributors. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program as the file LICENSE.txt; if not, please see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + */ + +/** + * Holds Key / Value pairs that represent a Solr Document along with any associated boost + * values. Field values can be accessed by direct dereferencing such as: + * + * @code + * $document->title = 'Something'; + * echo $document->title; + * + * Additionally, the field values can be iterated with foreach + * + * @code + * foreach ($document as $fieldName => $fieldValue) { + * ... + * } + * + */ +class ApacheSolrDocument implements IteratorAggregate { + + /** + * Document boost value + * + * @var float + */ + protected $_documentBoost = FALSE; + + /** + * Document field values, indexed by name + * + * @var array + */ + protected $_fields = array(); + + /** + * Document field boost values, indexed by name + * + * @var array array of floats + */ + protected $_fieldBoosts = array(); + + /** + * Clear all boosts and fields from this document + */ + public function clear() { + $this->_documentBoost = FALSE; + + $this->_fields = array(); + $this->_fieldBoosts = array(); + } + + /** + * Get current document boost + * + * @return mixed + * will be false for default, or else a float + */ + public function getBoost() { + return $this->_documentBoost; + } + + /** + * Set document boost factor + * + * @param mixed $boost + * Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setBoost($boost) { + $boost = (float) $boost; + + if ($boost > 0.0) { + $this->_documentBoost = $boost; + } + else { + $this->_documentBoost = FALSE; + } + } + + /** + * Add a value to a multi-valued field + * + * NOTE: the solr XML format allows you to specify boosts + * PER value even though the underlying Lucene implementation + * only allows a boost per field. To remedy this, the final + * field boost value will be the product of all specified boosts + * on field values - this is similar to SolrJ's functionality. + * + * @code + * $doc = new ApacheSolrDocument(); + * $doc->addField('foo', 'bar', 2.0); + * $doc->addField('foo', 'baz', 3.0); + * // resultant field boost will be 6! + * echo $doc->getFieldBoost('foo'); + * + * @param string $key + * @param mixed $value + * @param mixed $boost + * Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function addField($key, $value, $boost = FALSE) { + if (!isset($this->_fields[$key])) { + // create holding array if this is the first value + $this->_fields[$key] = array(); + } + else if (!is_array($this->_fields[$key])) { + // move existing value into array if it is not already an array + $this->_fields[$key] = array($this->_fields[$key]); + } + + if ($this->getFieldBoost($key) === FALSE) { + // boost not already set, set it now + $this->setFieldBoost($key, $boost); + } + else if ((float) $boost > 0.0) { + // multiply passed boost with current field boost - similar to SolrJ implementation + $this->_fieldBoosts[$key] *= (float) $boost; + } + + // add value to array + $this->_fields[$key][] = $value; + } + + /** + * Handle the array manipulation for a multi-valued field + * + * @param string $key + * @param string $value + * @param mixed $boost + * Use false for default boost, else cast to float that should be > 0 or will be treated as false + * + * @deprecated Use addField(...) instead + */ + public function setMultiValue($key, $value, $boost = FALSE) { + $this->addField($key, $value, $boost); + } + + /** + * Get field information + * + * @param string $key + * @return mixed associative array of info if field exists, false otherwise + */ + public function getField($key) { + if (isset($this->_fields[$key])) { + return array( + 'name' => $key, + 'value' => $this->_fields[$key], + 'boost' => $this->getFieldBoost($key) + ); + } + + return FALSE; + } + + /** + * Set a field value. Multi-valued fields should be set as arrays + * or instead use the addField(...) function which will automatically + * make sure the field is an array. + * + * @param string $key + * @param mixed $value + * @param mixed $boost + * Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setField($key, $value, $boost = FALSE) { + $this->_fields[$key] = $value; + $this->setFieldBoost($key, $boost); + } + + /** + * Get the currently set field boost for a document field + * + * @param string $key + * @return float + * currently set field boost, false if one is not set + */ + public function getFieldBoost($key) { + return isset($this->_fieldBoosts[$key]) ? $this->_fieldBoosts[$key] : FALSE; + } + + /** + * Set the field boost for a document field + * + * @param string $key + * field name for the boost + * @param mixed $boost + * Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setFieldBoost($key, $boost) { + $boost = (float) $boost; + + if ($boost > 0.0) { + $this->_fieldBoosts[$key] = $boost; + } + else { + $this->_fieldBoosts[$key] = FALSE; + } + } + + /** + * Return current field boosts, indexed by field name + * + * @return array + */ + public function getFieldBoosts() { + return $this->_fieldBoosts; + } + + /** + * Get the names of all fields in this document + * + * @return array + */ + public function getFieldNames() { + return array_keys($this->_fields); + } + + /** + * Get the values of all fields in this document + * + * @return array + */ + public function getFieldValues() { + return array_values($this->_fields); + } + + /** + * IteratorAggregate implementation function. Allows usage: + * + * @code + * foreach ($document as $key => $value) { + * ... + * } + * + */ + public function getIterator() { + $arrayObject = new ArrayObject($this->_fields); + + return $arrayObject->getIterator(); + } + + /** + * Magic get for field values + * + * @param string $key + * @return mixed + */ + public function __get($key) { + return $this->_fields[$key]; + } + + /** + * Magic set for field values. Multi-valued fields should be set as arrays + * or instead use the addField(...) function which will automatically + * make sure the field is an array. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) { + $this->setField($key, $value); + } + + /** + * Magic isset for fields values. Do not call directly. Allows usage: + * + * @code + * isset($document->some_field); + * + * @param string $key + * @return boolean + * Whether the given key is set in the document + */ + public function __isset($key) { + return isset($this->_fields[$key]); + } + + /** + * Magic unset for field values. Do not call directly. Allows usage: + * + * @code + * unset($document->some_field); + * + * @param string $key + */ + public function __unset($key) { + unset($this->_fields[$key]); + unset($this->_fieldBoosts[$key]); + } + + /** + * Create an XML fragment from a ApacheSolrDocument instance appropriate for use inside a Solr add call + * + * @param ApacheSolrDocument $document + * + * @return string + * an xml formatted string from the given document + */ + public static function documentToXml(ApacheSolrDocument $document) { + $xml = 'getBoost() !== FALSE) { + $xml .= ' boost="' . $document->getBoost() . '"'; + } + + $xml .= '>'; + + foreach ($document as $key => $value) { + $key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); + $fieldBoost = $document->getFieldBoost($key); + + if (is_array($value)) { + foreach ($value as $multivalue) { + $xml .= ''; + } + } + else { + $xml .= ''; + } + } + + $xml .= ''; + + // Remove any control characters to avoid Solr XML parser exception + return self::stripCtrlChars($xml); + } + + /** + * Replace control (non-printable) characters from string that are invalid to Solr's XML parser with a space. + * + * @param string $string + * @return string + */ + public static function stripCtrlChars($string) { + // See: http://w3.org/International/questions/qa-forms-utf-8.html + // Printable utf-8 does not include any of these chars below x7F + return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $string); + } +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/Drupal_Apache_Solr_Service.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/Drupal_Apache_Solr_Service.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,913 @@ + + */ + +/** + * Additional code Copyright (c) 2008-2011 by Robert Douglass, James McKinney, + * Jacob Singh, Alejandro Garza, Peter Wolanin, and additional contributors. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program as the file LICENSE.txt; if not, please see + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + */ + +/** + * Starting point for the Solr API. Represents a Solr server resource and has + * methods for pinging, adding, deleting, committing, optimizing and searching. + */ + +class DrupalsolrsearchService implements DrupalApacheSolrServiceInterface { + /** + * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values + * are 'map' (default) or 'flat'. + * + */ + const NAMED_LIST_FORMAT = 'map'; + + /** + * Servlet mappings + */ + const PING_SERVLET = 'admin/ping'; + const UPDATE_SERVLET = 'update'; + const SEARCH_SERVLET = 'select'; + const LUKE_SERVLET = 'admin/luke'; + const SYSTEM_SERVLET = 'admin/system'; + const STATS_SERVLET = 'admin/stats.jsp'; + const STATS_SERVLET_4 = 'admin/mbeans?wt=xml&stats=true'; + + /** + * Server url + * + * @var array + */ + protected $parsed_url; + + /** + * Constructed servlet full path URLs + * + * @var string + */ + protected $update_url; + + /** + * Default HTTP timeout when one is not specified (initialized to default_socket_timeout ini setting) + * + * var float + */ + protected $_defaultTimeout; + protected $env_id; + protected $luke; + protected $stats; + protected $system_info; + + /** + * Flag that denotes whether to use soft commits for Solr 4.x, defaults to FALSE. + * + * @var bool + */ + protected $soft_commit = FALSE; + + /** + * Call the /admin/ping servlet, to test the connection to the server. + * + * @param $timeout + * maximum time to wait for ping in seconds, -1 for unlimited (default 2). + * @return + * (float) seconds taken to ping the server, FALSE if timeout occurs. + */ + public function ping($timeout = 2) { + $start = microtime(TRUE); + + if ($timeout <= 0.0) { + $timeout = -1; + } + $pingUrl = $this->_constructUrl(self::PING_SERVLET); + // Attempt a HEAD request to the solr ping url. + $options = array( + 'method' => 'HEAD', + 'timeout' => $timeout, + ); + $response = $this->_makeHttpRequest($pingUrl, $options); + + if ($response->code == 200) { + // Add 0.1 ms to the ping time so we never return 0.0. + return microtime(TRUE) - $start + 0.0001; + } + else { + return FALSE; + } + } + + /** + * Flags whether to use soft commits for Solr 4.x. + * + * @param bool $soft_commit + * Whether or not to use soft commits for Solr 4.x. + */ + public function setSoftCommit($soft_commit) { + $this->soft_commit = (bool) $soft_commit; + } + + /** + * Returns the flag that denotes whether to use soft commits for Solr 4.x. + * + * @return bool + * Whether to use soft commits for Solr 4.x. + */ + public function getSoftCommit() { + return $this->soft_commit; + } + + /** + * Call the /admin/system servlet + * + * @return + * (array) With all the system info + */ + protected function setSystemInfo() { + $url = $this->_constructUrl(self::SYSTEM_SERVLET, array('wt' => 'json')); + if ($this->env_id) { + $this->system_info_cid = $this->env_id . ":system:" . drupal_hash_base64($url); + $cache = cache_get($this->system_info_cid, 'cache_solrsearch'); + if (isset($cache->data)) { + $this->system_info = json_decode($cache->data); + } + } + // Second pass to populate the cache if necessary. + if (empty($this->system_info)) { + $response = $this->_sendRawGet($url); + $this->system_info = json_decode($response->data); + if ($this->env_id) { + cache_set($this->system_info_cid, $response->data, 'cache_solrsearch'); + } + } + } + + /** + * Get information about the Solr Core. + * + * @return + * (string) system info encoded in json + */ + public function getSystemInfo() { + if (!isset($this->system_info)) { + $this->setSystemInfo(); + } + return $this->system_info; + } + + /** + * Sets $this->luke with the meta-data about the index from admin/luke. + */ + protected function setLuke($num_terms = 0) { + if (empty($this->luke[$num_terms])) { + $params = array( + 'numTerms' => "$num_terms", + 'wt' => 'json', + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + $url = $this->_constructUrl(self::LUKE_SERVLET, $params); + if ($this->env_id) { + $cid = $this->env_id . ":luke:" . drupal_hash_base64($url); + $cache = cache_get($cid, 'cache_solrsearch'); + if (isset($cache->data)) { + $this->luke = $cache->data; + } + } + } + // Second pass to populate the cache if necessary. + if (empty($this->luke[$num_terms])) { + $this->luke[$num_terms] = $this->_sendRawGet($url); + if ($this->env_id) { + cache_set($cid, $this->luke, 'cache_solrsearch'); + } + } + } + + /** + * Get just the field meta-data about the index. + */ + public function getFields($num_terms = 0) { + return $this->getLuke($num_terms)->fields; + } + + /** + * Get meta-data about the index. + */ + public function getLuke($num_terms = 0) { + if (!isset($this->luke[$num_terms])) { + $this->setLuke($num_terms); + } + return $this->luke[$num_terms]; + } + + /** + * Get the current solr version. This could be 1, 3 or 4 + * + * @return int + * 1, 3 or 4. Does not give a more details version, for that you need + * to get the system info. + */ + public function getSolrVersion() { + $system_info = $this->getSystemInfo(); + // Get our solr version number + if (isset($system_info->lucene->{'solr-spec-version'})) { + return $system_info->lucene->{'solr-spec-version'}[0]; + } + return 0; + } + + /** + * Sets $this->stats with the information about the Solr Core form + */ + protected function setStats() { + $data = $this->getLuke(); + $solr_version = $this->getSolrVersion(); + // Only try to get stats if we have connected to the index. + if (empty($this->stats) && isset($data->index->numDocs)) { + if ($solr_version >= 4) { + $url = $this->_constructUrl(self::STATS_SERVLET_4); + } + else { + $url = $this->_constructUrl(self::STATS_SERVLET); + } + if ($this->env_id) { + $this->stats_cid = $this->env_id . ":stats:" . drupal_hash_base64($url); + $cache = cache_get($this->stats_cid, 'cache_solrsearch'); + if (isset($cache->data)) { + $this->stats = simplexml_load_string($cache->data); + } + } + // Second pass to populate the cache if necessary. + if (empty($this->stats)) { + $response = $this->_sendRawGet($url); + $this->stats = simplexml_load_string($response->data); + if ($this->env_id) { + cache_set($this->stats_cid, $response->data, 'cache_solrsearch'); + } + } + } + } + + /** + * Get information about the Solr Core. + * + * Returns a Simple XMl document + */ + public function getStats() { + if (!isset($this->stats)) { + $this->setStats(); + } + return $this->stats; + } + + /** + * Get summary information about the Solr Core. + */ + public function getStatsSummary() { + $stats = $this->getStats(); + $solr_version = $this->getSolrVersion(); + + $summary = array( + '@pending_docs' => '', + '@autocommit_time_seconds' => '', + '@autocommit_time' => '', + '@deletes_by_id' => '', + '@deletes_by_query' => '', + '@deletes_total' => '', + '@schema_version' => '', + '@core_name' => '', + '@index_size' => '', + ); + + if (!empty($stats)) { + if ($solr_version <= 3) { + $docs_pending_xpath = $stats->xpath('//stat[@name="docsPending"]'); + $summary['@pending_docs'] = (int) trim(current($docs_pending_xpath)); + $max_time_xpath = $stats->xpath('//stat[@name="autocommit maxTime"]'); + $max_time = (int) trim(current($max_time_xpath)); + // Convert to seconds. + $summary['@autocommit_time_seconds'] = $max_time / 1000; + $summary['@autocommit_time'] = format_interval($max_time / 1000); + $deletes_id_xpath = $stats->xpath('//stat[@name="deletesById"]'); + $summary['@deletes_by_id'] = (int) trim(current($deletes_id_xpath)); + $deletes_query_xpath = $stats->xpath('//stat[@name="deletesByQuery"]'); + $summary['@deletes_by_query'] = (int) trim(current($deletes_query_xpath)); + $summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query']; + $schema = $stats->xpath('/solr/schema[1]'); + $summary['@schema_version'] = trim($schema[0]); + $core = $stats->xpath('/solr/core[1]'); + $summary['@core_name'] = trim($core[0]); + $size_xpath = $stats->xpath('//stat[@name="indexSize"]'); + $summary['@index_size'] = trim(current($size_xpath)); + } + else { + $system_info = $this->getSystemInfo(); + $docs_pending_xpath = $stats->xpath('//lst["stats"]/long[@name="docsPending"]'); + $summary['@pending_docs'] = (int) trim(current($docs_pending_xpath)); + $max_time_xpath = $stats->xpath('//lst["stats"]/str[@name="autocommit maxTime"]'); + $max_time = (int) trim(current($max_time_xpath)); + // Convert to seconds. + $summary['@autocommit_time_seconds'] = $max_time / 1000; + $summary['@autocommit_time'] = format_interval($max_time / 1000); + $deletes_id_xpath = $stats->xpath('//lst["stats"]/long[@name="deletesById"]'); + $summary['@deletes_by_id'] = (int) trim(current($deletes_id_xpath)); + $deletes_query_xpath = $stats->xpath('//lst["stats"]/long[@name="deletesByQuery"]'); + $summary['@deletes_by_query'] = (int) trim(current($deletes_query_xpath)); + $summary['@deletes_total'] = $summary['@deletes_by_id'] + $summary['@deletes_by_query']; + $schema = $system_info->core->schema; + $summary['@schema_version'] = $schema; + $core = $stats->xpath('//lst["core"]/str[@name="coreName"]'); + $summary['@core_name'] = trim(current($core)); + $size_xpath = $stats->xpath('//lst["core"]/str[@name="indexSize"]'); + $summary['@index_size'] = trim(current($size_xpath)); + } + } + + return $summary; + } + + /** + * Clear cached Solr data. + */ + public function clearCache() { + // Don't clear cached data if the server is unavailable. + if (@$this->ping()) { + $this->_clearCache(); + } + else { + throw new Exception('No Solr instance available when trying to clear the cache.'); + } + } + + protected function _clearCache() { + if ($this->env_id) { + cache_clear_all($this->env_id . ":stats:", 'cache_solrsearch', TRUE); + cache_clear_all($this->env_id . ":luke:", 'cache_solrsearch', TRUE); + } + $this->luke = array(); + $this->stats = NULL; + } + + /** + * Constructor + * + * @param $url + * The URL to the Solr server, possibly including a core name. E.g. http://localhost:8983/solr/ + * or https://search.example.com/solr/core99/ + * @param $env_id + * The machine name of a corresponding saved configuration used for loading + * data like which facets are enabled. + */ + public function __construct($url, $env_id = NULL) { + $this->env_id = $env_id; + $this->setUrl($url); + + // determine our default http timeout from ini settings + $this->_defaultTimeout = (int) ini_get('default_socket_timeout'); + + // double check we didn't get 0 for a timeout + if ($this->_defaultTimeout <= 0) { + $this->_defaultTimeout = 60; + } + } + + function getId() { + return $this->env_id; + } + + /** + * Check the reponse code and thow an exception if it's not 200. + * + * @param stdClass $response + * response object. + * + * @return + * response object + * @thows Exception + */ + protected function checkResponse($response) { + $code = (int) $response->code; + if ($code != 200) { + if ($code >= 400 && $code != 403 && $code != 404) { + // Add details, like Solr's exception message. + $response->status_message .= $response->data; + } + throw new Exception('"' . $code . '" Status: ' . $response->status_message); + } + return $response; + } + + /** + * Make a request to a servlet (a path) that's not a standard path. + * + * @param string $servlet + * A path to be added to the base Solr path. e.g. 'extract/tika' + * + * @param array $params + * Any request parameters when constructing the URL. + * + * @param array $options + * @see drupal_http_request() $options. + * + * @return + * response object + * + * @thows Exception + */ + public function makeServletRequest($servlet, $params = array(), $options = array()) { + // Add default params. + $params += array( + 'wt' => 'json', + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + + $url = $this->_constructUrl($servlet, $params); + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making a GET operation against this Solr Server + */ + protected function _sendRawGet($url, $options = array()) { + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making a POST operation against this Solr Server + */ + protected function _sendRawPost($url, $options = array()) { + $options['method'] = 'POST'; + // Normally we use POST to send XML documents. + if (!isset($options['headers']['Content-Type'])) { + $options['headers']['Content-Type'] = 'text/xml; charset=UTF-8'; + } + $response = $this->_makeHttpRequest($url, $options); + return $this->checkResponse($response); + } + + /** + * Central method for making the actual http request to the Solr Server + * + * This is just a wrapper around drupal_http_request(). + */ + protected function _makeHttpRequest($url, array $options = array()) { + if (!isset($options['method']) || $options['method'] == 'GET' || $options['method'] == 'HEAD') { + // Make sure we are not sending a request body. + $options['data'] = NULL; + } + + $result = drupal_http_request($url, $options); + + if (!isset($result->code) || $result->code < 0) { + $result->code = 0; + $result->status_message = 'Request failed'; + $result->protocol = 'HTTP/1.0'; + } + // Additional information may be in the error property. + if (isset($result->error)) { + $result->status_message .= ': ' . check_plain($result->error); + } + + if (!isset($result->data)) { + $result->data = ''; + $result->response = NULL; + } + else { + $response = json_decode($result->data); + if (is_object($response)) { + foreach ($response as $key => $value) { + $result->$key = $value; + } + } + } + return $result; + } + + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * NOTE: inside a phrase fewer characters need escaped, use {@link DrupalsolrsearchService::escapePhrase()} instead + * + * @param string $value + * @return string + */ + static public function escape($value) + { + //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters + $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Escape a value meant to be contained in a phrase for special query characters + * + * @param string $value + * @return string + */ + static public function escapePhrase($value) + { + $pattern = '/("|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Convenience function for creating phrase syntax from a value + * + * @param string $value + * @return string + */ + static public function phrase($value) + { + return '"' . self::escapePhrase($value) . '"'; + } + + /** + * Return a valid http URL given this server's host, port and path and a provided servlet name + * + * @param $servlet + * A string path to a Solr request handler. + * @param $params + * @param $parsed_url + * A url to use instead of the stored one. + * + * @return string + */ + protected function _constructUrl($servlet, $params = array(), $added_query_string = NULL) { + // PHP's built in http_build_query() doesn't give us the format Solr wants. + $query_string = $this->httpBuildQuery($params); + + if ($query_string) { + $query_string = '?' . $query_string; + if ($added_query_string) { + $query_string = $query_string . '&' . $added_query_string; + } + } + elseif ($added_query_string) { + $query_string = '?' . $added_query_string; + } + + $url = $this->parsed_url; + return $url['scheme'] . $url['user'] . $url['pass'] . $url['host'] . $url['port'] . $url['path'] . $servlet . $query_string; + } + + /** + * Get the Solr url + * + * @return string + */ + public function getUrl() { + return $this->_constructUrl(''); + } + + /** + * Set the Solr url. + * + * @param $url + * + * @return $this + */ + public function setUrl($url) { + $parsed_url = parse_url($url); + + if (!isset($parsed_url['scheme'])) { + $parsed_url['scheme'] = 'http'; + } + $parsed_url['scheme'] .= '://'; + + if (!isset($parsed_url['user'])) { + $parsed_url['user'] = ''; + } + else { + $parsed_url['host'] = '@' . $parsed_url['host']; + } + $parsed_url['pass'] = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; + $parsed_url['port'] = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; + + if (isset($parsed_url['path'])) { + // Make sure the path has a single leading/trailing slash. + $parsed_url['path'] = '/' . ltrim($parsed_url['path'], '/'); + $parsed_url['path'] = rtrim($parsed_url['path'], '/') . '/'; + } + else { + $parsed_url['path'] = '/'; + } + // For now we ignore query and fragment. + $this->parsed_url = $parsed_url; + // Force the update url to be rebuilt. + unset($this->update_url); + return $this; + } + + /** + * Raw update Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed xml document. + * + * @param string $rawPost + * @param float $timeout Maximum expected duration (in seconds) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function update($rawPost, $timeout = FALSE) { + // @todo: throw exception if updates are disabled. + if (empty($this->update_url)) { + // Store the URL in an instance variable since many updates may be sent + // via a single instance of this class. + $this->update_url = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => 'json')); + } + $options['data'] = $rawPost; + if ($timeout) { + $options['timeout'] = $timeout; + } + return $this->_sendRawPost($this->update_url, $options); + } + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of solrsearchDocument instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * + * @return response objecte + * + * @throws Exception If an error occurs during the service call + */ + public function addDocuments($documents, $overwrite = NULL, $commitWithin = NULL) { + $attr = ''; + + if (isset($overwrite)) { + $attr .= ' overwrite="' . empty($overwrite) ? 'false"' : 'true"'; + } + if (isset($commitWithin)) { + $attr .= ' commitWithin="' . intval($commitWithin) . '"'; + } + + $rawPost = ""; + foreach ($documents as $document) { + if (is_object($document) && ($document instanceof solrsearchDocument)) { + $rawPost .= solrsearchDocument::documentToXml($document); + } + } + $rawPost .= ''; + + return $this->update($rawPost); + } + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $optimize Defaults to true + * optimizes the index files. Only valid for solr versions <= 3 + * @param boolean $waitFlush + * block until index changes are flushed to disk. Only valid for solr versions <= 3 + * @param boolean $waitSearcher + * block until a new searcher is opened and registered as the main query searcher, making the changes visible. + * @param float $timeout + * Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) { + $optimizeValue = $optimize ? 'true' : 'false'; + $flushValue = $waitFlush ? 'true' : 'false'; + $searcherValue = $waitSearcher ? 'true' : 'false'; + $softCommit = $this->soft_commit ? 'true' : 'false'; + + $solr_version = $this->getSolrVersion(); + if ($solr_version <= 3) { + $rawPost = ''; + } + else { + $rawPost = ''; + } + + $response = $this->update($rawPost, $timeout); + $this->_clearCache(); + return $response; + } + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteById($id, $timeout = 3600) { + return $this->deleteByMultipleIds(array($id), $timeout); + } + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteByMultipleIds($ids, $timeout = 3600) { + $rawPost = ''; + + foreach ($ids as $id) { + $rawPost .= '' . htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8') . ''; + } + $rawPost .= ''; + + return $this->update($rawPost, $timeout); + } + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return stdClass response object + * + * @throws Exception If an error occurs during the service call + */ + public function deleteByQuery($rawQuery, $timeout = 3600) { + $rawPost = '' . htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8') . ''; + + return $this->update($rawPost, $timeout); + } + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * block until index changes are flushed to disk Removed in Solr 4.0 + * @param boolean $waitSearcher + * block until a new searcher is opened and registered as the main query searcher, making the changes visible. + * @param float $timeout + * Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function optimize($waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600) { + $flushValue = $waitFlush ? 'true' : 'false'; + $searcherValue = $waitSearcher ? 'true' : 'false'; + $softCommit = $this->soft_commit ? 'true' : 'false'; + + $solr_version = $this->getSolrVersion(); + if ($solr_version <= 3) { + $rawPost = ''; + } + else { + $rawPost = ''; + } + + return $this->update($rawPost, $timeout); + } + + /** + * Like PHP's built in http_build_query(), but uses rawurlencode() and no [] for repeated params. + */ + protected function httpBuildQuery(array $query, $parent = '') { + $params = array(); + + foreach ($query as $key => $value) { + $key = ($parent ? $parent : rawurlencode($key)); + + // Recurse into children. + if (is_array($value)) { + $params[] = $this->httpBuildQuery($value, $key); + } + // If a query parameter value is NULL, only append its key. + elseif (!isset($value)) { + $params[] = $key; + } + else { + $params[] = $key . '=' . rawurlencode($value); + } + } + + return implode('&', $params); + } + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + public function search($query = '', array $params = array(), $method = 'GET') { + // Always use JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning + + + + if (!user_access("view restricted content")){ + $params['fq'][]='access-type:free'; + + + } + + $params['wt'] = 'json'; + // Additional default params. + $params += array( + 'json.nl' => self::NAMED_LIST_FORMAT, + ); + if ($query) { + $params['q'] = $query; + } + // PHP's built in http_build_query() doesn't give us the format Solr wants. + $queryString = $this->httpBuildQuery($params); + // Check string length of the query string, change method to POST + $len = strlen($queryString); + // Fetch our threshold to find out when to flip to POST + $max_len = solrsearch_environment_variable_get($this->env_id, 'solrsearch_search_post_threshold', 3600); + + // if longer than $max_len (default 3600) characters + // we should switch to POST (a typical server handles 4096 max). + // If this class is used independently (without environments), we switch automatically to POST at an + // limit of 1800 chars. + if (($len > 1800) && (empty($this->env_id) || ($len > $max_len))) { + $method = 'POST'; + } + + if ($method == 'GET') { + $searchUrl = $this->_constructUrl(self::SEARCH_SERVLET, array(), $queryString); + + return $this->_sendRawGet($searchUrl); + } + else if ($method == 'POST') { + $searchUrl = $this->_constructUrl(self::SEARCH_SERVLET); + $options['data'] = $queryString; + $options['headers']['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + return $this->_sendRawPost($searchUrl, $options); + } + else { + throw new Exception("Unsupported method '$method' for search(), use GET or POST"); + } + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/Solr_Base_Query.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/Solr_Base_Query.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,657 @@ + 'is_uid', '#value' => 0) + * for anonymous content. + */ + protected $fields = array(); + + /** + * An array of subqueries. + */ + protected $subqueries = array(); + + function __construct($operator = 'OR') { + $this->operator = $operator; + $this->id = ++SolrFilterSubQuery::$idCount; + } + + function __clone() { + $this->id = ++SolrFilterSubQuery::$idCount; + } + + public function getFilters($name = NULL) { + if (empty($name)) { + return $this->fields; + } + reset($this->fields); + $matches = array(); + foreach ($this->fields as $filter) { + if ($filter['#name'] == $name) { + $matches[] = $filter; + } + } + return $matches; + } + + public function hasFilter($name, $value, $exclude = FALSE) { + foreach ($this->fields as $pos => $values) { + if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) { + return TRUE; + } + } + return FALSE; + } + + public function addFilter($name, $value, $exclude = FALSE, $local = '') { + // @todo - escape the value if it has spaces in it and is not a range query or parenthesized. + $filter = array( + '#exclude' => (bool) $exclude, + '#name' => trim($name), + '#value' => trim($value), + '#local' => trim($local), + ); + $this->fields[] = $filter; + return $this; + } + + public function removeFilter($name, $value = NULL, $exclude = FALSE) { + // Remove from the public list of filters. + $this->unsetFilter($this->fields, $name, $value, $exclude); + return $this; + } + + protected function unsetFilter(&$fields, $name, $value, $exclude) { + if (!isset($value)) { + foreach ($fields as $pos => $values) { + if ($values['#name'] == $name) { + unset($fields[$pos]); + } + } + } + else { + foreach ($fields as $pos => $values) { + if ($values['#name'] == $name && $values['#value'] == $value && $values['#exclude'] == $exclude) { + unset($fields[$pos]); + } + } + } + } + + public function getFilterSubQueries() { + return $this->subqueries; + } + + public function addFilterSubQuery(SolrFilterSubQuery $query) { + $this->subqueries[$query->id] = $query; + return $this; + } + + public function removeFilterSubQuery(SolrFilterSubQuery $query) { + unset($this->subqueries[$query->id]); + return $this; + } + + public function removeFilterSubQueries() { + $this->subqueries = array(); + return $this; + } + + public function makeFilterQuery(array $filter) { + $prefix = empty($filter['#exclude']) ? '' : '-'; + if ($filter['#local']) { + $prefix = '{!' . $filter['#local'] . '}' . $prefix; + } + // If the field value contains a colon or a space, wrap it in double quotes, + // unless it is a range query or is already wrapped in double quotes or + // parentheses. + if (preg_match('/[ :]/', $filter['#value']) && !preg_match('/^[\[\{]\S+ TO \S+[\]\}]$/', $filter['#value']) && !preg_match('/^["\(].*["\)]$/', $filter['#value'])) { + $filter['#value'] = '"' . $filter['#value'] . '"'; + } + return $prefix . $filter['#name'] . ':' . $filter['#value']; + } + + /** + * Make sure our query matches the pattern name:value or name:"value" + * Make sure that if we are ranges we use name:[ AND ] + * allowed inputs : + * a. bundle:article + * b. date:[1970-12-31T23:59:59Z TO NOW] + * Split the text in 4 different parts + * 1. name, eg.: bundle or date + * 2. The first opening bracket (or nothing), eg.: [ + * 3. The value of the field, eg. article or 1970-12-31T23:59:59Z TO NOW + * 4. The last closing bracket, eg.: ] + * @param string $filter + * The filter to validate + * @return boolean + */ + public static function validFilterValue($filter) { + $opening = 0; + $closing = 0; + $name = NULL; + $value = NULL; + + if (preg_match('/(?P[^:]+):(?P.+)?$/', $filter, $matches)) { + foreach ($matches as $match_id => $match) { + switch($match_id) { + case 'name' : + $name = $match; + break; + case 'value' : + $value = $match; + break; + } + } + + // For the name we allow any character that fits between the A-Z0-9 range and + // any alternative for this in other languages. No special characters allowed + if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) { + return FALSE; + } + + // For the value we allow anything that is UTF8 + if (!drupal_validate_utf8($value)) { + return FALSE; + } + + // Check our bracket count. If it does not match it is also not valid + $valid_brackets = TRUE; + $brackets['opening']['{'] = substr_count($value, '{'); + $brackets['closing']['}'] = substr_count($value, '}'); + $valid_brackets = ($brackets['opening']['{'] != $brackets['closing']['}']) ? FALSE : TRUE; + $brackets['opening']['['] = substr_count($value, '['); + $brackets['closing'][']'] = substr_count($value, ']'); + $valid_brackets = ($brackets['opening']['['] != $brackets['closing'][']']) ? FALSE : TRUE; + $brackets['opening']['('] = substr_count($value, '('); + $brackets['closing'][')'] = substr_count($value, ')'); + $valid_brackets = ($brackets['opening']['('] != $brackets['closing'][')']) ? FALSE : TRUE; + if (!$valid_brackets) { + return FALSE; + } + + // Check the date field inputs + if (preg_match('/\[(.+) TO (.+)\]$/', $value, $datefields)) { + // Only Allow a value in the form of + // http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html + // http://lucene.apache.org/solr/api/org/apache/solr/util/DateMathParser.html + // http://wiki.apache.org/solr/SolrQuerySyntax + // 1976-03-06T23:59:59.999Z (valid) + // * (valid) + // 1995-12-31T23:59:59.999Z (valid) + // 2007-03-06T00:00:00Z (valid) + // NOW-1YEAR/DAY (valid) + // NOW/DAY+1DAY (valid) + // 1976-03-06T23:59:59.999Z (valid) + // 1976-03-06T23:59:59.999Z+1YEAR (valid) + // 1976-03-06T23:59:59.999Z/YEAR (valid) + // 1976-03-06T23:59:59.999Z (valid) + // 1976-03-06T23::59::59.999Z (invalid) + if (!empty($datefields[1]) && !empty($datefields[2])) { + // Do not check to full value, only the splitted ones + unset($datefields[0]); + // Check if both matches are valid datefields + foreach ($datefields as $datefield) { + if (!preg_match('/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:[\d\.]{2,6}Z(\S)*)|(^([A-Z\*]+)(\A-Z0-9\+\-\/)*)/', $datefield, $datefield_match)) { + return FALSE; + } + } + } + } + } + return TRUE; + } + + /** + * Builds a set of filter queries from $this->fields and all subqueries. + * + * Returns an array of strings that can be combined into + * a URL query parameter or passed to Solr as fq paramters. + */ + protected function rebuildFq() { + $fq = array(); + foreach ($this->fields as $pos => $field) { + $fq[] = $this->makeFilterQuery($field); + } + foreach ($this->subqueries as $subquery) { + $subfq = $subquery->rebuildFq(); + if ($subfq) { + $operator = $subquery->operator; + $fq[] = "(" . implode(" $operator ", $subfq) . ")"; + } + } + return $fq; + } + +} + +class SolrBaseQuery extends SolrFilterSubQuery implements DrupalSolrQueryInterface { + + /** + * The parameters that get sent to Solr. + */ + protected $params = array('start' => 0, 'rows' => 10, 'fq' => array()); + + /** + * The search base path. + */ + protected $base_path; + protected $field_map = array(); + + /** + * DrupalApacheSolrService object + */ + protected $solr; + // The array keys must always be real Solr index fields. + protected $available_sorts; + + /** + * The query name is used to construct a searcher string. Mostly the + * environment id + */ + protected $name; + protected $context = array(); + // Makes sure we always have a valid sort. + protected $solrsort = array('#name' => 'score', '#direction' => 'desc'); + // A flag to allow the search to be aborted. + public $abort_search = FALSE; + + // A flag to check if need to retrieve another page of the result set + public $page = 0; + + /** + * @param $name + * The search name, used for finding the correct blocks and other config. + * Typically "apachesolr". + * + * @param $solr + * An instantiated DrupalApacheSolrService Object. + * Can be instantiated from apachesolr_get_solr(). + * + * @param $params + * Array of params to initialize the object (typically 'q' and 'fq'). + * + * @param $sortstring + * Visible string telling solr how to sort - added to GET query params. + * + * @param $base_path + * The search base path (without the keywords) for this query, without trailing slash. + */ + function __construct($name, $solr, array $params = array(), $sortstring = '', $base_path = '', $context = array()) { + parent::__construct(); + $this->name = $name; + $this->solr = $solr; + $this->addContext((array) $context); + $this->addParams((array) $params); + $this->available_sorts = $this->defaultSorts(); + $this->sortstring = trim($sortstring); + $this->parseSortString(); + $this->base_path = $base_path; + } + + protected function defaultSorts() { + return array( + 'score' => array('title' => t('Relevancy'), 'default' => 'desc'), + 'title' => array('title' => t('Title'), 'default' => 'asc'), + 'author_s' => array('title' => t('Author'), 'default' => 'asc'), + 'date' => array('title' => t('Date'), 'default' => 'desc'), + ); + } + + /** + * Get query name. + */ + public function getName() { + return $this->name; + } + + /** + * Get query searcher name (for facetapi, views, pages, etc). + */ + public function getSearcher() { + return $this->name . '@' . $this->solr->getId(); + } + + /** + * Get context values. + */ + public function getContext() { + return $this->context; + } + + /** + * Set context value. + */ + public function addContext(array $context) { + foreach ($context as $k => $v) { + $this->context[$k] = $v; + } + // The env_id must match that of the actual $solr object + $this->context['env_id'] = $this->solr->getId(); + return $this->context; + } + + protected $single_value_params = array( + 'q' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q + 'q.op' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q.op + 'q.alt' => TRUE, // http://wiki.apache.org/solr/SearchHandler#q + 'df' => TRUE, + 'qt' => TRUE, + 'defType' => TRUE, + 'timeAllowed' => TRUE, + 'omitHeader' => TRUE, + 'debugQuery' => TRUE, + 'start' => TRUE, + 'rows' => TRUE, + 'stats' => TRUE, + 'facet' => TRUE, + 'facet.prefix' => TRUE, + 'facet.limit' => TRUE, + 'facet.offset' => TRUE, + 'facet.mincount' => TRUE, + 'facet.missing' => TRUE, + 'facet.method' => TRUE, + 'facet.enum.cache.minDf' => TRUE, + 'facet.date.start' => TRUE, + 'facet.date.end' => TRUE, + 'facet.date.gap' => TRUE, + 'facet.date.hardend' => TRUE, + 'facet.date.other' => TRUE, + 'facet.date.include' => TRUE, + 'hl' => TRUE, + 'hl.snippets' => TRUE, + 'hl.fragsize' => TRUE, + 'hl.mergeContiguous' => TRUE, + 'hl.requireFieldMatch' => TRUE, + 'hl.maxAnalyzedChars' => TRUE, + 'hl.alternateField' => TRUE, + 'hl.maxAlternateFieldLength' => TRUE, + 'hl.formatter' => TRUE, + 'hl.simple.pre/hl.simple.post' => TRUE, + 'hl.fragmenter' => TRUE, + 'hl.fragListBuilder' => TRUE, + 'hl.fragmentsBuilder' => TRUE, + 'hl.useFastVectorHighlighter' => TRUE, + 'hl.usePhraseHighlighter' => TRUE, + 'hl.highlightMultiTerm' => TRUE, + 'hl.regex.slop' => TRUE, + 'hl.regex.pattern' => TRUE, + 'hl.regex.maxAnalyzedChars' => TRUE, + 'spellcheck' => TRUE, + ); + + public function getParam($name) { + if ($name == 'fq') { + return $this->rebuildFq(); + } + $empty = isset($this->single_value_params[$name]) ? NULL : array(); + return isset($this->params[$name]) ? $this->params[$name] : $empty; + } + + public function getParams() { + $params = $this->params; + $params['fq'] = $this->rebuildFq(); + return $params; + } + + public function getSolrParams() { + $params = $this->getParams(); + // For certain fields Solr prefers a comma separated list. + foreach (array('fl', 'hl.fl', 'sort', 'mlt.fl') as $name) { + if (isset($params[$name])) { + $params[$name] = implode(',', $params[$name]); + } + } + return $params; + } + + protected function addFq($string, $index = NULL) { + $string = trim($string); + $local = ''; + $exclude = FALSE; + $name = NULL; + $value = NULL; + + // Check if we are dealing with an exclude + if (preg_match('/^-(.*)/', $string, $matches)) { + $exclude = TRUE; + $string = $matches[1]; + } + + // If {!something} is found as first character then this is a local value + if (preg_match('/\{!([^}]+)\}(.*)/', $string, $matches)) { + $local = $matches[1]; + $string = $matches[2]; + } + + // Anything that has a name and value + // check if we have a : in the string + if (strstr($string, ':')) { + list($name, $value) = explode(":", $string, 2); + } + else { + $value = $string; + } + $this->addFilter($name, $value, $exclude, $local); + return $this; + } + + public function addParam($name, $value) { + if (isset($this->single_value_params[$name])) { + if (is_array($value)) { + $value = end($value); + } + $this->params[$name] = $this->normalizeParamValue($value); + return $this; + } + // We never actually populate $this->params['fq']. Instead + // we manage everything via the filter methods. + if ($name == 'fq') { + if (is_array($value)) { + array_walk_recursive($value, array($this, 'addFq')); + return $this; + } + else { + return $this->addFq($value); + } + } + + if (!isset($this->params[$name])) { + $this->params[$name] = array(); + } + + if (!is_array($value)) { + // Convert to array for array_map. + $param_values = array($value); + } + else { + // Convert to a numerically keyed array. + $param_values = array_values($value); + } + $this->params[$name] = array_merge($this->params[$name], array_map(array($this, 'normalizeParamValue'), $param_values)); + + return $this; + } + + protected function normalizeParamValue($value) { + // Convert boolean to string. + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + // Convert to trimmed string. + return trim($value); + } + + public function addParams(Array $params) { + foreach ($params as $name => $value) { + $this->addParam($name, $value); + } + return $this; + } + + public function removeParam($name) { + unset($this->params[$name]); + if ($name == 'fq') { + $this->fields = array(); + $this->subqueries = array(); + } + return $this; + } + + public function replaceParam($name, $value) { + $this->removeParam($name); + return $this->addParam($name, $value); + } + + /** + * Handles aliases for field to make nicer URLs. + * + * @param $field_map + * An array keyed with real Solr index field names with the alias as value. + * + * @return DrupalSolrQueryInterface + * The called object. + */ + public function addFieldAliases($field_map) { + $this->field_map = array_merge($this->field_map, $field_map); + // We have to re-parse the filters. + $this->parseSortString(); + return $this; + } + + public function getFieldAliases() { + return $this->field_map; + } + + public function clearFieldAliases() { + $this->field_map = array(); + // We have to re-parse the filters. + $this->parseSortString(); + return $this; + } + + protected function parseSortString() { + // Substitute any field aliases with real field names. + $sortstring = strtr($this->sortstring, $this->field_map); + // Score is a special case - it's the default sort for Solr. + if ('' == $sortstring || 'score desc' == $sortstring) { + $this->solrsort['#name'] = 'score'; + $this->solrsort['#direction'] = 'desc'; + unset($this->params['sort']); + } + else { + // Validate and set sort parameter + $fields = implode('|', array_keys($this->available_sorts)); + if (preg_match('/^(?:(' . $fields . ') (asc|desc),?)+$/', $sortstring, $matches)) { + // We only use the last match. + $this->solrsort['#name'] = $matches[1]; + $this->solrsort['#direction'] = $matches[2]; + $this->params['sort'] = array($sortstring); + } + } + } + + public function getAvailableSorts() { + return $this->available_sorts; + } + + public function setAvailableSort($name, $sort) { + // We expect non-aliased sorts to be added. + $this->available_sorts[$name] = $sort; + // Re-parse the sortstring. + $this->parseSortString(); + return $this; + } + + public function setAvailableSorts($sorts) { + // We expect a complete array of valid sorts. + $this->available_sorts = $sorts; + $this->parseSortString(); + return $this; + } + + public function removeAvailableSort($name) { + unset($this->available_sorts[$name]); + // Re-parse the sortstring. + $this->parseSortString(); + return $this; + } + + public function getSolrsort() { + return $this->solrsort; + } + + public function setSolrsort($name, $direction) { + $this->sortstring = trim($name) . ' ' . trim($direction); + $this->parseSortString(); + return $this; + } + + public function getPath($new_keywords = NULL) { + if (isset($new_keywords)) { + return $this->base_path . '/' . $new_keywords; + } + elseif ($this->getParam('q')) { + return $this->base_path . '/' . $this->getParam('q'); + } + else { + // Return with empty query (the slash). The path for a facet + // becomes $this->base_path . '//facetinfo'; + // We do this so we can have a consistent way of retrieving the query + + // additional parameters + return $this->base_path . '/'; + } + } + + public function getSolrsortUrlQuery() { + $queryvalues = array(); + $solrsort = $this->solrsort; + if ($solrsort && ($solrsort['#name'] != 'score')) { + if (isset($this->field_map[$solrsort['#name']])) { + $solrsort['#name'] = $this->field_map[$solrsort['#name']]; + } + $queryvalues['solrsort'] = $solrsort['#name'] . ' ' . $solrsort['#direction']; + } + else { + // Return to default relevancy sort. + unset($queryvalues['solrsort']); + } + return $queryvalues; + } + + public function search($keys = NULL) { + if ($this->abort_search) { + return NULL; + } + + return $this->solr->search($keys, $this->getSolrParams()); + } + + public function solr($method) { + return $this->solr->$method(); + } + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/apachesolr.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/apachesolr.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1342 @@ + 'value', + '#value' => $environment['env_id'], + ); + if (isset($environment['export_type']) && $environment['export_type'] == 3) { + $verb = t('Revert'); + } + else { + $verb = t('Delete'); + } + return confirm_form( + $form, + t('Are you sure you want to !verb search environment %name?', array('%name' => $environment['name'], '!verb' => strtolower($verb))), + 'admin/config/search/apachesolr', + t('This action cannot be undone.'), + $verb, + t('Cancel') + ); +} + +/** + * Submit handler for the delete form + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_delete_form_submit(array $form, array &$form_state) { + if (apachesolr_environment_delete($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was deleted')); + } + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; +} + +function apachesolr_environment_edit_delete_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $form_state['values']['env_id'] . '/delete'; + + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Settings page for a specific environment (or default one if not provided) + * + * @param array|bool $environment + * + * @return array Render array for a settings page + */ +function apachesolr_environment_settings_page(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Initializes output with information about which environment's setting we are + // editing, as it is otherwise not transparent to the end user. + $output = array( + 'apachesolr_environment' => array( + '#theme' => 'apachesolr_settings_title', + '#env_id' => $env_id, + ), + ); + $output['form'] = drupal_get_form('apachesolr_environment_edit_form', $environment); + return $output; +} + +/** + * Form to clone a certain environment + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return array output of confirm_form() + */ +function apachesolr_environment_clone_form(array $form, array &$form_state, array $environment) { + $form['env_id'] = array( + '#type' => 'value', + '#value' => $environment['env_id'], + ); + return confirm_form( + $form, + t('Are you sure you want to clone search environment %name?', array('%name' => $environment['name'])), + 'admin/config/search/apachesolr', + '', + t('Clone'), + t('Cancel') + ); +} + +/** + * Submit handler for the clone form + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_clone_form_submit(array $form, array &$form_state) { + if (apachesolr_environment_clone($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was cloned')); + } + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; +} + +/** + * Submit handler for the confirmation page of cloning an environment + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_clone_submit(array $form, array &$form_state) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $form_state['values']['env_id'] . '/clone'; +} + +/** + * Form builder for adding/editing a Solr environment used as a menu callback. + */ +function apachesolr_environment_edit_form(array $form, array &$form_state, array $environment = array()) { + if (empty($environment)) { + $environment = array(); + } + $environment += array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '', 'conf' => array()); + + $form['#environment'] = $environment; + $form['url'] = array( + '#type' => 'textfield', + '#title' => t('Solr server URL'), + '#default_value' => $environment['url'], + '#description' => t('Example: http://localhost:8983/solr'), + '#required' => TRUE, + ); + $is_default = $environment['env_id'] == apachesolr_default_environment(); + $form['make_default'] = array( + '#type' => 'checkbox', + '#title' => t('Make this Solr search environment the default'), + '#default_value' => $is_default, + '#disabled' => $is_default, + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Description'), + '#default_value' => $environment['name'], + '#required' => TRUE, + ); + $form['env_id'] = array( + '#type' => 'machine_name', + '#title' => t('Environment id'), + '#machine_name' => array( + 'exists' => 'apachesolr_environment_load', + ), + '#default_value' => $environment['env_id'], + '#disabled' => !empty($environment['env_id']), // Cannot change it once set. + '#description' => t('Unique, machine-readable identifier for this Solr environment.'), + '#required' => TRUE, + ); + $form['service_class'] = array( + '#type' => 'value', + '#value' => $environment['service_class'], + ); + $form['conf'] = array( + '#tree' => TRUE, + ); + $form['conf']['apachesolr_read_only'] = array( + '#type' => 'radios', + '#title' => t('Index write access'), + '#default_value' => isset($environment['conf']['apachesolr_read_only']) ? $environment['conf']['apachesolr_read_only'] : APACHESOLR_READ_WRITE, + '#options' => array(APACHESOLR_READ_WRITE => t('Read and write (normal)'), APACHESOLR_READ_ONLY => t('Read only')), + '#description' => t('Read only stops this site from sending updates to this search environment. Useful for development sites.'), + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['save'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_submit'), + '#value' => t('Save'), + ); + $form['actions']['save_edit'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_submit'), + '#value' => t('Save and edit'), + ); + $form['actions']['test'] = array( + '#type' => 'submit', + '#validate' => array('apachesolr_environment_edit_validate'), + '#submit' => array('apachesolr_environment_edit_test_submit'), + '#value' => t('Test connection'), + ); + if (!empty($environment['env_id']) && !$is_default) { + $form['actions']['delete'] = array( + '#type' => 'submit', + '#submit' => array('apachesolr_environment_edit_delete_submit'), + '#value' => t('Delete'), + ); + } + + // Ensures destination is an internal URL, builds "cancel" link. + if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { + $destination = $_GET['destination']; + } + else { + $destination = 'admin/config/search/apachesolr/settings'; + } + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => $destination, + ); + + return $form; +} + +/** + * Submit handler for the test button in the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_test_submit(array $form, array &$form_state) { + $ping = apachesolr_server_status($form_state['values']['url'], $form_state['values']['service_class']); + if ($ping) { + drupal_set_message(t('Your site has contacted the Apache Solr server.')); + } + else { + drupal_set_message(t('Your site was unable to contact the Apache Solr server.'), 'error'); + } + $form_state['rebuild'] = TRUE; +} + +/** + * Validate handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_validate(array $form, array &$form_state) { + $parts = parse_url($form_state['values']['url']); + foreach (array('scheme', 'host', 'path') as $key) { + if (empty($parts[$key])) { + form_set_error('url', t('The Solr server URL needs to include a !part', array('!part' => $key))); + } + } + if (isset($parts['port'])) { + // parse_url() should always give an integer for port. Since drupal_http_request() + // also uses parse_url(), we don't need to validate anything except the range. + $pattern = empty($parts['user']) ? '@://[^:]+:([^/]+)@' : '#://[^@]+@[^:]+:([^/]+)#'; + preg_match($pattern, $form_state['values']['url'], $m); + if (empty($m[1]) || !ctype_digit($m[1]) || $m[1] < 1 || $m[1] > 65535) { + form_set_error('port', t('The port has to be an integer between 1 and 65535.')); + } + else { + // Normalize the url by removing extra slashes and whitespace. + $form_state['values']['url'] = trim($form_state['values']['url'], "/ \t\r\n\0\x0B"); + } + } +} + +/** + * Submit handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function apachesolr_environment_edit_submit(array $form, array &$form_state) { + apachesolr_environment_save($form_state['values']); + if (!empty($form_state['values']['make_default'])) { + apachesolr_set_default_environment($form_state['values']['env_id']); + } + cache_clear_all('apachesolr:environments', 'cache_apachesolr'); + drupal_set_message(t('The %name search environment has been saved.', array('%name' => $form_state['values']['name']))); + if ($form_state['values']['op'] == t('Save')) { + $form_state['redirect'] = 'admin/config/search/apachesolr/settings'; + } + else { + $form_state['redirect'] = current_path(); + } + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Check to see if the facetapi module is installed, and if not put up + * a message. + * + * Only call this function if the user is already in a position for this to + * be useful. + */ +function apachesolr_check_facetapi() { + if (!module_exists('facetapi')) { + $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'facetapi'", 0, 1) + ->fetchField(); + if ($filename && file_exists($filename)) { + drupal_set_message(t('If you enable the facetapi module, Apache Solr Search will provide you with configurable facets.', array('@modules' => url('admin/modules')))); + } + else { + drupal_set_message(t('If you install the facetapi module from !href, Apache Solr Search will provide you with configurable facets.', array('!href' => url('http://drupal.org/project/facetapi')))); + } + } +} + +/** + * Form builder for general settings used as a menu callback. + * + * @param array $form + * @param array $form_state + * + * @return array Output of the system_settings_form() + */ +function apachesolr_settings(array $form, array &$form_state) { + $form = array(); + $rows = array(); + + // Environment settings + $id = apachesolr_default_environment(); + $environments = apachesolr_load_all_environments(); + $default_environment = apachesolr_default_environment(); + apachesolr_check_facetapi(); + + // Reserve a row for the default one + $rows[$default_environment] = array(); + + foreach ($environments as $environment_id => $data) { + // Define all the Operations + $confs = array(); + $ops = array(); + // Whenever facetapi is enabled we also enable our operation link + if (module_exists('facetapi')) { + $confs['facets'] = array( + 'class' => 'operation', + 'data' => l(t('Facets'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/facets', + array('query' => array('destination' => current_path())) + ), + ); + } + // These are our result and bias settings + if (module_exists('apachesolr_search')) { + $confs['result_bias'] = array( + 'class' => 'operation', + 'data' => l(t('Bias'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/bias', + array('query' => array('destination' => current_path())) + ), + ); + } + $confs['index'] = array( + 'class' => 'operation', + 'data' => l(t('Index'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/index' + ), + ); + $ops['edit'] = array( + 'class' => 'operation', + 'data' => l(t('Edit'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/edit', + array('query' => array('destination' => current_path())) + ), + ); + + $ops['clone'] = array( + 'class' => 'operation', + 'data' => l(t('Clone'), + 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/clone', + array('query' => array('destination' => $_GET['q'])) + ), + ); + $env_name = l($data['name'], 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/edit', array('query' => array('destination' => $_GET['q']))); + + // Is this row our default environment? + if ($environment_id == $default_environment) { + $env_name = t('!environment (Default)', array('!environment' => $env_name)); + $env_class_row = 'default-environment'; + } + else { + $env_class_row = ''; + } + // For every non-default we add a delete link + // Allow to revert a search environment or to delete it + $delete_value = ''; + if (!isset($data['in_code_only'])) { + if ((isset($data['type']) && $data['type'] == 'Overridden')) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Revert'), 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/delete'), + ); + } + // don't allow the deletion of the default environment + elseif ($environment_id != $default_environment) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Delete'), 'admin/config/search/apachesolr/settings/' . $data['env_id'] . '/delete'), + ); + } + } + $ops['delete'] = $delete_value; + + // When we are receiving a http POST (so the page does not show) we do not + // want to check the statusses of any environment + $class = ''; + if (empty($form_state['input'])) { + $class = apachesolr_server_status($data['url'], $data['service_class']) ? 'ok' : 'error'; + } + + $headers = array( + array('data' => t('Name'), 'colspan' => 2), + t('URL'), + array('data' => t('Configuration'), 'colspan' => count($confs)), + array('data' => t('Operations'), 'colspan' => count($ops)), + ); + + $rows[$environment_id] = array('data' => + array( + // Cells + array( + 'class' => 'status-icon', + 'data' => '
' . $class . '
', + ), + array( + 'class' => $env_class_row, + 'data' => $env_name, + ), + check_plain($data['url']), + ), + 'class' => array(drupal_html_class($class)), + ); + // Add the links to the page + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $confs); + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $ops); + } + + $form['apachesolr_host_settings']['actions'] = array( + '#markup' => '', + ); + $form['apachesolr_host_settings']['table'] = array( + '#theme' => 'table', + '#header' => $headers, + '#rows' => array_values($rows), + '#attributes' => array('class' => array('admin-apachesolr')), + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced configuration'), + '#collapsed' => TRUE, + '#collapsible' => TRUE, + ); + $form['advanced']['apachesolr_set_nodeapi_messages'] = array( + '#type' => 'radios', + '#title' => t('Extra help messages for administrators'), + '#description' => t('Adds notices to a page whenever Drupal changed content that needs reindexing'), + '#default_value' => variable_get('apachesolr_set_nodeapi_messages', 1), + '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), + ); + + // Number of Items to index + $numbers = drupal_map_assoc(array(1, 5, 10, 20, 50, 100, 200)); + $default_cron_limit = variable_get('apachesolr_cron_limit', 50); + + // apachesolr_cron_limit may be overridden in settings.php. If its current + // value is not among the default set of options, add it. + if (!isset($numbers[$default_cron_limit])) { + $numbers[$default_cron_limit] = $default_cron_limit; + } + $form['advanced']['apachesolr_cron_limit'] = array( + '#type' => 'select', + '#title' => t('Number of items to index per cron run'), + '#default_value' => $default_cron_limit, + '#options' => $numbers, + '#description' => t('Reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) + ); + + $options = array('apachesolr:show_error' => t('Show error message')); + $system_info = system_get_info('module'); + if (module_exists('search')) { + foreach (search_get_info() as $module => $search_info) { + // Don't allow apachesolr to return results on failure of apachesolr. + if ($module == 'apachesolr_search') { + continue; + } + $options[$module] = t('Show @name search results', array('@name' => $system_info[$module]['name'])); + } + } + + $options['apachesolr:show_no_results'] = t('Show no results'); + $form['advanced']['apachesolr_failure'] = array( + '#type' => 'select', + '#title' => t('On failure'), + '#options' => $options, + '#default_value' => variable_get('apachesolr_failure', 'apachesolr:show_error'), + ); + + return system_settings_form($form); +} + +/** + * Gets information about the fields already in solr index. + * + * @param array $environment + * The environment for which we need to ask the status from + * + * @return array page render array + */ +function apachesolr_status_page($environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + $environment = apachesolr_environment_load($env_id); + } + else { + $env_id = $environment['env_id']; + } + + // Check for availability + if (!apachesolr_server_status($environment['url'], $environment['service_class'])) { + drupal_set_message(t('The server seems to be unavailable. Please verify the server settings at the settings page', array('!settings_page' => url("admin/config/search/apachesolr/settings/{$environment['env_id']}/edit", array('query' => drupal_get_destination())))), 'warning'); + return ''; + } + + try { + $solr = apachesolr_get_solr($environment["env_id"]); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + $data = new stdClass; + $data->fields = array(); + } + + $messages = array(); + if (isset($data->index->numDocs)) { + try { + // Collect the stats + $stats_summary = $solr->getStatsSummary(); + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $status = apachesolr_index_status($environment["env_id"]); + // We need a schema version greater than beta3. This is mostly to catch + // people using the Drupal 6 schema. + if (preg_match('/^drupal-[13]/', $stats_summary['@schema_version'])) { + $minimum = 'drupal-3.0-beta4'; + if (version_compare($stats_summary['@schema_version'], $minimum, '<')) { + drupal_set_message(t('Your schema.xml version is too old. You must update it to at least %minimum and re-index your content.', array('%minimum' => $minimum)), 'error'); + } + } + $pending_msg = $stats_summary['@pending_docs'] ? t('(@pending_docs sent but not yet processed)', $stats_summary) : ''; + $index_msg = $stats_summary['@index_size'] ? t('(@index_size on disk)', $stats_summary) : ''; + $indexed_message = t('@num Items !pending !index_msg', array( + '@num' => $data->index->numDocs, + '!pending' => $pending_msg, + '!index_msg' => $index_msg, + )); + $messages[] = array(t('Indexed'), $indexed_message); + + $remaining_message = t('@items (@percentage% has been sent to the server)', array( + '@items' => format_plural($status['remaining'], t('1 item'), t('@count items')), + '@percentage' => ((int)min(100, 100 * ($status['total'] - $status['remaining']) / max(1, $status['total']))), + ) + ); + $messages[] = array(t('Remaining'), $remaining_message); + + $messages[] = array(t('Schema'), t('@schema_version', $stats_summary)); + if (!empty($stats_summary['@core_name'])) { + $messages[] = array(t('Solr Core Name'), t('@core_name', $stats_summary)); + } + $messages[] = array(t('Delay'), t('@autocommit_time before updates are processed.', $stats_summary)); + $messages[] = array(t('Pending Deletions'), t('@deletes_total', $stats_summary)); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + if (empty($messages)) { + $messages[] = array(t('Error'), t('No data was returned from the server. Check your log messages.')); + } + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $output['apachesolr_index_action_status'] = array( + '#prefix' => '

' . t('@environment: Search Index Content', array('@environment' => $environment['name'])) . '

', + '#theme' => 'table', + '#header' => array(t('Type'), t('Value')), + '#rows' => $messages, + ); + + $output['viewmore'] = array( + '#markup' => l(t('View more details on the search index contents'), 'admin/reports/apachesolr'), + ); + + $write_status = apachesolr_environment_variable_get($env_id, 'apachesolr_read_only', APACHESOLR_READ_WRITE); + if ($write_status == APACHESOLR_READ_WRITE) { + $output['index_action_form'] = drupal_get_form('apachesolr_index_action_form', $env_id); + $output['index_config_form'] = drupal_get_form('apachesolr_index_config_form', $env_id); + } + else { + drupal_set_message(t('Options for deleting and re-indexing are not available because the index is read-only. This can be changed on the settings page', array('!settings_page' => url('admin/config/search/apachesolr/settings/' . $env_id . '/edit', array('query' => drupal_get_destination())))), 'warning'); + } + + return $output; +} + +/** + * Get the report, eg.: some statistics and useful data from the Apache Solr index + * + * @param array $environment + * + * @return array page render array + */ +function apachesolr_index_report(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + drupal_goto('admin/reports/apachesolr/' . $env_id); + } + $environments = apachesolr_load_all_environments(); + $environments_list = array(); + foreach ($environments as $env) { + $var_status = array('!name' =>$env['name']); + $environments_list[] = l(t('Statistics for !name', $var_status), 'admin/reports/apachesolr/' . $env['env_id']); + } + $output['environments_list'] = array( + '#theme' => 'item_list', + '#items' => $environments_list, + ); + + try { + $solr = apachesolr_get_solr($environment['env_id']); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + return $output; + } + + $messages = array(); + $messages[] = array(t('Number of documents in index'), $data->index->numDocs); + + $limit = variable_get('apachesolr_luke_limit', 20000); + if (isset($data->index->numDocs) && $data->index->numDocs > $limit) { + $messages[] = array(t('Limit'), t('You have more than @limit documents, so term frequencies are being omitted for performance reasons.', array('@limit' => $limit))); + $not_found = t('Omitted'); + } + elseif (isset($data->index->numDocs)) { + $not_found = t('Not indexed'); + try { + $solr = apachesolr_get_solr($environment['env_id']); + // Note: we use 2 since 1 fails on Ubuntu Hardy. + $data = $solr->getLuke(2); + if (isset($data->index->numTerms)) { + $messages[] = array(t('# of terms in index'), $data->index->numTerms); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + $data->fields = array(); + } + } + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $fields = (array)$data->fields; + if ($fields) { + $messages[] = array(t('# of fields in index'), count($fields)); + } + + // Output the messages we have for this page + $output['apachesolr_index_report'] = array( + '#theme' => 'table', + '#header' => array('type', 'value'), + '#rows' => $messages, + ); + + if ($fields) { + // Initializes table header. + $header = array( + 'name' => t('Field name'), + 'type' => t('Index type'), + 'terms' => t('Distinct terms'), + ); + + // Builds table rows. + $rows = array(); + foreach ($fields as $name => $field) { + // TODO: try to map the name to something more meaningful. + $rows[$name] = array( + 'name' => $name, + 'type' => $field->type, + 'terms' => isset($field->distinct) ? $field->distinct : $not_found + ); + } + ksort($rows); + // Output the fields we found for this environment + $output['field_table'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + ); + } + else { + $output['field_table'] = array('#markup' => t('No data on indexed fields.')); + } + return $output; +} + +/** + * Page callback to show available conf files. + * + * @param array $environment + * + * @return string + * A non-render array but plain theme output for the config files overview. Could be done better probably + */ +function apachesolr_config_files_overview(array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $xml = NULL; + try { + $solr = apachesolr_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('wt' => 'xml')); + $xml = simplexml_load_string($response->data); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + + if ($xml) { + // Retrieve our items from the xml using xpath + $items = $xml->xpath('//lst[@name="files"]/lst'); + + // Add all the data of the file in a files array + $files = array(); + foreach ($items as $item_id => $item) { + // Do not list directories. Always a bool + if (isset($item->bool)) { + break; + } + // Get data from the files. + $name = $item->attributes(); + $name = ((string)$item->attributes()) ? (string)$item->attributes() : t('No name found'); + $files[$item_id]['name'] = l($name, 'admin/reports/apachesolr/' . $env_id . '/conf/' . $name); + + // Retrieve the date attribute + if (isset($item->date)) { + $modified = ((string)$item->date->attributes() == 'modified') ? (string) $item->date : t('No date found'); + $files[$item_id]['modified'] = format_date(strtotime($modified)); + } + + // Retrieve the size attribute + if (isset($item->long)) { + $size = ((string)$item->long->attributes() == 'size') ? (string) $item->long : t('No size found'); + $files[$item_id]['size'] = t('Size (bytes): @bytes', array('@bytes' => $size)); + } + } + // Sort our files alphabetically + ksort($files); + + // Initializes table header. + $header = array( + 'name' => t('File name'), + 'date' => t('Modified'), + 'size' => t('Size'), + ); + + // Display the table of field names, index types, and term counts. + $variables = array( + 'header' => $header, + 'rows' => $files, + ); + $output = theme('table', $variables); + } + else { + $output = '

' . t('No data about any file found.') . "

\n"; + } + return $output; +} + +/** + * Page callback to show one conf file. + * + * @param string $name + * @param array $environment + * + * @return string + * the requested config file + */ +function apachesolr_config_file($name, array $environment = array()) { + if (empty($environment)) { + $env_id = apachesolr_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $output = ''; + try { + $solr = apachesolr_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('file' => $name)); + $raw_file = $response->data; + $output = '
' . check_plain($raw_file) . '
'; + drupal_set_title(check_plain($name)); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + return $output; +} + +/** + * Form builder for the Apachesolr Indexer actions form. + * + * @param array $form + * @param array $form_state + * @param string $env_id + * The machine name of the environment. + * @see apachesolr_index_action_form_delete_submit(). + * + * @return array $form + */ +function apachesolr_index_action_form(array $form, array $form_state, $env_id) { + $form = array(); + $form['action'] = array( + '#type' => 'fieldset', + '#title' => t('Actions'), + '#collapsible' => TRUE, + ); + + $form['action']['env_id'] = array( + '#type' => 'value', + '#value' => $env_id, + ); + + $form['action']['cron'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Index queued content (!amount)', array('!amount' => variable_get('apachesolr_cron_limit', 50))), + '#submit' => array('apachesolr_index_action_form_cron_submit'), + ); + $form['action']['cron_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Indexes just as many items as 1 cron run would do.'), + ); + + $form['action']['remaining'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Index all queued content'), + '#submit' => array('apachesolr_index_action_form_remaining_submit'), + ); + $form['action']['remaining_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Could take time and could put an increased load on your server.'), + ); + + $form['action']['reset'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#type' => 'submit', + '#value' => t('Queue all content for reindexing'), + '#submit' => array('apachesolr_index_action_form_reset_submit'), + ); + $form['action']['delete'] = array( + '#prefix' => '
', + '#type' => 'submit', + '#value' => t('Delete the Search & Solr index'), + '#submit' => array('apachesolr_index_action_form_delete_submit'), + ); + $form['action']['delete_description'] = array( + '#prefix' => '', + '#suffix' => '
', + '#markup' => t('Useful with a corrupt index or a new schema.xml.'), + ); + + return $form; +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_remaining_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/remaining', array('query' => $destination)); +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_delete_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/delete', array('query' => $destination)); +} + +/** + * Submit handler for the Indexer actions form, delete button. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_reset_submit(array $form, array &$form_state) { + $destination = array(); + if (isset($_GET['destination'])) { + $destination = drupal_get_destination(); + unset($_GET['destination']); + } + $env_id = $form_state['values']['env_id']; + $form_state['redirect'] = array('admin/config/search/apachesolr/settings/' . $env_id . '/index/reset', array('query' => $destination)); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_cron_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0])) { + $env_id = $form_state['build_info']['args'][0]; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + apachesolr_cron($env_id); + drupal_set_message(t('Apachesolr cron succesfully executed')); +} + +/** + * Form builder for to reindex the remaining items left in the queue. + * + * @see apachesolr_index_action_form_delete_confirm_submit(). + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return mixed + */ +function apachesolr_index_action_form_remaining_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want index all remaining content?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + NULL, + t('Index all remaining') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_remaining_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + apachesolr_index_batch_index_remaining($env_id); +} + +/** + * Form builder for the index re-enqueue form. + * + * @see apachesolr_index_action_form_reset_confirm_submit(). + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return mixed + */ +function apachesolr_index_action_form_reset_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want to queue content for reindexing?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + t('All content on the site will be queued for indexing. The documents currently in the Solr index will remain searchable.'), + t('Queue all content') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_reset_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + apachesolr_index_mark_for_reindex($env_id); + drupal_set_message(t('All the content on your site is queued for indexing. You can wait for it to be indexed during cron runs, or you can manually reindex it.')); +} + +/** + * Form builder for the index delete/clear form. + * + * @see apachesolr_index_action_form_delete_confirm_submit(). + + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return array output of confirm_form() + */ +function apachesolr_index_action_form_delete_confirm(array $form, array &$form_state, array $environment) { + return confirm_form($form, + t('Are you sure you want to clear your index?'), + 'admin/config/search/apachesolr/settings/' . $environment['env_id'] . '/index', + t('This will remove all data from your index and all search results will be incomplete until your site is reindexed.'), + t('Delete index') + ); +} + +/** + * Submit handler for the deletion form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_action_form_delete_confirm_submit(array $form, array &$form_state) { + if (!empty($form_state['build_info']['args'][0]['env_id'])) { + $env_id = $form_state['build_info']['args'][0]['env_id']; + $form_state['redirect'] = 'admin/config/search/apachesolr/settings/' . $env_id . '/index'; + } + else { + $env_id = apachesolr_default_environment(); + $form_state['redirect'] = 'admin/config/search/apachesolr'; + } + // Rebuild our tracking table. + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + apachesolr_index_delete_index($env_id); + drupal_set_message(t('The index has been deleted.')); +} + +/** + * Submit a batch job to index the remaining, non-indexed content. + * + * @param string $env_id + * The environment ID where it needs to index the remaining items for + */ +function apachesolr_index_batch_index_remaining($env_id, $total_limit = null) { + $batch = array( + 'operations' => array( + array( + 'apachesolr_index_batch_index_entities', + array( + $env_id, + $total_limit, + ), + ), + ), + 'finished' => 'apachesolr_index_batch_index_finished', + 'title' => t('Indexing'), + 'init_message' => t('Preparing to submit content to Solr for indexing...'), + 'progress_message' => t('Submitting content to Solr...'), + 'error_message' => t('Solr indexing has encountered an error.'), + 'file' => drupal_get_path('module', 'apachesolr') . '/apachesolr.admin.inc', + ); + batch_set($batch); +} + + +/** + * Batch Operation Callback + * + * @param string $env_id + * The machine name of the environment. + * @param $total_limit + * The total number of items to index across all batches + * @param array $context + * + * @return false + * return false when an exception was caught + * + * @throws Exception + * When solr gives an error, throw an exception that solr is not available + */ +function apachesolr_index_batch_index_entities($env_id, $total_limit = NULL, &$context) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + if (empty($context['sandbox'])) { + try { + // Get the $solr object + $solr = apachesolr_get_solr($env_id); + // If there is no server available, don't continue. + if (!$solr->ping()) { + throw new Exception(t('No Solr instance available during indexing.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR); + return FALSE; + } + + $status = apachesolr_index_status($env_id); + $context['sandbox']['progress'] = 0; + $context['sandbox']['submitted'] = 0; + + // How many items do we want to index? All or a limited set of items + if (empty($total_limit)) { + $context['sandbox']['max'] = $status['remaining']; + } + else { + $context['sandbox']['max'] = $total_limit; + } + } + + // 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); + + // Reduce the limit for our final batch if we would be processing more than had been requested + if ($limit + $context['sandbox']['progress'] > $context['sandbox']['max']) { + $limit = $context['sandbox']['max'] - $context['sandbox']['progress']; + } + + if ($context['sandbox']['max'] >= $context['sandbox']['progress'] + $limit) { + $context['sandbox']['progress'] += $limit; + } + else { + $context['sandbox']['progress'] = $context['sandbox']['max']; + } + $context['sandbox']['submitted'] += apachesolr_index_entities($env_id, $limit); + + $arguments = array( + '@current' => $context['sandbox']['progress'], + '@total' => $context['sandbox']['max'], + '@submitted' => $context['sandbox']['submitted'], + ); + $context['message'] = t('Inspected @current of @total entities. Submitted @submitted documents to Solr', $arguments); + + // Inform the batch engine that we are not finished, and provide an + // estimation of the completion level we reached. + $context['finished'] = empty($context['sandbox']['max']) ? 1 : $context['sandbox']['progress'] / $context['sandbox']['max']; + + // Put the total into the results section when we're finished so we can + // show it to the admin. + if ($context['finished']) { + $context['results']['count'] = $context['sandbox']['progress']; + $context['results']['submitted'] = $context['sandbox']['submitted']; + } +} + +/** + * Batch 'finished' callback + * + * @param bool $success + * Whether the batch ended with success or not + * @param array $results + * @param array $operations + */ +function apachesolr_index_batch_index_finished($success, array $results, array $operations) { + $message = ''; + // $results['count'] will not be set if Solr is unavailable. + if (isset($results['count'])) { + $message .= format_plural($results['count'], '1 item processed successfully. ', '@count items successfully processed. '); + } + if (isset($results['submitted'])) { + $message .= format_plural($results['submitted'], '1 document successfully sent to Solr.', '@count documents successfully sent to Solr.'); + } + if ($success) { + $type = 'status'; + } + else { + // An error occurred. $operations contains the unprocessed operations. + $error_operation = reset($operations); + $message .= ' ' . t('An error occurred while processing @num with arguments: @args', array('@num' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE))); + $type = 'error'; + } + drupal_set_message($message, $type); +} + +/** + * Form builder for the bundle configuration form. + * + * @see apachesolr_index_config_form_submit(). + * + * @param array $form + * @param array $form_state + * @param string $env_id + * The machine name of the environment. + * + * @return array $form + */ +function apachesolr_index_config_form(array $form, array $form_state, $env_id) { + $form['config'] = array( + '#type' => 'fieldset', + '#title' => t('Configuration'), + '#collapsible' => TRUE, + ); + + $form['config']['bundles'] = array( + '#type' => 'markup', + '#markup' => t('Select the entity types and bundles that should be indexed.'), + ); + + // For future extensibility, when we have multiple cores. + $form['config']['env_id'] = array( + '#type' => 'value', + '#value' => $env_id, + ); + + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!empty($entity_info['apachesolr']['indexable'])) { + $options = array(); + foreach ($entity_info['bundles'] as $key => $info) { + $options[$key] = $info['label']; + } + + $form['config']['entities']['#tree'] = TRUE; + $form['config']['entities'][$entity_type] = array( + '#type' => 'checkboxes', + '#title' => check_plain($entity_info['label']), + '#options' => $options, + '#default_value' => apachesolr_get_index_bundles($env_id, $entity_type), + ); + } + } + + $form['config']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + return $form; +} + +/** + * Submit handler for the bundle configuration form. + * + * @param array $form + * @param array $form_state + */ +function apachesolr_index_config_form_submit(array $form, array &$form_state) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $form_values = $form_state['values']; + $env_id = $form_values['env_id']; + + foreach ($form_values['entities'] as $entity_type => $bundles) { + $existing_bundles = apachesolr_get_index_bundles($env_id, $entity_type); + $all_bundles = array_keys($bundles); + $new_bundles = array_values(array_filter($bundles)); + apachesolr_index_set_bundles($env_id, $entity_type, $new_bundles); + + // Remove all excluded bundles - this happens on form submit + // even if there is no change so the admin can remove + // bundles if there was an error. + $excluded_bundles = array_diff($all_bundles, $new_bundles); + if (apachesolr_index_delete_bundles($env_id, $entity_type, $excluded_bundles)) { + $callback = apachesolr_entity_get_callback($entity_type, 'bundles changed callback'); + if (!empty($callback)) { + call_user_func($callback, $env_id, $existing_bundles, $new_bundles); + } + } + else { + drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error'); + } + } + + // Clear the entity cache, since we will be changing its data. + entity_info_cache_clear(); + cache_clear_all('apachesolr:environments', 'cache_apachesolr'); + drupal_set_message(t('Your settings have been saved.')); +} + +/** + * Page callback for node/%node/devel/apachesolr. + * + * @param object $node + * @return string debugging information + */ +function apachesolr_devel($node) { + module_load_include('inc', 'apachesolr', 'apachesolr.index'); + $item = new stdClass(); + $item->entity_type = 'node'; + $item->entity_id = $node->nid; + $output = ''; + foreach (apachesolr_load_all_environments() as $env_id => $environment) { + $documents = apachesolr_index_entity_to_documents($item, $env_id); + $output .= '

' . t('Environment %name (%env_id)', array('%name' => $environment['name'], '%env_id' => $env_id)). '

'; + foreach ($documents as $document) { + $debug_data = array(); + foreach ($document as $key => $value) { + $debug_data[$key] = $value; + } + $output .= kdevel_print_object($debug_data); + } + } + return $output; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/facetapi.callbacks.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/facetapi.callbacks.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,164 @@ + $bundle_info) { + $names[$bundle_name] = $bundle_info['label']; + } + } + } + return array_intersect_key($names, array_flip($values)); +} + +/** + * Map callback for node authors. + */ +function facetapi_map_author(array $values) { + $map = array(); + $users = user_load_multiple($values); + foreach ($users as $user) { + $map[$user->uid] = format_username($user); + } + if (isset($map[0])) { + $map[0] = variable_get('anonymous', t('Anonymous')); + } + return $map; +} + +/** + * Map callback for languages. + */ +function facetapi_map_language(array $values) { + $map = array(); + $language_list = language_list(); + foreach ($values as $language) { + if (isset($language_list[$language])) { + $map[$language] = t($language_list[$language]->name); + } + else { + $map[$language] = t('Language neutral'); + } + } + return $map; +} + +/** + * Maps date ranges to human readable dates. + * + * @param array $values + * An array of indexed values being mapped. + * @param array $options + * An associative array of map options containing: + * - format callback: The callback used to format the date, defaults to + * "facetapi_format_timestamp". + * + * @return array + * An array mapping the indexed values to human readable values. + */ +function facetapi_map_date(array $values, array $options) { + if (empty($options['format callback'])) { + $options['format callback'] = 'facetapi_format_timestamp'; + } + $map = array(); + foreach ($values as $value) { + $range = explode(' TO ', trim($value, '{[]}')); + if (isset($range[1])) { + $gap = facetapi_get_date_gap($range[0], $range[1]); + $map[$value] = facetapi_format_date($range[0], $gap, $options['format callback']); + } + } + return $map; +} + +/** + * Callback that returns the minimum date in the node table. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The minimum time in the node table. + * + * @todo Cache this value. + */ +function facetapi_get_min_date(array $facet) { + $query = db_select('node', 'n')->condition('status', 1); + $query->addExpression('MIN(' . $facet['name'] . ')', 'max'); + return $query->execute()->fetch()->max; +} + +/** + * Callback that returns the minimum value in the node table. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The minimum time in the node table. + * + * @todo Cache this value. + */ +function facetapi_get_max_date(array $facet) { + $query = db_select('node', 'n')->condition('status', 1); + $query->addExpression('MAX(' . $facet['name'] . ')', 'max'); + return $query->execute()->fetch()->max; +} + +/** + * Map callback for taxonomy terms. + */ +function facetapi_map_taxonomy_terms(array $values) { + $map = array(); + $terms = taxonomy_term_load_multiple($values); + foreach ($terms as $term) { + $map[$term->tid] = entity_label('taxonomy_term', $term); + } + return $map; +} + +/** + * Gets parent information for taxonomy terms. + * + * @param array $values + * An array containing the term ids. + * + * @return + * An associative array keyed by term ID to parent ID. + */ +function facetapi_get_taxonomy_hierarchy(array $values) { + $result = db_select('taxonomy_term_hierarchy', 'th') + ->fields('th', array('tid', 'parent')) + ->condition('th.parent', '0', '>') + ->condition(db_or() + ->condition('th.tid', $values, 'IN') + ->condition('th.parent', $values, 'IN') + ) + ->execute(); + + $parents = array(); + foreach ($result as $record) { + $parents[$record->tid][] = $record->parent; + } + return $parents; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/adapter.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/adapter.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,217 @@ +getSearcher(), '@'), '@'); + $path .= '/' . $env_id . '/facets'; + // Add the realm name to the path if it is not the first one in the list. + if (key(facetapi_get_realm_info()) != $realm_name) { + $path .= '/' . $realm_name; + } + } + return $path; + } + + /** + * Allows the backend to initialize its query object before adding the facet + * filters. + * + * @param mixed $query + * The backend's native object. + */ + function initActiveFilters($query) { + $enabled_facets = facetapi_get_enabled_facets($this->info['name']); + if ($enabled_facets) { + $query->addParam('facet', 'true'); + $query->addParam('facet.sort', 'count'); + $query->addParam('facet.mincount', '1'); + } + } + + /** + * Returns a boolean flagging whether $this->_searcher executed a search. + */ + public function searchExecuted() { + // Initial check - has ANY solr query run in our environment. + $env_id = $this->info['instance']; + $this_has_searched = solrsearch_has_searched($env_id); + // Secondary check - do we have results for this searcher? + $this_has_searched = $this_has_searched && solrsearch_static_response_cache($this->getSearcher()); + return $this_has_searched; + } + + /** + * Suppress output of the realm + * + * @param string $realm_name + * + * @return bool $flag + * Returns if it was suppressed or not + */ + public function suppressOutput($realm_name) { + $flag = FALSE; + //dpm("suppressoutput"); + if ($realm_name == 'block') { + //dpm($this->info); + $env_id = $this->info['instance']; + $flag = solrsearch_suppress_blocks($env_id); + //dpm($env_id); + //dpm($flag); + + } + return FALSE; + return $flag || !$this->searchExecuted(); } + + /** + * Returns the search keys. + * + * @return string + */ + public function getSearchKeys() { + if (NULL === $this->keys) { + $env_id = $this->info['instance']; + if ($query = solrsearch_current_query($env_id)) { + return $query->getParam('q'); + } + } + else { + return $this->keys; + } + return FALSE; + } + + /** + * Returns the search path. + * + * @return string + * A string containing the search path. + * + * @todo D8 should provide an API function for this. + */ + public function getSearchPath() { + $env_id = $this->info['instance']; + $query = solrsearch_current_query($env_id); + if (!$query || (NULL === $this->searchPath && NULL === $query->getPath())) { + if ($path = module_invoke($this->info['module'] . '_search', 'search_info')) { + $this->searchPath = 'search/' . $path['path']; + if (!isset($_GET['keys']) && ($keys = $this->getSearchKeys())) { + $this->searchPath .= '/' . $keys; + } + } + } + if (!$query || NULL === $query->getPath()) { + return $this->searchPath; + } + else { + return $query->getPath(); + } + + } + + /** + * Returns the number of total results found for the current search. + * + * @return bool|int + * Number of results or false if no search response was found + */ + public function getResultCount() { + $response = solrsearch_static_response_cache($this->getSearcher()); + if ($response) { + return $response->response->numFound; + } + return FALSE; + } + + /** + * Allows for backend specific overrides to the settings form. + * + * @param array $form + * @param array $form_state + */ + public function settingsForm(&$form, &$form_state) { + $form['#validate'][] = 'solrsearch_facet_form_validate'; + } + + + /** + * Allows the backend to add facet queries to its native query object. + * + * This method is called by the implementing module to initialize the facet + * display process. The following actions are taken: + * - FacetapiAdapter::initActiveFilters() hook is invoked. + * - Dependency plugins are instantiated and executed. + * - Query type plugins are executed. + * + * @param mixed $query + * The backend's native query object. + * + * @todo Should this method be deprecated in favor of one name init()? This + * might make the code more readable in implementing modules. + * + * @see FacetapiAdapter::initActiveFilters() + */ + function addActiveFilters($query) { + module_load_include('inc', 'solrsearch', 'facetapi.callbacks'); + facetapi_add_active_searcher($this->info['name']); + + // Invoke initActiveFilters hook. + $this->initActiveFilters($query); + + + foreach ($this->getEnabledFacets() as $facet) { + + $settings = $this->getFacet($facet)->getSettings(); + + // Instantiate and execute dependency plugins. + $display = TRUE; + foreach ($facet['dependency plugins'] as $id) { + $class = ctools_plugin_load_class('facetapi', 'dependencies', $id, 'handler'); + $plugin = new $class($id, $this, $facet, $settings, $this->activeItems['facet']); + if (NULL !== ($return = $plugin->execute())) { + $display = $return; + } + } + + // Store whether this facet passed its dependencies. + $this->dependenciesPassed[$facet['name']] = $display; + + // Execute query type plugin if dependencies were met, otherwise remove + // the facet's active items so they don't display in the current search + // block or appear as active in the breadcrumb trail. + if ($display && $this->queryTypes[$facet['name']]) { + $this->queryTypes[$facet['name']]->execute($query); + } + else { + foreach ($this->activeItems['facet'][$facet['name']] as $item) { + $this->urlProcessor->removeParam($item['pos']); + $filter = $item['field alias'] . ':' . $item['value']; + unset($this->activeItems['filter'][$filter]); + } + $this->activeItems['facet'][$facet['name']] = array(); + } + } + + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_date.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_date.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,185 @@ +getDateRange($query); + if (empty($date_range)) { + return NULL; + } + list($start, $end, $gap) = $date_range; + $query->addParam('facet.date', $this->facet['field']); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.start', $start); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.end', $end); + $query->addParam('f.' . $this->facet['field'] . '.facet.date.gap', $gap); + + // Adds "hard limit" parameter to prevent too many return values. + $settings = $this->adapter->getFacet($this->facet)->getSettings(); + $limit = empty($settings->settings['hard_limit']) ? 20 : (int) $settings->settings['hard_limit']; + $query->addParam('f.' . $this->facet['field'] . '.facet.limit', $limit); + + $active = $this->adapter->getActiveItems($this->facet); + // Date filters don't support OR operator. + foreach ($active as $value => $item) { + $query->addFilter($this->facet['field'], $value); + } + } + + /** + * Gets the range of dates we are using. + * + * @param DrupalSolrQueryInterface $query + * A SolrBaseQuery object. + * + * @return bool|array + * An array containing the gap and range information or false if not present + */ + function getDateRange(DrupalSolrQueryInterface $query) { + $return = NULL; + $gap = NULL; + + // Attempts to get next gap from passed date filters. + foreach ($this->adapter->getActiveItems($this->facet) as $item) { + if ($gap = facetapi_get_date_gap($item['start'], $item['end'])) { + $next_gap = facetapi_get_next_date_gap($gap, FACETAPI_DATE_SECOND); + if ($next_gap == $gap) { + $next_gap = NULL; + return NULL; + } + $return = array( + "{$item['start']}/$next_gap", + "{$item['end']}+1$next_gap/$next_gap", + "+1$next_gap", + ); + } + } + + // If no filters were passed, get default range. + if (NULL === $return) { + + // Builds SQL that gets minimum and maximum values from node table. + $minimum = $maximum = FALSE; + if ($this->facet['min callback'] && is_callable($this->facet['min callback'])) { + $minimum = $this->facet['min callback']($this->facet); + } + if ($this->facet['max callback'] && is_callable($this->facet['max callback'])) { + $maximum = $this->facet['max callback']($this->facet); + } + + // Gets the default gap. + //$gap = FACETAPI_DATE_YEAR; + if ($minimum && $maximum) { + $gap = facetapi_get_timestamp_gap($minimum, $maximum); + $minimum = facetapi_isodate($minimum, $gap); + $maximum = facetapi_isodate($maximum, $gap); + $return = array( + "$minimum/$gap", + "$maximum+1$gap/$gap", + "+1$gap", + ); + } + } + // Returns the range information. + return $return; + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + + // Initializes build and gets static response. + if (!$response = solrsearch_static_response_cache($this->adapter->getSearcher())) { + return array(); + } + $build = array(); + + // Gets total number of documents matched in search. + $total = $response->response->numFound; + + // Gets the active date facets, starts to builds the "parent - child" + // relationships. + $parent = NULL; + foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) { + // Builds the raw facet "value", the count for selected items will be the + // total number of rows returned in the query. + $build[$value] = array('#count' => $total); + + // If there is a previous item, there is a parent, uses a reference so the + // arrays are populated when they are updated. + if (NULL !== $parent) { + $build[$parent]['#item_children'][$value] = &$build[$value]; + $build[$value]['#item_parents'][$parent] = $parent; + } + + // Stores the last value iterated over. + $parent = $value; + } + + // Gets raw facet data from the Solr server. + if (isset($response->facet_counts->facet_dates) && isset($response->facet_counts->facet_dates->{$this->facet['field']})) { + $raw_data = (array) $response->facet_counts->facet_dates->{$this->facet['field']}; + } + else { + $raw_data = array(); + } + //$end = (!empty($raw_data['end'])) ? $raw_data['end'] : ''; + //$start = (!empty($raw_data['start'])) ? $raw_data['start'] : ''; + $gap = (!empty($raw_data['gap'])) ? $raw_data['gap'] : ''; + + // We cannot list anything below a minute (range of 00 seconds till 59 + // seconds. Milliseconds are not possible) + if ($gap != "+1SECOND") { + unset($raw_data['start']); + unset($raw_data['end']); + unset($raw_data['gap']); + + // Treat each date facet as a range start, and use the next date facet + // as range end. Use 'end' for the final end. + $previous = NULL; + + // Builds facet counts object used by the server. + foreach ($raw_data as $value => $count) { + if ($count) { + $from = $value; + $to = facetapi_isodate(strtotime($value . $gap)); + $new_value = '[' . $from . ' TO ' . $to . ']'; + $build[$new_value] = array('#count' => $count, '#active' => 0); + if (NULL !== $parent) { + $build[$parent]['#item_children'][$new_value] = &$build[$new_value]; + $build[$new_value]['#item_parents'][$parent] = $parent; + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_geo.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_geo.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,84 @@ +adapter->getFacet($this->facet)->getSettings(); + + $facet_distances = explode(',', $this->facet_options); + + $active_items = $this->adapter->getActiveItems($this->facet); + + if (empty($active_items)) { + $distance = $this->default_radius; + } + else { + $active_item = array_pop($active_items); + $distance = substr($active_item['value'], 1); + // Add current selected distance to have possibility to unselect it. + $facet_distances[] = 1; + } + + // Search center point. + $query->addParam('pt', $this->center_point); + + // Set location field name. + $query->addParam('sfield', $this->facet['field']); + $query->addParam('fq', '{!geofilt sfield=' . $this->facet['field'] . '}'); + + // Set search radius. + $query->addParam('d', $distance); + + // Set facets. + foreach ($facet_distances as $facet_option) { + $facet_distance = $distance * $facet_option; + $query->addParam('facet.query', '{!geofilt d=' . $facet_distance . ' key=d' . $facet_distance . '}'); + } + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + $build = array(); + if ($response = apachesolr_static_response_cache($this->adapter->getSearcher())) { + if (isset($response->facet_counts->facet_queries)) { + foreach ($response->facet_counts->facet_queries as $value => $count) { + // Skip zero results values. + if ($count > 0) { + $build[$value] = array('#count' => $count); + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_integer.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_integer.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,223 @@ +getIntegerRange($query); + + if (empty($integer_range)) { + return NULL; + } + list($start, $end, $gap) = $integer_range; + $query->addParam('facet.range', $this->facet['field']); + $query->addParam('f.' . $this->facet['field'] . '.facet.range.start', $start); + $query->addParam('f.' . $this->facet['field'] . '.facet.range.end', $end); + $query->addParam('f.' . $this->facet['field'] . '.facet.range.gap', $gap); + + // Adds "hard limit" parameter to prevent too many return values. + $settings = $this->adapter->getFacet($this->facet)->getSettings(); + $limit = empty($settings->settings['hard_limit']) ? 20 : (int) $settings->settings['hard_limit']; + $query->addParam('f.' . $this->facet['field'] . '.facet.limit', $limit); + + $active = $this->adapter->getActiveItems($this->facet); + // Date filters don't support OR operator. + foreach ($active as $value => $item) { + $query->addFilter($this->facet['field'], $value); + } + } + + /** + * Gets the range of integer rage we are using. + * + * @param DrupalSolrQueryInterface $query + * A SolrBaseQuery object. + * + * @return bool|array + * An array containing the gap and range information or false if not present + */ + function getIntegerRange(DrupalSolrQueryInterface $query) { + + $return = NULL; + $gap = NULL; + + // Attempts to get next gap from passed date filters. + foreach ($this->adapter->getActiveItems($this->facet) as $item) { + + if ($gap = ($item['end'] - $item['start'])) { + //$next_gap = facetapi_get_next_date_gap($gap, FACETAPI_DATE_SECOND); + //TODO: is something similar needed for integer range? + $next_gap =$gap; + if ($next_gap == $gap) { + $next_gap = NULL; + return NULL; + } + $return = array( + $item['start'], + $item['end'], + $next_gap, + ); + } + } + + // If no filters were passed, get default range. + if (NULL === $return) { + + // Builds SQL that gets minimum and maximum values from node table. + $minimum = $maximum =$gap = FALSE; + if ($this->facet['min callback'] && is_callable($this->facet['min callback'])) { + $minimum = $this->facet['min callback']($this->facet); + } + if ($this->facet['max callback'] && is_callable($this->facet['max callback'])) { + $maximum = $this->facet['max callback']($this->facet); + } + + if ($this->facet['gap callback'] && is_callable($this->facet['gap callback'])) { + $gap = $this->facet['gap callback']($this->facet); + } + + + // Gets the default gap. + //$gap = FACETAPI_DATE_YEAR; + if ($minimum && $maximum && $gap) { + + //$minimum = facetapi_isodate($minimum, $gap); + //$maximum = facetapi_isodate($maximum, $gap); + $return = array( + $minimum, + $maximum, + $gap , + ); + } + } + // Returns the range information. + return $return; + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + + // Initializes build and gets static response. + if (!$response = solrsearch_static_response_cache($this->adapter->getSearcher())) { + return array(); + } + $build = array(); + + // Gets total number of documents matched in search. + $total = $response->response->numFound; + + // Gets the active date facets, starts to builds the "parent - child" + // relationships. + $parent = NULL; + foreach ($this->adapter->getActiveItems($this->facet) as $value => $item) { + // Builds the raw facet "value", the count for selected items will be the + // total number of rows returned in the query. + $build[$value] = array('#count' => $total); + + // If there is a previous item, there is a parent, uses a reference so the + // arrays are populated when they are updated. + if (NULL !== $parent) { + $build[$parent]['#item_children'][$value] = &$build[$value]; + $build[$value]['#item_parents'][$parent] = $parent; + } + + // Stores the last value iterated over. + $parent = $value; + } + + // Gets raw facet data from the Solr server. + if (isset($response->facet_counts->facet_ranges) && isset($response->facet_counts->facet_ranges->{$this->facet['field']})) { + $raw_data = (array) $response->facet_counts->facet_ranges->{$this->facet['field']}; + } + else { + $raw_data = array(); + } + //$end = (!empty($raw_data['end'])) ? $raw_data['end'] : ''; + //$start = (!empty($raw_data['start'])) ? $raw_data['start'] : ''; + $gap = (!empty($raw_data['gap'])) ? $raw_data['gap'] : ''; + + // We cannot list anything below a minute (range of 00 seconds till 59 + // seconds. Milliseconds are not possible) + + unset($raw_data['start']); + unset($raw_data['end']); + unset($raw_data['gap']); + + // Treat each date facet as a range start, and use the next date facet + // as range end. Use 'end' for the final end. + $previous = NULL; + + // Builds facet counts object used by the server. + + foreach ($raw_data as $value => $counts) { + foreach($counts as $val => $count){ + if ($count) { + $from = $val; + $to = $val+$gap; + $new_value = '[' . $from . ' TO ' . $to . ']'; + $build[$new_value] = array('#count' => $count, '#active' => 0); + if (NULL !== $parent) { + $build[$parent]['#item_children'][$new_value] = &$build[$new_value]; + $build[$new_value]['#item_parents'][$parent] = $parent; + } + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_numeric_range.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_numeric_range.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,102 @@ +adapter->getFacet($this->facet)->getSettings(); + $active = $this->adapter->getActiveItems($this->facet); + + $singular_field_info = $this->facet['map options']; + $singular_field_info['multiple'] = FALSE; + + $this->single_key = solrsearch_index_key($singular_field_info); + // See: http://wiki.apache.org/solr/StatsComponent + $query->addParam('stats', 'true'); + $query->addParam('stats.field', $this->single_key); + $query->addParam('stats.facet', $this->single_key); + // Range filters don't support OR operator. + foreach ($active as $value => $item) { + $query->addFilter($this->single_key, $value); + } + } + + /** + * Initializes the facet's build array. + * + * Any calls to this method need to be wrapped in a try-catch block. + * + * @return array + * The initialized render array. + */ + public function build() { + $build = array(); + if (!isset($this->single_key)) { + return $build; + } + + // Per key we save our statistics result + $cache = cache_get('stats_' . $this->single_key, 'cache_solrsearch'); + $stats_minmax = array(); + + if (!isset($cache->data)) { + // we need an additional query for the statistics of the field + // We can optionally specify a Solr object. + $solr = solrsearch_get_solr(); + + // We possibly need some caching for this query + $query_stats = solrsearch_drupal_query('solrsearch_stats', array(), '', '', $solr); + $query_stats->addParam('stats', 'true'); + $query_stats->addParam('stats.field', $this->single_key); + $query_stats->addParam('stats.facet', $this->single_key); + $response_stats = $query_stats->search(); + + if ($response_stats->response) { + $stats_minmax = $response_stats->stats->stats_fields->{$this->single_key}; + cache_set('stats_' . $this->single_key, $stats_minmax, 'cache_solrsearch'); + } + } + else { + // Set our statistics from the cache + $stats_minmax = $cache->data; + } + + if ($response = solrsearch_static_response_cache($this->adapter->getSearcher())) { + if (isset($response->stats->stats_fields->{$this->single_key})) { + $stats = (array) $response->stats->stats_fields->{$this->single_key}; + foreach ($stats as $key => $val) { + $build[$this->facet['field']]['#range_' . $key] = $val; + $build[$this->facet['field']]['#global_range_' . $key] = $stats_minmax->$key; + } + } + } + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_term.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/plugins/facetapi/query_type_term.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,106 @@ +adapter->getFacet($this->facet)->getSettings(); + // Adds the operator parameter. + $operator = $settings->settings['operator']; + $ex = (FACETAPI_OPERATOR_OR != $operator) ? '' : "{!ex={$this->facet['field']}}"; + $query->addParam('facet.field', $ex . $this->facet['field']); + + if (!empty($settings->settings['facet_missing'])) { + $query->addParam('f.' . $this->facet['field'] . '.facet.missing', 'true'); + } + // Adds "hard limit" parameter to prevent too many return values. + $limit = empty($settings->settings['hard_limit']) ? 20 : (int) $settings->settings['hard_limit']; + $query->addParam('f.' . $this->facet['field'] . '.facet.limit', $limit); + + // Adds "facet mincount" parameter to limit the number of facets. + if (isset($settings->settings['facet_mincount'])) { + $count = $settings->settings['facet_mincount']; + $query->addParam('f.' . $this->facet['field'] . '.facet.mincount', $count); + } + + $active = $this->adapter->getActiveItems($this->facet); + + // Adds filter based on the operator. + if (FACETAPI_OPERATOR_OR != $operator) { + foreach ($active as $value => $item) { + // Handle facet missing: + if ($value == '_empty_' && !empty($settings->settings['facet_missing'])) { + $query->addFilter($this->facet['field'], '[* TO *]', TRUE); + } + else { + $query->addFilter($this->facet['field'], $value); + } + } + } + else { + // OR facet. + $local = "tag={$this->facet['field']}"; + $values = array_keys($active); + if ($values) { + // Quote any values that have white space or colons. + foreach ($values as &$v) { + if (preg_match('/[:\s]/', $v)) { + $v = '"' . $v . '"'; + } + } + $query->addFilter($this->facet['field'], '(' . implode(' OR ', $values) . ')', FALSE, $local); + } + } + } + + /** + * Initializes the facet's build array. + * + * @return array + * The initialized render array. + */ + public function build() { + + + $build = array(); + if ($response = solrsearch_static_response_cache($this->adapter->getSearcher())) { + + $settings = $this->adapter->getFacet($this->facet)->getSettings(); + + if (isset($response->facet_counts->facet_fields->{$this->facet['field']})) { + $values = (array) $response->facet_counts->facet_fields->{$this->facet['field']}; + foreach ($values as $value => $count) { + // Facet missing may return 0 even if mincount is 1. + if (empty($settings->settings['facet_mincount']) || $count) { + $build[$value] = array('#count' => $count); + } + } + } + } + + return $build; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solr-search-result.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solr-search-result.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,58 @@ + + + + + + + + +> + + + + + + + +intern + + + + + + + + + + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solr-search-results.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solr-search-results.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,52 @@ + +
+ + + + +

+ + + +
+ + + + + + +
+ + + +
+ + +

+ + +
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-author-block-form.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-author-block-form.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,40 @@ + + *
+ * + *
+ * + * @endcode + * + * @see template_preprocess_search_block_form() + */ +?> +
+ subject)): ?> +

+ +

+
+ +
+
diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-block-form.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-block-form.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,40 @@ + + *
+ * + *
+ * + * @endcode + * + * @see template_preprocess_search_block_form() + */ +?> +
+ subject)): ?> +

+ +

+
+ +
+
diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-mpiwg.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-mpiwg.js Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,35 @@ +/* + * Javascript for MPIWG website + */ + +(function ($) { +$(document).ready(function() { + /* + * autosubmit forms + */ + $('form.autosubmit').find('.autosubmit').change(function() { + this.form.submit(); + }); + // hide submit button + $('form.autosubmit input[type="submit"].autosubmit').hide(); + + /* + * foldout divs + */ + $('.foldable').each(function() { + var $this = $(this); + var $head = $this.find('.fold_head'); + var $img = $head.find('img'); + var $body = $this.find('.fold_body'); + $head.on('click', function() { + $body.slideToggle('fast'); + $img.toggle(); + }).css('cursor', 'pointer'); + if (! $this.hasClass('initially_open')) { + // hide body initially + $body.hide(); + $img.toggle(); + } + }); +}); +})(jQuery); \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-term-list-author.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-term-list-author.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,7 @@ + + +

Authors (found starting with letter "")

+ + $hits):?> +
+ \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-term-list-title.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-term-list-title.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,7 @@ + + +

Titles (found starting with letter "")

+ + $hits):?> +
+ \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-term-selection-form.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-term-selection-form.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,40 @@ + +

Sources

+ +
+
+ +
+
    +
  • + checked="checked" name="browseField" value="title_s"/> + Title A-Z +
  • +
  • + checked="checked" name="browseField" value="author_c"/> + Author A-Z +
  • +
  • + +
  • +
+
+
+ +
+
+
\ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch-title-block-form.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch-title-block-form.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,40 @@ + + *
+ * + *
+ * + * @endcode + * + * @see template_preprocess_search_block_form() + */ +?> +
+ subject)): ?> +

+ +

+
+ +
+
diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,817 @@ + 'value', + '#value' => $environment['env_id'], + ); + if (isset($environment['export_type']) && $environment['export_type'] == 3) { + $verb = t('Revert'); + } + else { + $verb = t('Delete'); + } + return confirm_form( + $form, + t('Are you sure you want to !verb search environment %name?', array('%name' => $environment['name'], '!verb' => strtolower($verb))), + 'admin/config/search/solrsearch', + t('This action cannot be undone.'), + $verb, + t('Cancel') + ); +} + +/** + * Submit handler for the delete form + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_delete_form_submit(array $form, array &$form_state) { + if (solrsearch_environment_delete($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was deleted')); + } + $form_state['redirect'] = 'admin/config/search/solrsearch/settings'; +} + +function solrsearch_environment_edit_delete_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/search/solrsearch/settings/' . $form_state['values']['env_id'] . '/delete'; + + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Settings page for a specific environment (or default one if not provided) + * + * @param array|bool $environment + * + * @return array Render array for a settings page + */ +function solrsearch_environment_settings_page(array $environment = array()) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + $environment = solrsearch_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Initializes output with information about which environment's setting we are + // editing, as it is otherwise not transparent to the end user. + $output = array( + 'solrsearch_environment' => array( + '#theme' => 'solrsearch_settings_title', + '#env_id' => $env_id, + ), + ); + $output['form'] = drupal_get_form('solrsearch_environment_edit_form', $environment); + return $output; +} + +/** + * Form to clone a certain environment + * + * @param array $form + * @param array $form_state + * @param array $environment + * + * @return array output of confirm_form() + */ +function solrsearch_environment_clone_form(array $form, array &$form_state, array $environment) { + $form['env_id'] = array( + '#type' => 'value', + '#value' => $environment['env_id'], + ); + return confirm_form( + $form, + t('Are you sure you want to clone search environment %name?', array('%name' => $environment['name'])), + 'admin/config/search/solrsearch', + '', + t('Clone'), + t('Cancel') + ); +} + +/** + * Submit handler for the clone form + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_clone_form_submit(array $form, array &$form_state) { + if (solrsearch_environment_clone($form_state['values']['env_id'])) { + drupal_set_message(t('The search environment was cloned')); + } + $form_state['redirect'] = 'admin/config/search/solrsearch/settings'; +} + +/** + * Submit handler for the confirmation page of cloning an environment + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_clone_submit(array $form, array &$form_state) { + $form_state['redirect'] = 'admin/config/search/solrsearch/settings/' . $form_state['values']['env_id'] . '/clone'; +} + +/** + * Form builder for adding/editing a Solr environment used as a menu callback. + */ +function solrsearch_environment_edit_form(array $form, array &$form_state, array $environment = array()) { + if (empty($environment)) { + $environment = array(); + } + $environment += array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => '', 'conf' => array()); + + $form['#environment'] = $environment; + $form['url'] = array( + '#type' => 'textfield', + '#title' => t('Solr server URL'), + '#default_value' => $environment['url'], + '#description' => t('Example: http://localhost:8983/solr'), + '#required' => TRUE, + ); + $is_default = $environment['env_id'] == solrsearch_default_environment(); + $form['make_default'] = array( + '#type' => 'checkbox', + '#title' => t('Make this Solr search environment the default'), + '#default_value' => $is_default, + '#disabled' => $is_default, + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Description'), + '#default_value' => $environment['name'], + '#required' => TRUE, + ); + $form['env_id'] = array( + '#type' => 'machine_name', + '#title' => t('Environment id'), + '#machine_name' => array( + 'exists' => 'solrsearch_environment_load', + ), + '#default_value' => $environment['env_id'], + '#disabled' => !empty($environment['env_id']), // Cannot change it once set. + '#description' => t('Unique, machine-readable identifier for this Solr environment.'), + '#required' => TRUE, + ); + $form['service_class'] = array( + '#type' => 'value', + '#value' => $environment['service_class'], + ); + $form['conf'] = array( + '#tree' => TRUE, + ); + $form['conf']['solrsearch_read_only'] = array( + '#type' => 'radios', + '#title' => t('Index write access'), + '#default_value' => isset($environment['conf']['solrsearch_read_only']) ? $environment['conf']['solrsearch_read_only'] : solrsearch_READ_WRITE, + '#options' => array(solrsearch_READ_WRITE => t('Read and write (normal)'), solrsearch_READ_ONLY => t('Read only')), + '#description' => t('Read only stops this site from sending updates to this search environment. Useful for development sites.'), + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['save'] = array( + '#type' => 'submit', + '#validate' => array('solrsearch_environment_edit_validate'), + '#submit' => array('solrsearch_environment_edit_submit'), + '#value' => t('Save'), + ); + $form['actions']['save_edit'] = array( + '#type' => 'submit', + '#validate' => array('solrsearch_environment_edit_validate'), + '#submit' => array('solrsearch_environment_edit_submit'), + '#value' => t('Save and edit'), + ); + $form['actions']['test'] = array( + '#type' => 'submit', + '#validate' => array('solrsearch_environment_edit_validate'), + '#submit' => array('solrsearch_environment_edit_test_submit'), + '#value' => t('Test connection'), + ); + if (!empty($environment['env_id']) && !$is_default) { + $form['actions']['delete'] = array( + '#type' => 'submit', + '#submit' => array('solrsearch_environment_edit_delete_submit'), + '#value' => t('Delete'), + ); + } + + // Ensures destination is an internal URL, builds "cancel" link. + if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { + $destination = $_GET['destination']; + } + else { + $destination = 'admin/config/search/solrsearch/settings'; + } + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => $destination, + ); + + return $form; +} + +/** + * Submit handler for the test button in the environment edit page + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_edit_test_submit(array $form, array &$form_state) { + $ping = solrsearch_server_status($form_state['values']['url'], $form_state['values']['service_class']); + if ($ping) { + drupal_set_message(t('Your site has contacted the Apache Solr server.')); + } + else { + drupal_set_message(t('Your site was unable to contact the Apache Solr server.'), 'error'); + } + $form_state['rebuild'] = TRUE; +} + +/** + * Validate handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_edit_validate(array $form, array &$form_state) { + $parts = parse_url($form_state['values']['url']); + foreach (array('scheme', 'host', 'path') as $key) { + if (empty($parts[$key])) { + form_set_error('url', t('The Solr server URL needs to include a !part', array('!part' => $key))); + } + } + if (isset($parts['port'])) { + // parse_url() should always give an integer for port. Since drupal_http_request() + // also uses parse_url(), we don't need to validate anything except the range. + $pattern = empty($parts['user']) ? '@://[^:]+:([^/]+)@' : '#://[^@]+@[^:]+:([^/]+)#'; + preg_match($pattern, $form_state['values']['url'], $m); + if (empty($m[1]) || !ctype_digit($m[1]) || $m[1] < 1 || $m[1] > 65535) { + form_set_error('port', t('The port has to be an integer between 1 and 65535.')); + } + else { + // Normalize the url by removing extra slashes and whitespace. + $form_state['values']['url'] = trim($form_state['values']['url'], "/ \t\r\n\0\x0B"); + } + } +} + +/** + * Submit handler for the environment edit page + * + * @param array $form + * @param array $form_state + */ +function solrsearch_environment_edit_submit(array $form, array &$form_state) { + solrsearch_environment_save($form_state['values']); + if (!empty($form_state['values']['make_default'])) { + solrsearch_set_default_environment($form_state['values']['env_id']); + } + cache_clear_all('solrsearch:environments', 'cache_solrsearch'); + drupal_set_message(t('The %name search environment has been saved.', array('%name' => $form_state['values']['name']))); + if ($form_state['values']['op'] == t('Save')) { + $form_state['redirect'] = 'admin/config/search/solrsearch/settings'; + } + else { + $form_state['redirect'] = current_path(); + } + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); +} + +/** + * Check to see if the facetapi module is installed, and if not put up + * a message. + * + * Only call this function if the user is already in a position for this to + * be useful. + */ +function solrsearch_check_facetapi() { + if (!module_exists('facetapi')) { + $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'facetapi'", 0, 1) + ->fetchField(); + if ($filename && file_exists($filename)) { + drupal_set_message(t('If you enable the facetapi module, Apache Solr Search will provide you with configurable facets.', array('@modules' => url('admin/modules')))); + } + else { + drupal_set_message(t('If you install the facetapi module from !href, Apache Solr Search will provide you with configurable facets.', array('!href' => url('http://drupal.org/project/facetapi')))); + } + } +} + +/** + * Form builder for general settings used as a menu callback. + * + * @param array $form + * @param array $form_state + * + * @return array Output of the system_settings_form() + */ +function solrsearch_settings(array $form, array &$form_state) { + $form = array(); + $rows = array(); + + // Environment settings // Take environment settings form solrsearch + $id = solrsearch_default_environment(); + + $environments = solrsearch_load_all_environments(); + $default_environment = solrsearch_default_environment(); + solrsearch_check_facetapi(); + + // Reserve a row for the default one + $rows[$default_environment] = array(); + + foreach ($environments as $environment_id => $data) { + // Define all the Operations + $confs = array(); + $ops = array(); + // Whenever facetapi is enabled we also enable our operation link + if (module_exists('facetapi')) { + $confs['facets'] = array( + 'class' => 'operation', + 'data' => l(t('Facets'), + 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/facets', + array('query' => array('destination' => current_path())) + ), + ); + } + // These are our result and bias settings + if (module_exists('solrsearch_search')) { + $confs['result_bias'] = array( + 'class' => 'operation', + 'data' => l(t('Bias'), + 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/bias', + array('query' => array('destination' => current_path())) + ), + ); + } + + $ops['edit'] = array( + 'class' => 'operation', + 'data' => l(t('Edit'), + 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/edit', + array('query' => array('destination' => current_path())) + ), + ); + + $ops['clone'] = array( + 'class' => 'operation', + 'data' => l(t('Clone'), + 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/clone', + array('query' => array('destination' => $_GET['q'])) + ), + ); + $env_name = l($data['name'], 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/edit', array('query' => array('destination' => $_GET['q']))); + + // Is this row our default environment? + if ($environment_id == $default_environment) { + $env_name = t('!environment (Default)', array('!environment' => $env_name)); + $env_class_row = 'default-environment'; + } + else { + $env_class_row = ''; + } + // For every non-default we add a delete link + // Allow to revert a search environment or to delete it + $delete_value = ''; + if (!isset($data['in_code_only'])) { + if ((isset($data['type']) && $data['type'] == 'Overridden')) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Revert'), 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/delete'), + ); + } + // don't allow the deletion of the default environment + elseif ($environment_id != $default_environment) { + $delete_value = array( + 'class' => 'operation', + 'data' => l(t('Delete'), 'admin/config/search/solrsearch/settings/' . $data['env_id'] . '/delete'), + ); + } + } + $ops['delete'] = $delete_value; + + // When we are receiving a http POST (so the page does not show) we do not + // want to check the statusses of any environment + $class = ''; + if (empty($form_state['input'])) { + $class = solrsearch_server_status($data['url'], $data['service_class']) ? 'ok' : 'error'; + } + + $headers = array( + array('data' => t('Name'), 'colspan' => 2), + t('URL'), + array('data' => t('Configuration'), 'colspan' => count($confs)), + array('data' => t('Operations'), 'colspan' => count($ops)), + ); + + $rows[$environment_id] = array('data' => + array( + // Cells + array( + 'class' => 'status-icon', + 'data' => '
' . $class . '
', + ), + array( + 'class' => $env_class_row, + 'data' => $env_name, + ), + check_plain($data['url']), + ), + 'class' => array(drupal_html_class($class)), + ); + // Add the links to the page + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $confs); + $rows[$environment_id]['data'] = array_merge($rows[$environment_id]['data'], $ops); + } + + $form['solrsearch_host_settings']['actions'] = array( + '#markup' => '', + ); + $form['solrsearch_host_settings']['table'] = array( + '#theme' => 'table', + '#header' => $headers, + '#rows' => array_values($rows), + '#attributes' => array('class' => array('admin-solrsearch')), + ); + + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced configuration'), + '#collapsed' => TRUE, + '#collapsible' => TRUE, + ); + $form['advanced']['solrsearch_set_nodeapi_messages'] = array( + '#type' => 'radios', + '#title' => t('Extra help messages for administrators'), + '#description' => t('Adds notices to a page whenever Drupal changed content that needs reindexing'), + '#default_value' => variable_get('solrsearch_set_nodeapi_messages', 1), + '#options' => array(0 => t('Disabled'), 1 => t('Enabled')), + ); + + // Number of Items to index + $numbers = drupal_map_assoc(array(1, 5, 10, 20, 50, 100, 200)); + $default_cron_limit = variable_get('solrsearch_cron_limit', 50); + + // solrsearch_cron_limit may be overridden in settings.php. If its current + // value is not among the default set of options, add it. + if (!isset($numbers[$default_cron_limit])) { + $numbers[$default_cron_limit] = $default_cron_limit; + } + $form['advanced']['solrsearch_cron_limit'] = array( + '#type' => 'select', + '#title' => t('Number of items to index per cron run'), + '#default_value' => $default_cron_limit, + '#options' => $numbers, + '#description' => t('Reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) + ); + + $options = array('solrsearch:show_error' => t('Show error message')); + $system_info = system_get_info('module'); + if (module_exists('search')) { + foreach (search_get_info() as $module => $search_info) { + // Don't allow solrsearch to return results on failure of solrsearch. + if ($module == 'solrsearch_search') { + continue; + } + $options[$module] = t('Show @name search results', array('@name' => $system_info[$module]['name'])); + } + } + + $options['solrsearch:show_no_results'] = t('Show no results'); + $form['advanced']['solrsearch_failure'] = array( + '#type' => 'select', + '#title' => t('On failure'), + '#options' => $options, + '#default_value' => variable_get('solrsearch_failure', 'solrsearch:show_error'), + ); + + return system_settings_form($form); +} + +/** + * Gets information about the fields already in solr index. + * + * @param array $environment + * The environment for which we need to ask the status from + * + * @return array page render array + */ +function solrsearch_status_page($environment = array()) { + + if (empty($environment)) { + + $env_id = solrsearch_default_environment(); + $environment = solrsearch_environment_load($env_id); + } + else { + $env_id = $environment['env_id']; + } + + // Check for availability + if (!solrsearch_server_status($environment['url'], $environment['service_class'])) { + + drupal_set_message(t('The server seems to be unavailable. Please verify the server settings at the settings page', array('!settings_page' => url("admin/config/search/solrsearch/settings/{$environment['env_id']}/edit", array('query' => drupal_get_destination())))), 'warning'); + return ''; + } + + try { + $solr = solrsearch_get_solr($environment["env_id"]); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + $data = new stdClass; + $data->fields = array(); + } + + $messages = array(); + + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $output['solrsearch_index_action_status'] = array( + '#prefix' => '

' . t('@environment: Search Index Content', array('@environment' => $environment['name'])) . '

', + '#theme' => 'table', + '#header' => array(t('Type'), t('Value')), + '#rows' => $messages, + ); + + $output['viewmore'] = array( + '#markup' => l(t('View more details on the search index contents'), 'admin/reports/solrsearch'), + ); + + $write_status = solrsearch_environment_variable_get($env_id, 'solrsearch_read_only', solrsearch_READ_WRITE); + if ($write_status == solrsearch_READ_WRITE) { + /*$output['index_action_form'] = drupal_get_form('solrsearch_index_action_form', $env_id); + $output['index_config_form'] = drupal_get_form('solrsearch_index_config_form', $env_id);*/ + } + else { + drupal_set_message(t('Options for deleting and re-indexing are not available because the index is read-only. This can be changed on the settings page', array('!settings_page' => url('admin/config/search/solrsearch/settings/' . $env_id . '/edit', array('query' => drupal_get_destination())))), 'warning'); + } + + return $output; +} + +/** + * Get the report, eg.: some statistics and useful data from the Apache Solr index + * + * @param array $environment + * + * @return array page render array + */ +function solrsearch_index_report(array $environment = array()) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + drupal_goto('admin/reports/solrsearch/' . $env_id); + } + $environments = solrsearch_load_all_environments(); + $environments_list = array(); + foreach ($environments as $env) { + $var_status = array('!name' =>$env['name']); + $environments_list[] = l(t('Statistics for !name', $var_status), 'admin/reports/solrsearch/' . $env['env_id']); + } + $output['environments_list'] = array( + '#theme' => 'item_list', + '#items' => $environments_list, + ); + + try { + $solr = solrsearch_get_solr($environment['env_id']); + $solr->clearCache(); + $data = $solr->getLuke(); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + return $output; + } + + $messages = array(); + $messages[] = array(t('Number of documents in index'), $data->index->numDocs); + + $limit = variable_get('solrsearch_luke_limit', 20000); + if (isset($data->index->numDocs) && $data->index->numDocs > $limit) { + $messages[] = array(t('Limit'), t('You have more than @limit documents, so term frequencies are being omitted for performance reasons.', array('@limit' => $limit))); + $not_found = t('Omitted'); + } + elseif (isset($data->index->numDocs)) { + $not_found = t('Not indexed'); + try { + $solr = solrsearch_get_solr($environment['env_id']); + // Note: we use 2 since 1 fails on Ubuntu Hardy. + $data = $solr->getLuke(2); + if (isset($data->index->numTerms)) { + $messages[] = array(t('# of terms in index'), $data->index->numTerms); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + $data->fields = array(); + } + } + // Initializes output with information about which server's setting we are + // editing, as it is otherwise not transparent to the end user. + $fields = (array)$data->fields; + if ($fields) { + $messages[] = array(t('# of fields in index'), count($fields)); + } + + // Output the messages we have for this page + $output['solrsearch_index_report'] = array( + '#theme' => 'table', + '#header' => array('type', 'value'), + '#rows' => $messages, + ); + + if ($fields) { + // Initializes table header. + $header = array( + 'name' => t('Field name'), + 'type' => t('Index type'), + 'terms' => t('Distinct terms'), + ); + + // Builds table rows. + $rows = array(); + foreach ($fields as $name => $field) { + // TODO: try to map the name to something more meaningful. + $rows[$name] = array( + 'name' => $name, + 'type' => $field->type, + 'terms' => isset($field->distinct) ? $field->distinct : $not_found + ); + } + ksort($rows); + // Output the fields we found for this environment + $output['field_table'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + ); + } + else { + $output['field_table'] = array('#markup' => t('No data on indexed fields.')); + } + return $output; +} + +/** + * Page callback to show available conf files. + * + * @param array $environment + * + * @return string + * A non-render array but plain theme output for the config files overview. Could be done better probably + */ +function solrsearch_config_files_overview(array $environment = array()) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $xml = NULL; + try { + $solr = solrsearch_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('wt' => 'xml')); + $xml = simplexml_load_string($response->data); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + + if ($xml) { + // Retrieve our items from the xml using xpath + $items = $xml->xpath('//lst[@name="files"]/lst'); + + // Add all the data of the file in a files array + $files = array(); + foreach ($items as $item_id => $item) { + // Do not list directories. Always a bool + if (isset($item->bool)) { + break; + } + // Get data from the files. + $name = $item->attributes(); + $name = ((string)$item->attributes()) ? (string)$item->attributes() : t('No name found'); + $files[$item_id]['name'] = l($name, 'admin/reports/solrsearch/' . $env_id . '/conf/' . $name); + + // Retrieve the date attribute + if (isset($item->date)) { + $modified = ((string)$item->date->attributes() == 'modified') ? (string) $item->date : t('No date found'); + $files[$item_id]['modified'] = format_date(strtotime($modified)); + } + + // Retrieve the size attribute + if (isset($item->long)) { + $size = ((string)$item->long->attributes() == 'size') ? (string) $item->long : t('No size found'); + $files[$item_id]['size'] = t('Size (bytes): @bytes', array('@bytes' => $size)); + } + } + // Sort our files alphabetically + ksort($files); + + // Initializes table header. + $header = array( + 'name' => t('File name'), + 'date' => t('Modified'), + 'size' => t('Size'), + ); + + // Display the table of field names, index types, and term counts. + $variables = array( + 'header' => $header, + 'rows' => $files, + ); + $output = theme('table', $variables); + } + else { + $output = '

' . t('No data about any file found.') . "

\n"; + } + return $output; +} + +/** + * Page callback to show one conf file. + * + * @param string $name + * @param array $environment + * + * @return string + * the requested config file + */ +function solrsearch_config_file($name, array $environment = array()) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + } + else { + $env_id = $environment['env_id']; + } + + $output = ''; + try { + $solr = solrsearch_get_solr($env_id); + $response = $solr->makeServletRequest('admin/file', array('file' => $name)); + $raw_file = $response->data; + $output = '
' . check_plain($raw_file) . '
'; + drupal_set_title(check_plain($name)); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), "warning"); + } + return $output; +} + + + +/** + * Page callback for node/%node/devel/solrsearch. + * + * @param object $node + * @return string debugging information + */ +function solrsearch_devel($node) { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $item = new stdClass(); + $item->entity_type = 'node'; + $item->entity_id = $node->nid; + $output = ''; + foreach (solrsearch_load_all_environments() as $env_id => $environment) { + $documents = solrsearch_index_entity_to_documents($item, $env_id); + $output .= '

' . t('Environment %name (%env_id)', array('%name' => $environment['name'], '%env_id' => $env_id)). '

'; + foreach ($documents as $document) { + $debug_data = array(); + foreach ($document as $key => $value) { + $debug_data[$key] = $value; + } + $output .= kdevel_print_object($debug_data); + } + } + return $output; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,22 @@ +p.solrext-help, +div.solrext { +display: block; +color: white; +padding: 5px; +background-color: black; +border: 4px solid white; +-webkit-border-radius: 8px; +-moz-border-radius: 8px; +border-radius: 8px; +} + + +div.solrsearch_search_form div input.form-submit { + +background: url("http://localhost/drupal-dev/themes/bartik/images/search-button.png") no-repeat scroll center top transparent; +text-indent: -9999px; + } + +.solrsearch_internal{ +background-color: red; +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.index.inc_unused --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.index.inc_unused Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1469 @@ + $info) { + // With each pass through the callback, retrieve the next group of nids. + $rows = solrsearch_index_get_entities_to_index($env_id, $entity_type, $limit); + $documents = array(); + foreach ($rows as $row) { + $row_documents = solrsearch_index_entities_document($row, $entity_type, $env_id); + $documents = array_merge($documents, $row_documents); + } + + $indexed = solrsearch_index_send_to_solr($env_id, $documents); + if ($indexed !== FALSE) { + $documents_submitted += count($documents); + $index_position = solrsearch_get_last_index_position($env_id, $entity_type); + $max_changed = $index_position['last_changed']; + $max_entity_id = $index_position['last_entity_id']; + foreach ($rows as $row) { + if (!empty($row->status)) { + if ($row->changed > $max_changed) { + $max_changed = $row->changed; + } + if ($row->entity_id > $max_entity_id) { + $max_entity_id = $row->entity_id; + } + } + } + solrsearch_set_last_index_position($env_id, $entity_type, $max_changed, $max_entity_id); + solrsearch_set_last_index_updated($env_id, REQUEST_TIME); + } + } + return $documents_submitted; +} + +/** + * Convert a certain entity from the solrsearch index table to a set of documents. 1 entity + * can be converted in multiple documents if the solrsearch_index_entity_to_documents decides to do so. + * + * @param array $row + * A row from the indexing table + * @param string $entity_type + * The type of the entity + * @param string $env_id + * The machine name of the environment. + * + * @return array of solrsearchDocument(s) + */ +function solrsearch_index_entities_document($row, $entity_type, $env_id) { + $documents = array(); + if (!empty($row->status)) { + // Let any module exclude this entity from the index. + $build_document = TRUE; + foreach (module_implements('solrsearch_exclude') as $module) { + $exclude = module_invoke($module, 'solrsearch_exclude', $row->entity_id, $entity_type, $row, $env_id); + // If the hook returns TRUE we should exclude the entity + if (!empty($exclude)) { + $build_document = FALSE; + } + } + foreach (module_implements('solrsearch_' . $entity_type . '_exclude') as $module) { + $exclude = module_invoke($module, 'solrsearch_' . $entity_type . '_exclude', $row->entity_id, $row, $env_id); + // If the hook returns TRUE we should exclude the entity + if (!empty($exclude)) { + $build_document = FALSE; + } + } + if ($build_document) { + $documents = array_merge($documents, solrsearch_index_entity_to_documents($row, $env_id)); + } + } + else { + // Delete the entity from our index if the status callback returned 0 + solrsearch_remove_entity($env_id, $row->entity_type, $row->entity_id); + } + // Clear entity cache for this specific entity + entity_get_controller($row->entity_type)->resetCache(array($row->entity_id)); + return $documents; +} +/** + * Returns the total number of documents that are able to be indexed and the + * number of documents left to be indexed. + * + * This is a helper function for modules that implement hook_search_status(). + * + * @param string $env_id + * The machine name of the environment. + * + * @return array + * An associative array with the key-value pairs: + * - remaining: The number of items left to index. + * - total: The total number of items to index. + * + * @see hook_search_status() + */ +function solrsearch_index_status($env_id) { + $remaining = 0; + $total = 0; + + foreach (entity_get_info() as $entity_type => $info) { + $bundles = solrsearch_get_index_bundles($env_id, $entity_type); + if (empty($bundles)) { + continue; + } + + $table = solrsearch_get_indexer_table($entity_type); + $query = db_select($table, 'asn')->condition('asn.status', 1)->condition('asn.bundle', $bundles); + $total += $query->countQuery()->execute()->fetchField(); + + // Get $last_entity_id and $last_changed. + $last_index_position = solrsearch_get_last_index_position($env_id, $entity_type); + $last_entity_id = $last_index_position['last_entity_id']; + $last_changed = $last_index_position['last_changed']; + + // Find the remaining entities to index for this entity type. + $query = db_select($table, 'aie') + ->condition('aie.bundle', $bundles) + ->condition('aie.status', 1) + ->condition(db_or() + ->condition('aie.changed', $last_changed, '>') + ->condition(db_and() + ->condition('aie.changed', $last_changed, '<=') + ->condition('aie.entity_id', $last_entity_id, '>'))) + ->addTag('solrsearch_index_' . $entity_type); + + + if ($table == 'solrsearch_index_entities') { + // Other, entity-specific tables don't need this condition. + $query->condition('aie.entity_type', $entity_type); + } + $remaining += $query->countQuery()->execute()->fetchField(); + } + return array('remaining' => $remaining, 'total' => $total); +} + +/** + * Worker callback for solrsearch_index_entities(). + * + * Loads and proccesses the entity queued for indexing and converts into one or + * more documents that are sent to the Apache Solr server for indexing. + * + * The entity is loaded as the user specified in the "solrsearch_index_user" + * system variable in order to prevent sentive data from being indexed and + * displayed to underprivileged users in search results. The index user defaults + * to a user ID of "0", which is the anonymous user. + * + * After the entity is loaded, it is converted to an array via the callback + * specified in the entity type's info array. The array that the entity is + * converted to is the model of the document sent to the Apache Solr server for + * indexing. This function allows develoeprs to modify the document by + * implementing the following hooks: + * - solrsearch_index_document_build() + * - solrsearch_index_document_build_ENTITY_TYPE() + * - solrsearch_index_documents_alter() + * + * @param stdClass $item + * The data returned by the queue table containing: + * - entity_id: An integer containing the unique identifier of the entity, for + * example a node ID or comment ID. + * - entity_type: The unique identifier for the entity, i.e. "node", "file". + * - bundle: The machine-readable name of the bundle the passed entity is + * associated with. + * - status: The "published" status of the entity. The status will also be set + * to "0" when entity is deleted but the Apache Solr server is unavailable. + * - changed: A timestamp flagging when the entity was last modified. + * @param string $env_id + * The machine name of the environment. + * + * @return array + * An associative array of documents that are sent to the Apache Solr server + * for indexing. + * + * @see solrsearch_index_nodes() for the old-skool version. + */ +function solrsearch_index_entity_to_documents($item, $env_id) { + global $user; + drupal_save_session(FALSE); + $saved_user = $user; + // build the content for the index as an anonymous user to avoid exposing restricted fields and such. + // By setting a variable, indexing can take place as a different user + $uid = variable_get('solrsearch_index_user', 0); + if ($uid == 0) { + $user = drupal_anonymous_user(); + } + else { + $user = user_load($uid); + } + // Pull out all of our pertinent data. + $entity_type = $item->entity_type; + + // Entity cache will be reset at the end of the indexing algorithm, to use the cache properly whenever + // the code does another entity_load + $entity = entity_load($entity_type, array($item->entity_id)); + $entity = $entity ? reset($entity) : FALSE; + + if (empty($entity)) { + // If the object failed to load, just stop. + return FALSE; + } + + list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + + // Create a new document, and do the bare minimum on it. + $document = _solrsearch_index_process_entity_get_document($entity, $entity_type); + + //Get the callback array to add stuff to the document + $callbacks = solrsearch_entity_get_callback($entity_type, 'document callback', $bundle); + $documents = array(); + foreach ($callbacks as $callback) { + // Call a type-specific callback to add stuff to the document. + $documents = array_merge($documents, $callback($document, $entity, $entity_type, $env_id)); + } + + //do this for all possible documents that were returned by the callbacks + foreach ($documents as $document) { + // Call an all-entity hook to add stuff to the document. + module_invoke_all('solrsearch_index_document_build', $document, $entity, $entity_type, $env_id); + + // Call a type-specific hook to add stuff to the document. + module_invoke_all('solrsearch_index_document_build_' . $entity_type, $document, $entity, $env_id); + + // Final processing to ensure that the document is properly structured. + // All records must have a label field, which is used for user-friendly labeling. + if (empty($document->label)) { + $document->label = ''; + } + + // All records must have a "content" field, which is used for fulltext indexing. + // If we don't have one, enter an empty value. This does mean that the entity + // will not be fulltext searchable. + if (empty($document->content)) { + $document->content = ''; + } + + // All records must have a "teaser" field, which is used for abbreviated + // displays when no highlighted text is available. + if (empty($document->teaser)) { + $document->teaser = truncate_utf8($document->content, 300, TRUE); + } + + // Add additional indexing based on the body of each record. + solrsearch_index_add_tags_to_document($document, $document->content); + } + + // Now allow modules to alter each other's additions for maximum flexibility. + + // Hook to allow modifications of the retrieved results + foreach (module_implements('solrsearch_index_documents_alter') as $module) { + $function = $module . '_solrsearch_index_documents_alter'; + $function($documents, $entity, $entity_type, $env_id); + } + + // Restore the user. + $user = $saved_user; + drupal_save_session(TRUE); + + return $documents; +} + +/** + * Index an array of documents to solr. + * + * @param $env_id + * @param array $documents + * + * @return bool|int number indexed, or FALSE on failure. + * @throws Exception + */ +function solrsearch_index_send_to_solr($env_id, array $documents) { + try { + // Get the $solr object + $solr = solrsearch_get_solr($env_id); + // If there is no server available, don't continue. + if (!$solr->ping(variable_get('solrsearch_ping_timeout', 4))) { + throw new Exception(t('No Solr instance available during indexing.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } + // Do not index when we do not have any documents to send + // Send TRUE because this is not an error + if (empty($documents)) { + return TRUE; + } + // Send the document off to Solr. + watchdog('Apache Solr', 'Adding @count documents.', array('@count' => count($documents))); + try { + $docs_chunk = array_chunk($documents, 20); + foreach ($docs_chunk as $docs) { + $solr->addDocuments($docs); + } + watchdog('Apache Solr', 'Indexing succeeded on @count documents', array( + '@count' => count($documents), + ), WATCHDOG_INFO); + return count($documents); + } + catch (Exception $e) { + if (!empty($docs)) { + foreach ($docs as $doc) { + $eids[] = $doc->entity_type . '/' . $doc->entity_id; + } + } + watchdog('Apache Solr', 'Indexing failed on one of the following entity ids: @eids
!message', array( + '@eids' => implode(', ', $eids), + '!message' => nl2br(strip_tags($e->getMessage())), + ), WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Extract HTML tag contents from $text and add to boost fields. + * + * @param solrsearchDocument $document + * @param string $text + * must be stripped of control characters before hand. + * + */ +function solrsearch_index_add_tags_to_document(solrsearchDocument $document, $text) { + $tags_to_index = variable_get('solrsearch_tags_to_index', array( + 'h1' => 'tags_h1', + 'h2' => 'tags_h2_h3', + 'h3' => 'tags_h2_h3', + 'h4' => 'tags_h4_h5_h6', + 'h5' => 'tags_h4_h5_h6', + 'h6' => 'tags_h4_h5_h6', + 'u' => 'tags_inline', + 'b' => 'tags_inline', + 'i' => 'tags_inline', + 'strong' => 'tags_inline', + 'em' => 'tags_inline', + 'a' => 'tags_a' + )); + + // Strip off all ignored tags. + $text = strip_tags($text, '<' . implode('><', array_keys($tags_to_index)) . '>'); + + preg_match_all('@<(' . implode('|', array_keys($tags_to_index)) . ')[^>]*>(.*)@Ui', $text, $matches); + foreach ($matches[1] as $key => $tag) { + $tag = drupal_strtolower($tag); + // We don't want to index links auto-generated by the url filter. + if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) { + if (!isset($document->{$tags_to_index[$tag]})) { + $document->{$tags_to_index[$tag]} = ''; + } + $document->{$tags_to_index[$tag]} .= ' ' . solrsearch_clean_text($matches[2][$key]); + } + } +} + +/** + * Returns a generic Solr document object for this entity. + * + * This function will do the basic processing for the document that is common + * to all entities, but virtually all entities will need their own additional + * processing. + * + * @param object $entity + * The entity for which we want a document. + * @param string $entity_type + * The type of entity we're processing. + * @return solrsearchDocument + */ +function _solrsearch_index_process_entity_get_document($entity, $entity_type) { + list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + + $document = new solrsearchDocument(); + + // Define our url options in advance. This differs depending on the + // language + $languages = language_list(); + $url_options = array('absolute' => TRUE); + if (isset($entity->language) && isset($languages[$entity->language])) { + $url_options = $url_options + array('language' => $languages[$entity->language]); + } + + $document->id = solrsearch_document_id($entity_id, $entity_type); + $document->site = url(NULL, $url_options); + $document->hash = solrsearch_site_hash(); + + $document->entity_id = $entity_id; + $document->entity_type = $entity_type; + $document->bundle = $bundle; + $document->bundle_name = entity_bundle_label($entity_type, $bundle); + + if (empty($entity->language)) { + // 'und' is the language-neutral code in Drupal 7. + $document->language = LANGUAGE_NONE; + } + else { + $document->language = $entity->language; + } + + $path = entity_uri($entity_type, $entity); + // A path is not a requirement of an entity + if (!empty($path)) { + $document->path = $path['path']; + $document->url = url($path['path'], $path['options'] + $url_options); + // Path aliases can have important information about the content. + // Add them to the index as well. + if (function_exists('drupal_get_path_alias')) { + // Add any path alias to the index, looking first for language specific + // aliases but using language neutral aliases otherwise. + $output = drupal_get_path_alias($document->path, $document->language); + if ($output && $output != $document->path) { + $document->path_alias = $output; + } + } + } + return $document; +} + +/** + * Returns an array of rows from a query based on an indexing environment. + * @todo Remove the read only because it is not environment specific + * + * @param $env_id + * @param $entity_type + * @param $limit + * + * @return array list of row to index + */ +function solrsearch_index_get_entities_to_index($env_id, $entity_type, $limit) { + $rows = array(); + if (variable_get('solrsearch_read_only', 0)) { + return $rows; + } + $bundles = solrsearch_get_index_bundles($env_id, $entity_type); + if (empty($bundles)) { + return $rows; + } + + $table = solrsearch_get_indexer_table($entity_type); + // Get $last_entity_id and $last_changed. + $last_index_position = solrsearch_get_last_index_position($env_id, $entity_type); + $last_entity_id = $last_index_position['last_entity_id']; + $last_changed = $last_index_position['last_changed']; + + // Find the next batch of entities to index for this entity type. Note that + // for ordering we're grabbing the oldest first and then ordering by ID so + // that we get a definitive order. + // Also note that we fetch ALL fields from the indexer table + $query = db_select($table, 'aie') + ->fields('aie') + ->condition('aie.bundle', $bundles) + ->condition(db_or() + ->condition('aie.changed', $last_changed, '>') + ->condition(db_and() + ->condition('aie.changed', $last_changed, '<=') + ->condition('aie.entity_id', $last_entity_id, '>'))) + ->orderBy('aie.changed', 'ASC') + ->orderBy('aie.entity_id', 'ASC') + ->addTag('solrsearch_index_' . $entity_type); + + if ($table == 'solrsearch_index_entities') { + // Other, entity-specific tables don't need this condition. + $query->condition('aie.entity_type', $entity_type); + } + $query->range(0, $limit); + $records = $query->execute(); + + $status_callbacks = solrsearch_entity_get_callback($entity_type, 'status callback'); + foreach ($records as $record) { + // Check status and status callbacks before sending to the index + if (is_array($status_callbacks)) { + foreach($status_callbacks as $status_callback) { + if (is_callable($status_callback)) { + // by placing $status in front we prevent calling any other callback + // after one status callback returned false + $record->status = $record->status && $status_callback($record->entity_id, $record->entity_type); + } + } + } + $rows[] = $record; + } + return $rows; +} + +/** + * Delete the whole index for an environment. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * (optional) specify to remove just this entity_type from the index. + * @param string $bundle + * (optional) also specify a bundle to remove just the bundle from + * the index. + */ +function solrsearch_index_delete_index($env_id, $entity_type = NULL, $bundle = NULL) { + // Instantiate a new Solr object. + try { + $solr = solrsearch_get_solr($env_id); + $query = '*:*'; + + if (!empty($entity_type) && !empty($bundle)) { + $query = "(bundle:$bundle AND entity_type:$entity_type) OR sm_parent_entity_bundle:{$entity_type}-{$bundle}"; + } + elseif (!empty($bundle)) { + $query = "(bundle:$bundle)"; + } + + // Allow other modules to modify the delete query. + // For example, use the site hash so that you only delete this site's + // content: $query = 'hash:' . solrsearch_site_hash() + drupal_alter('solrsearch_delete_by_query', $query); + $solr->deleteByQuery($query); + $solr->commit(); + + if (!empty($entity_type)) { + $rebuild_callback = solrsearch_entity_get_callback($entity_type, 'reindex callback'); + if (is_callable($rebuild_callback)) { + $rebuild_callback($env_id, $bundle); + } + } + else { + solrsearch_index_mark_for_reindex($env_id); + } + + solrsearch_set_last_index_updated($env_id, REQUEST_TIME); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +/** + * Delete from the index documents with the entity type and any of the excluded bundles. + * + * Also deletes all documents that have the entity type and bundle as a parent. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * @param array $excluded_bundles + * + * @return true on success, false on failure. + */ +function solrsearch_index_delete_bundles($env_id, $entity_type, array $excluded_bundles) { + // Remove newly omitted bundles. + try { + $solr = solrsearch_get_solr($env_id); + foreach ($excluded_bundles as $bundle) { + $query = "(bundle:$bundle AND entity_type:$entity_type) OR sm_parent_entity_bundle:{$entity_type}-{$bundle}"; + + // Allow other modules to modify the delete query. + // For example, use the site hash so that you only delete this site's + // content: $query = 'hash:' . solrsearch_site_hash() + drupal_alter('solrsearch_delete_by_query', $query); + $solr->deleteByQuery($query); + } + if ($excluded_bundles) { + $solr->commit(); + } + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Delete an entity from the index. + * + * Also deletes all documents that have the deleted document as a parent. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * @param string $entity_id + * + * @return true on success, false on failure. + */ +function solrsearch_index_delete_entity_from_index($env_id, $entity_type, $entity_id) { + static $failed = FALSE; + if ($failed) { + return FALSE; + } + try { + $solr = solrsearch_get_solr($env_id); + $document_id = solrsearch_document_id($entity_id, $entity_type); + $query = "id:\"$document_id\" OR sm_parent_document_id:\"$document_id\""; + $solr->deleteByQuery($query); + solrsearch_set_last_index_updated($env_id, REQUEST_TIME); + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + // Don't keep trying queries if they are failing. + $failed = TRUE; + return FALSE; + } +} + +/** + * Mark a certain entity type for a specific environment for reindexing. + * + * @param $env_id + * @param null $entity_type + */ +function solrsearch_index_mark_for_reindex($env_id, $entity_type = NULL) { + foreach (entity_get_info() as $type => $entity_info) { + if (($type == $entity_type) || ($entity_type == NULL)) { + if (isset($entity_info['solrsearch']) && ($entity_info['solrsearch']['indexable'])) { + $reindex_callback = solrsearch_entity_get_callback($type, 'reindex callback'); + if (!empty($reindex_callback)) { + call_user_func($reindex_callback, $env_id); + } + } + } + } + solrsearch_clear_last_index_position($env_id, $entity_type); + cache_clear_all('*', 'cache_solrsearch', TRUE); +} + +/** + * Sets what bundles on the specified entity type should be indexed. + * + * @param string $env_id + * The machine name of the environment. + * @param string $entity_type + * The entity type to index. + * @param array $bundles + * The machine names of the bundles to index. + * + * @throws Exception + */ +function solrsearch_index_set_bundles($env_id, $entity_type, array $bundles) { + $transaction = db_transaction(); + try { + db_delete('solrsearch_index_bundles') + ->condition('env_id', $env_id) + ->condition('entity_type', $entity_type) + ->execute(); + + if ($bundles) { + $insert = db_insert('solrsearch_index_bundles') + ->fields(array('env_id', 'entity_type', 'bundle')); + + foreach ($bundles as $bundle) { + $insert->values(array( + 'env_id' => $env_id, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + )); + } + $insert->execute(); + } + } + catch (Exception $e) { + $transaction->rollback(); + // Re-throw the exception so we are aware of the failure. + throw $e; + } +} + +// This really should be in core, but it isn't yet. When it gets added to core, +// we can remove this version. +// @see http://drupal.org/node/969180 +if (!function_exists('entity_bundle_label')) { + +/** + * Returns the label of a bundle. + * + * @param string $entity_type + * The entity type; e.g. 'node' or 'user'. + * @param string $bundle_name + * The bundle for which we want the label from + * + * @return + * A string with the human-readable name of the bundle, or FALSE if not specified. + */ +function entity_bundle_label($entity_type, $bundle_name) { + $labels = &drupal_static(__FUNCTION__, array()); + + if (empty($labels)) { + foreach (entity_get_info() as $type => $info) { + foreach ($info['bundles'] as $bundle => $bundle_info) { + $labels[$type][$bundle] = !empty($bundle_info['label']) ? $bundle_info['label'] : FALSE; + } + } + } + + return $labels[$entity_type][$bundle_name]; +} + +} + +/** + * Builds the node-specific information for a Solr document. + * + * @param solrsearchDocument $document + * The Solr document we are building up. + * @param object $node + * The entity we are indexing. + * @param string $entity_type + * The type of entity we're dealing with. + * @param string $env_id + * The type of entity we're dealing with. + * + * @return array A set of solrsearchDocument documents + */ +function solrsearch_index_node_solr_document(solrsearchDocument $document, $node, $entity_type, $env_id) { + // None of these get added unless they are explicitly in our schema.xml + $document->label = solrsearch_clean_text($node->title); + + // Build the node body. + $build = node_view($node, 'search_index', !empty($node->language) ? $node->language : LANGUAGE_NONE); + // Remove useless html crap out of the render. + unset($build['#theme']); + $text = drupal_render($build); + $document->content = solrsearch_clean_text($text); + + // Adding the teaser + if (isset($node->teaser)) { + $document->teaser = solrsearch_clean_text($node->teaser); + } + else { + $document->teaser = truncate_utf8($document->content, 300, TRUE); + } + + // Path aliases can have important information about the content. + // Add them to the index as well. + if (function_exists('drupal_get_path_alias')) { + // Add any path alias to the index, looking first for language specific + // aliases but using language neutral aliases otherwise. + $language = empty($node->language) ? NULL : $node->language; + $path = 'node/' . $node->nid; + $output = drupal_get_path_alias($path, $language); + if ($output && $output != $path) { + $document->path_alias = $output; + } + } + + // Author information + $document->ss_name = $node->name; + // We want the name to be searchable for keywords. + $document->tos_name = $node->name; + + // Index formatted username so it can be searched and sorted on. + $account = (object) array('uid' => $node->uid, 'name' => $node->name); + $username = format_username($account); + $document->ss_name_formatted = $username; + $document->tos_name_formatted = $username; + $document->is_uid = $node->uid; + $document->bs_status = $node->status; + $document->bs_sticky = $node->sticky; + $document->bs_promote = $node->promote; + $document->is_tnid = $node->tnid; + $document->bs_translate = $node->translate; + + // Language specific checks + if (empty($node->language)) { + // 'und' is the language-neutral code in Drupal 7. + $document->ss_language = LANGUAGE_NONE; + } + else { + $document->ss_language = $node->language; + } + + // Timestamp of the node + $document->ds_created = solrsearch_date_iso($node->created); + $document->ds_changed = solrsearch_date_iso($node->changed); + + // Comment counts + time + if (isset($node->last_comment_timestamp) && !empty($node->comment_count)) { + $document->ds_last_comment_timestamp = solrsearch_date_iso($node->last_comment_timestamp); + $document->ds_last_comment_or_change = solrsearch_date_iso(max($node->last_comment_timestamp, $node->changed)); + $document->is_comment_count = $node->comment_count; + } + else { + $document->ds_last_comment_or_change = solrsearch_date_iso($node->changed); + } + + // Fetch extra data normally not visible, including comments. + // We do this manually (with module_implements instead of node_invoke_nodeapi) + // because we want a keyed array to come back. Only in this way can we decide + // whether to index comments or not. + $extra = array(); + $excludes = variable_get('solrsearch_exclude_nodeapi_types', array()); + $exclude_nodeapi = isset($excludes[$node->type]) ? $excludes[$node->type] : array(); + + foreach (module_implements('node_update_index') as $module) { + // Invoke nodeapi if this module has not been excluded, for example, + // exclude 'comment' for a type to skip indexing its comments. + if (empty($exclude_nodeapi[$module])) { + $function = $module . '_node_update_index'; + if ($output = $function($node)) { + $extra[$module] = $output; + } + } + } + + // Adding the text of the comments + if (isset($extra['comment'])) { + $comments = $extra['comment']; + // Remove comments from the extra fields + unset($extra['comment']); + $document->ts_comments = solrsearch_clean_text($comments); + // @todo: do we want to reproduce solrsearch_add_tags_to_document() for comments? + } + // If there are other extra fields, add them to the document + if (!empty($extra)) { + // Use an omit-norms text field since this is generally going to be short; not + // really a full-text field. + $document->tos_content_extra = solrsearch_clean_text(implode(' ', $extra)); + } + + // Generic use case for future reference. Callbacks can + // allow you to send back multiple documents + $documents = array(); + $documents[] = $document; + return $documents; +} + +/** + * Function that will be executed if the node bundles were updated. + * Currently it does nothing, but it could potentially do something later on. + * + * @param $env_id + * @param $existing_bundles + * @param $new_bundles + */ +function solrsearch_index_node_bundles_changed($env_id, $existing_bundles, $new_bundles) { + // Nothing to do for now. +} + +/** + * Reindexing callback for solrsearch, for nodes. + * + * @param string $env_id + * The machine name of the environment. + * @param string|null $bundle + * (optional) The bundle type to reindex. If not used + * all bundles will be re-indexed. + * + * @return null + * returns NULL if the specified bundle is not in the indexable bundles list + * + * @throws Exception + */ +function solrsearch_index_node_solr_reindex($env_id, $bundle = NULL) { + $indexer_table = solrsearch_get_indexer_table('node'); + $transaction = db_transaction(); + try { + $indexable_bundles = solrsearch_get_index_bundles($env_id, 'node'); + + if ($bundle && !empty($indexable_bundles) && !in_array($bundle, $indexable_bundles)) { + // The bundle specified is not in the indexable bundles list. + return NULL; + } + + // Leave status 0 rows - those need to be + // removed from the index later. + $delete = db_delete($indexer_table); + $delete->condition('status', 1); + + if (!empty($bundle)) { + $delete->condition('bundle', $bundle); + } + elseif (!empty($indexable_bundles)) { + $delete->condition('bundle', $indexable_bundles, 'IN'); + } + + $delete->execute(); + + $select = db_select('node', 'n'); + $select->condition('status', 1); + $select->addExpression("'node'", 'entity_type'); + $select->addField('n', 'nid', 'entity_id'); + $select->addField('n', 'type', 'bundle'); + $select->addField('n', 'status', 'status'); + $select->addExpression(REQUEST_TIME, 'changed'); + + if ($bundle) { + // Mark all nodes of the specified content type for reindexing. + $select->condition('n.type', $bundle); + } + elseif (!empty($indexable_bundles)) { + // Restrict reindex to content types in the indexable bundles list. + $select->condition('n.type', $indexable_bundles, 'IN'); + } + + $insert = db_insert($indexer_table) + ->fields(array('entity_id', 'bundle', 'status', 'entity_type', 'changed')) + ->from($select) + ->execute(); + } + catch (Exception $e) { + $transaction->rollback(); + throw $e; + } +} + +/** + * Status callback for solrsearch, for nodes. + * after indexing a certain amount of nodes + * + * @param $entity_id + * @param $entity_type + * + * @return int + * The status of the node + */ +function solrsearch_index_node_status_callback($entity_id, $entity_type) { + // Make sure we have a boolean value. + // Anything different from 1 becomes zero + $entity = entity_load($entity_type, array($entity_id)); + $entity = $entity ? reset($entity) : FALSE; + + if (empty($entity)) { + // If the object failed to load, just stop. + return FALSE; + } + $status = ($entity->status == 1 ? 1 : 0); + return $status; +} + +/** + * Callback that converts term_reference field into an array + * + * @param object $node + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + * fields that will be indexed for this term reference + */ +function solrsearch_term_reference_indexing_callback($node, $field_name, $index_key, array $field_info) { + // Keep ancestors cached + $ancestors = &drupal_static(__FUNCTION__, array()); + + $fields = array(); + $vocab_names = array(); + if (!empty($node->{$field_name}) && function_exists('taxonomy_get_parents_all')) { + $field = $node->$field_name; + list($lang, $items) = each($field); + foreach ($items as $item) { + // Triple indexing of tids lets us do efficient searches (on tid) + // and do accurate per field or per-vocabulary faceting. + + // By including the ancestors to a term in the index we make + // sure that searches for general categories match specific + // categories, e.g. Fruit -> apple, a search for fruit will find + // content categorized with apple. + if (!isset($ancestors[$item['tid']])) { + $ancestors[$item['tid']] = taxonomy_get_parents_all($item['tid']); + } + foreach ($ancestors[$item['tid']] as $ancestor) { + // Index parent term against the field. Note that this happens + // regardless of whether the facet is set to show as a hierarchy or not. + // We would need a separate field if we were to index terms without any + // hierarchy at all. + // If the term is singular, then we cannot add another value to the + // document as the field is single + if ($field_info['multiple'] == true) { + $fields[] = array( + 'key' => $index_key, + 'value' => $ancestor->tid, + ); + } + $fields[] = array( + 'key' => 'tid', + 'value' => $ancestor->tid, + ); + $fields[] = array( + 'key' => 'im_vid_' . $ancestor->vid, + 'value' => $ancestor->tid, + ); + $name = solrsearch_clean_text($ancestor->name); + $vocab_names[$ancestor->vid][] = $name; + // We index each name as a string for cross-site faceting + // using the vocab name rather than vid in field construction . + $fields[] = array( + 'key' => 'sm_vid_' . solrsearch_vocab_name($ancestor->vid), + 'value' => $name, + ); + } + } + // Index the term names into a text field for MLT queries and keyword searching. + foreach ($vocab_names as $vid => $names) { + $fields[] = array( + 'key' => 'tm_vid_' . $vid . '_names', + 'value' => implode(' ', $names), + ); + } + } + return $fields; +} + +/** + * Helper function - return a safe (PHP identifier) vocabulary name. + * + * @param integer $vid + * @return string + */ +function solrsearch_vocab_name($vid) { + $names = &drupal_static(__FUNCTION__, array()); + + if (!isset($names[$vid])) { + $vocab_name = db_query('SELECT v.name FROM {taxonomy_vocabulary} v WHERE v.vid = :vid', array(':vid' => $vid))->fetchField(); + $names[$vid] = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '_', $vocab_name); + // Fallback for names ending up all as '_'. + $check = rtrim($names[$vid], '_'); + if (!$check) { + $names[$vid] = '_' . $vid . '_'; + } + } + return $names[$vid]; +} + +/** + * Callback that converts list module field into an array + * For every multivalued value we also add a single value to be able to + * use the stats + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function solrsearch_fields_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + $numeric = TRUE; + if (!empty($entity->{$field_name})) { + $field = $entity->$field_name; + list($lang, $values) = each($field); + switch ($field_info['index_type']) { + case 'integer': + case 'half-int': + case 'sint': + case 'tint': + case 'thalf-int': + case 'boolean': + $function = 'intval'; + break; + case 'float': + case 'double': + case 'sfloat': + case 'sdouble': + case 'tfloat': + case 'tdouble': + $function = 'solrsearch_floatval'; + break; + default: + $numeric = FALSE; + $function = 'solrsearch_clean_text'; + } + for ($i = 0; $i < count($values); $i++) { + $fields[] = array( + 'key' => $index_key, + 'value' => $function($values[$i]['value']), + ); + } + // Also store the first value of the field in a singular index for multi value fields + if ($field_info['multiple'] && $numeric && !empty($values[0])) { + $singular_field_info = $field_info; + $singular_field_info['multiple'] = FALSE; + $single_key = solrsearch_index_key($singular_field_info); + $fields[] = array( + 'key' => $single_key, + 'value' => $function($values[0]['value']), + ); + } + } + return $fields; +} + +/** + * This function is used during indexing to normalize the DATE and DATETIME + * fields into the appropriate format for Apache Solr. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function solrsearch_date_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + $field = $entity->$field_name; + list($lang, $values) = each($field); + // Construct a Solr-ready date string in UTC time zone based on the field's date string and time zone. + $tz = new DateTimeZone(isset($field['timezone']) ? $field['timezone'] : 'UTC'); + + // $fields may end up having two values; one for the start date + // and one for the end date. + foreach ($values as $value) { + if ($date = date_create($value['value'], $tz)) { + $index_value = solrsearch_date_iso($date->format('U')); + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + + if (isset($value['value2'])) { + if ($date = date_create($value['value2'], $tz)) { + $index_value = solrsearch_date_iso($date->format('U')); + $fields[] = array( + // The value2 element is the end date. Therefore it gets indexed + // into its own Solr field. + 'key' => $index_key . '_end', + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * This function is used during indexing to normalize the DATESTAMP fields + * into the appropriate format for Apache Solr. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function solrsearch_datestamp_default_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + // $fields may end up having two values; one for the start date + // and one for the end date. + $field = $entity->$field_name; + list($lang, $values) = each($field); + + foreach ($values as $value) { + if (isset($value['value']) && $value['value'] != 0) { + $index_value = solrsearch_date_iso($value['value']); + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + if (isset($value['value2']) && $value['value'] != 0) { + $index_value = solrsearch_date_iso($value['value2']); + $fields[] = array( + // The value2 element is the end date. Therefore it gets indexed + // into its own Solr field. + 'key' => $index_key . '_end', + 'value' => $index_value, + ); + } + } + } + return $fields; +} + +function solrsearch_floatval($value) { + return sprintf('%0.20f', $value); +} + +/** + * Indexing callback for the node_reference module + * by the references module + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function solrsearch_nodereference_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + $index_key = solrsearch_index_key($field_info); + foreach ($entity->$field_name as $field_references) { + foreach ($field_references as $reference) { + if ($index_value = (!empty($reference['nid'])) ? $reference['nid'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * Indexing callback for the user_reference module + * by the references module + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + */ +function solrsearch_userreference_indexing_callback($entity, $field_name, $index_key, array $field_info) { + $fields = array(); + if (!empty($entity->$field_name)) { + $index_key = solrsearch_index_key($field_info); + foreach ($entity->$field_name as $field_references) { + foreach ($field_references as $reference) { + if ($index_value = (isset($reference['uid']) && strlen($reference['uid'])) ? $reference['uid'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $index_value, + ); + } + } + } + } + return $fields; +} + +/** + * Indexing callback for entityreference fields. + * + * @param object $entity + * @param string $field_name + * @param string $index_key + * @param array $field_info + * @return array $fields + * + */ +function solrsearch_entityreference_indexing_callback($entity, $field_name, $index_key, $field_info) { + $fields = array(); + if (!empty($entity->{$field_name})) { + + // Gets entity type and index key. We need to prefix the ID with the entity + // type so we know what entity we are dealing with in the mapping callback. + $entity_type = $field_info['field']['settings']['target_type']; + $index_key = solrsearch_index_key($field_info); + + // Iterates over all references and adds them to the fields. + foreach ($entity->$field_name as $entity_references) { + foreach ($entity_references as $reference) { + if ($id = (!empty($reference['target_id'])) ? $reference['target_id'] : FALSE) { + $fields[] = array( + 'key' => $index_key, + 'value' => $entity_type . ':' . $id, + ); + } + } + } + } + return $fields; +} + +/** + * Extract HTML tag contents from $text and add to boost fields. + * + * $text must be stripped of control characters before hand. + * + * @param solrsearchDocument $document + * @param type $text + */ +function solrsearch_add_tags_to_document(solrsearchDocument $document, $text) { + $tags_to_index = variable_get('solrsearch_tags_to_index', array( + 'h1' => 'tags_h1', + 'h2' => 'tags_h2_h3', + 'h3' => 'tags_h2_h3', + 'h4' => 'tags_h4_h5_h6', + 'h5' => 'tags_h4_h5_h6', + 'h6' => 'tags_h4_h5_h6', + 'u' => 'tags_inline', + 'b' => 'tags_inline', + 'i' => 'tags_inline', + 'strong' => 'tags_inline', + 'em' => 'tags_inline', + 'a' => 'tags_a' + )); + + // Strip off all ignored tags. + $text = strip_tags($text, '<' . implode('><', array_keys($tags_to_index)) . '>'); + + preg_match_all('@<(' . implode('|', array_keys($tags_to_index)) . ')[^>]*>(.*)@Ui', $text, $matches); + foreach ($matches[1] as $key => $tag) { + $tag = strtolower($tag); + // We don't want to index links auto-generated by the url filter. + if ($tag != 'a' || !preg_match('@(?:http://|https://|ftp://|mailto:|smb://|afp://|file://|gopher://|news://|ssl://|sslv2://|sslv3://|tls://|tcp://|udp://|www\.)[a-zA-Z0-9]+@', $matches[2][$key])) { + if (!isset($document->{$tags_to_index[$tag]})) { + $document->{$tags_to_index[$tag]} = ''; + } + $document->{$tags_to_index[$tag]} .= ' ' . solrsearch_clean_text($matches[2][$key]); + } + } +} + +/** + * hook_cron() helper to try to make the index table consistent with their + * respective entity table. + */ +function solrsearch_index_node_check_table() { + // Check for unpublished content that wasn't deleted from the index. + $table = solrsearch_get_indexer_table('node'); + // We do not check more nodes than double the cron limit per time + // Update or delete at most this many in each Solr query. + $limit = variable_get('solrsearch_cron_mass_limit', 500); + $query = db_select($table, 'aien') + ->fields('n', array('nid', 'status')) + ->where('aien.status <> n.status') + ->range(0, ($limit * 2)) + ->addTag('solrsearch_index_node'); + $query->innerJoin('node', 'n', 'n.nid = aien.entity_id'); + $nodes = $query->execute()->fetchAllAssoc('nid'); + + $node_lists = array_chunk($nodes, $limit, TRUE); + foreach ($node_lists as $nodes) { + watchdog('Apache Solr', 'On cron running solrsearch_nodeapi_mass_update() on nids @nids', array('@nids' => implode(',', array_keys($nodes))), WATCHDOG_NOTICE); + if (!solrsearch_index_nodeapi_mass_update($nodes, $table)) { + // Solr query failed - so stop trying. + break; + } + } + + // Check for deleted content that wasn't deleted from the index. + $query = db_select($table, 'aien') + ->isNull('n.nid') + ->range(0, ($limit*2)); + $query->addExpression('aien.entity_id', 'nid'); + $query->leftJoin('node', 'n', 'n.nid = aien.entity_id'); + $nodes = $query->execute()->fetchAllAssoc('nid'); + $node_lists = array_chunk($nodes, $limit, TRUE); + + foreach ($node_lists as $nodes) { + watchdog('Apache Solr', 'On cron running solrsearch_nodeapi_mass_delete() on nids @nids', array('@nids' => implode(',', array_keys($nodes))), WATCHDOG_NOTICE); + if (!solrsearch_index_nodeapi_mass_delete($nodes, $table)) { + // Solr query failed - so stop trying. + break; + } + } +} + +/** + * Mass Update nodes from the solr indexer table + * + * @param array $nodes + * @param string $table + * @return boolean + * true if we mass updated, false if failed + */ +function solrsearch_index_nodeapi_mass_update(array $nodes, $table = NULL) { + if (empty($nodes)) { + return TRUE; + } + if (empty($table)) { + $table = solrsearch_get_indexer_table('node'); + } + + if (solrsearch_environment_variable_get(solrsearch_default_environment(), 'solrsearch_read_only', solrsearch_READ_WRITE) == solrsearch_READ_ONLY) { + return TRUE; + } + + $published_ids = array(); + $unpublished_ids = array(); + foreach ($nodes as $node) { + if ($node->status) { + $published_ids[$node->nid] = solrsearch_document_id($node->nid); + } + else { + $unpublished_ids[$node->nid] = solrsearch_document_id($node->nid); + } + } + try { + $env_id = solrsearch_default_environment(); + $solr = solrsearch_get_solr($env_id); + $solr->deleteByMultipleIds($unpublished_ids); + solrsearch_set_last_index_updated($env_id, REQUEST_TIME); + + // There was no exception, so update the table. + if ($published_ids) { + db_update($table) + ->fields(array('changed' => REQUEST_TIME, 'status' => 1)) + ->condition('entity_id', array_keys($published_ids), 'IN') + ->execute(); + } + if ($unpublished_ids) { + db_update($table) + ->fields(array('changed' => REQUEST_TIME, 'status' => 0)) + ->condition('entity_id', array_keys($unpublished_ids), 'IN') + ->execute(); + } + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} + +/** + * Mass delete nodes from the solr indexer tables. + * + * @param array $nodes + * @param string $table + * @return boolean + * true if we mass updated, false if failed + */ +function solrsearch_index_nodeapi_mass_delete(array $nodes, $table = NULL) { + if (empty($nodes)) { + return TRUE; + } + if (empty($table)) { + $table = solrsearch_get_indexer_table('node'); + } + + if (solrsearch_environment_variable_get(solrsearch_default_environment(), 'solrsearch_read_only', solrsearch_READ_WRITE) == solrsearch_READ_ONLY) { + return TRUE; + } + + $ids = array(); + $nids = array(); + foreach ($nodes as $node) { + $ids[] = solrsearch_document_id($node->nid); + $nids[] = $node->nid; + } + try { + $env_id = solrsearch_default_environment(); + $solr = solrsearch_get_solr($env_id); + $solr->deleteByMultipleIds($ids); + solrsearch_set_last_index_updated($env_id, REQUEST_TIME); + // There was no exception, so update the table. + db_delete($table) + ->condition('entity_id', $nids, 'IN') + ->execute(); + return TRUE; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + return FALSE; + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,23 @@ +name = solrsearch +description = Frontend for searching Solr +core = 7.x +stylesheets[all][] = solrsearch.css +package = SolrSearch +dependencies[] = digitalobjects + + +configure = admin/config/search/searchsolr/settings +files[] = solrsearch.install +files[] = solrsearch.module +files[] = solrsearch.admin.inc +files[] = solrsearch.interface.inc +files[] = Drupal_Apache_Solr_Service.php +files[] = Apache_Solr_Document.php +files[] = Solr_Base_Query.php +files[] = plugins/facetapi/adapter.inc +files[] = plugins/facetapi/query_type_date.inc +files[] = plugins/facetapi/query_type_term.inc +files[] = plugins/facetapi/query_type_numeric_range.inc +files[] = plugins/facetapi/query_type_integer.inc +files[] = plugins/facetapi/query_type_geo.inc + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,899 @@ + $t('Solr search'), + 'value' => $t('Missing environment configuration'), + 'description' => $t('Missing or invalid Solr environment record for the default environment ID %id.', array('%id' => $id)), + 'severity' => REQUIREMENT_ERROR, + ); + } + else { + $has_settings = TRUE; + } + + if ($has_settings) { + $ping = FALSE; + try { + $solr = solrsearch_get_solr($id); + $ping = @$solr->ping(variable_get('solrsearch_ping_timeout', 4)); + // If there is no $solr object, there is no instance available, so don't continue. + if (!$ping) { + throw new Exception(t('No Solr instance available when checking requirements.')); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + $value = $ping ? $t('Your site has contacted the Apache Solr server.') : $t('Your site was unable to contact the Apache Solr server.'); + $severity = $ping ? REQUIREMENT_OK : REQUIREMENT_ERROR; + $requirements['solrsearch'] = array( + 'title' => $t('Apache Solr'), + 'value' => $value, + 'description' => $t('Default environment url:
%url', array('%url' => $environment['url'])), + 'severity' => $severity, + ); + } + + return $requirements; +} + +/** + * Implements hook_install(). + */ +function solrsearch_install() { + module_load_include('inc', 'solrsearch', 'solrsearch_search.admin'); + /*module_load_include('inc', 'solrsearch', 'solrsearch.index');*/ + // Create one MLT block. + solrsearch_search_mlt_save_block(array('name' => st('More like this'))); + db_insert('solrsearch_environment')->fields(array('env_id' => 'echosearch', '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']); + $env_id = solrsearch_default_environment(); + /*solrsearch_index_set_bundles($env_id, 'node', $bundles);*/ + + drupal_set_message(st('Apache Solr is enabled. Visit the settings page.', array('@settings_link' => url('admin/config/search/solrsearch')))); +} + +/** + * Implements hook_enable(). + */ +function solrsearch_enable() { + // Completely build the index table. + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $env_id = solrsearch_default_environment(); + /*solrsearch_index_mark_for_reindex($env_id);*/ +} + +/** + * Implements hook_schema(). + */ +function solrsearch_schema() { + + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for solrsearch to store Luke data and indexing information.'; + $schema['cache_solrsearch'] = $table; + + $schema['solrsearch_environment'] = array( + 'description' => 'The Solr search environment table.', + // Enable CTools exportables based on this table. + 'export' => array( + // Environment machine name. + 'key' => 'env_id', + // Description of key. + 'key name' => 'Environment machine name', + // Apache Solr doesn't allow disabling environments. + 'can disable' => FALSE, + // Variable name to use in exported code. + 'identifier' => 'environment', + // Thin wrapper for the environment save callback. + 'save callback' => 'solrsearch_ctools_environment_save', + // Thin wrapper for the environment delete callback. + 'delete callback' => 'solrsearch_ctools_environment_delete', + // Includes the environment variables in 'conf' as well as the fields in this table. + 'export callback' => 'solrsearch_ctools_environment_export', + // Use the same hook as the API name below. + 'default hook' => 'solrsearch_environments', + // CTools API implementation. + 'api' => array( + 'owner' => 'solrsearch', + 'api' => 'solrsearch_environments', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + 'fields' => array( + 'env_id' => array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'Human-readable name for the server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'url' => array( + 'description' => 'Full url for the server', + 'type' => 'varchar', + 'length' => 1000, + 'not null' => TRUE, + ), + 'service_class' => array( + 'description' => 'Optional class name to use for connection', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + ), + 'primary key' => array('env_id'), + ); + $schema['solrsearch_environment_variable'] = array( + 'description' => 'Variable values for each Solr search environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'The name of the variable.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value of the variable.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array('env_id', 'name'), + ); + + // Technically the entity system does not require an integer ID. + // However, documentation mentions : + // id: The name of the property that contains the primary id of the + // entity. Every entity object passed to the Field API must have this + // property and its value must be numeric. + + //Predefine an amount of types that get their own table + $types = array( + 'other' => 'solrsearch_index_entities', + 'node' => 'solrsearch_index_entities_node', + ); + foreach ($types as $type => $table) { + $schema[$table] = array( + 'description' => 'Stores a record of when an entity changed to determine if it needs indexing by Solr.', + 'fields' => array( + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'entity_id' => array( + 'description' => 'The primary identifier for an entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to which this entity belongs.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the entity should be in the index.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when an entity was changed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'bundle_changed' => array('bundle', 'changed'), + ), + 'primary key' => array('entity_id'), + ); + if ($type == 'other') { + // Need the entity type also in the pkey for multiple entities in one table. + $schema[$table]['primary key'][] = 'entity_type'; + } + } + + $schema['solrsearch_index_bundles'] = array( + 'description' => 'Records what bundles we should be indexing for a given environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'The name of the environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to index.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + ), + 'primary key' => array('env_id', 'entity_type', 'bundle'), + ); + return $schema; +} + +/** + * Implements hook_uninstall(). + */ +function solrsearch_uninstall() { + // Remove variables. + variable_del('solrsearch_default_environment'); + variable_del('solrsearch_rows'); + variable_del('solrsearch_site_hash'); + variable_del('solrsearch_index_last'); + variable_del('solrsearch_search_mlt_blocks'); + variable_del('solrsearch_cron_limit'); + variable_del('solrsearch_exclude_nodeapi_types'); + variable_del('solrsearch_failure'); + variable_del('solrsearch_index_updated'); + variable_del('solrsearch_read_only'); + variable_del('solrsearch_set_nodeapi_messages'); + variable_del('solrsearch_last_optimize'); + variable_del('solrsearch_update_from_6303'); + // Remove blocks. + db_delete('block')->condition('module', 'solrsearch')->execute(); +} + +/** + * Add a table to track Solr servers. + */ +function solrsearch_update_7000() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + $schema['solrsearch_server'] = array( + 'description' => 'The Solr server table.', + 'fields' => array( + 'server_id' => array( + 'description' => 'Unique identifier for the server', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'Human-readable name for the server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'scheme' => array( + 'description' => 'Preferred scheme for the registered server', + 'type' => 'varchar', + 'length' => 10, + 'not null' => TRUE, + 'default' => 'http' + ), + 'host' => array( + 'description' => 'Host name for the registered server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'port' => array( + 'description' => 'Port number for the registered server', + 'type' => 'int', + 'not null' => TRUE, + ), + 'path' => array( + 'description' => 'Path to the registered server', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'service_class' => array( + 'description' => 'Optional class name to use for connection', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + ), + 'primary key' => array('server_id'), + ); + db_create_table('solrsearch_server', $schema['solrsearch_server']); + // Insert into the table the current single server record. + $host = variable_get('solrsearch_host', 'localhost'); + $port = variable_get('solrsearch_port', '8983'); + $path = variable_get('solrsearch_path', '/solr'); + db_insert('solrsearch_server')->fields(array('server_id' => 'solr', 'name' => 'Apache Solr server', 'host' => $host, 'port' => $port, 'path' => $path))->execute(); + variable_set('solrsearch_default_server', 'solr'); + variable_del('solrsearch_host'); + variable_del('solrsearch_port'); + variable_del('solrsearch_path'); + $value = variable_get('solrsearch_service_class', NULL); + if (is_array($value)) { + list($module, $filepath, $class) = $value; + variable_set('solrsearch_service_class', $class); + } + variable_del('solrsearch_logging'); +} + + +/** + * Re-jigger the schema to use fewer, shorter keys. + */ +function solrsearch_update_7001() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + if (db_field_exists('solrsearch_server', 'asid')) { + // You installed the beta1 and need to be fixed up. + db_drop_field('solrsearch_server', 'asid'); + db_drop_unique_key('solrsearch_server', 'server_id'); + db_add_primary_key('solrsearch_server', array('server_id')); + db_drop_unique_key('solrsearch_server', 'host_post_path'); + db_change_field('solrsearch_server', 'port', 'port', + array( + 'description' => 'Port number for the registered server', + 'type' => 'int', + 'not null' => TRUE, + ) + ); + } +} + +/** + * Create the per-server variable table. + */ +function solrsearch_update_7002() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + $schema['solrsearch_server_variable'] = array( + 'description' => 'Variable values for each Solr server.', + 'fields' => array( + 'server_id' => array( + 'description' => 'Unique identifier for the server', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => 'The name of the variable.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'description' => 'The value of the variable.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array('server_id', 'name'), + ); + db_create_table('solrsearch_server_variable', $schema['solrsearch_server_variable']); + $server_id = variable_get('solrsearch_default_server', 'solr'); + // Variables to be migrated: + $conf['solrsearch_enabled_facets'] = variable_get('solrsearch_enabled_facets', NULL); + $conf['solrsearch_search_query_fields'] = variable_get('solrsearch_search_query_fields', NULL); + $conf['solrsearch_search_type_boosts'] = variable_get('solrsearch_search_type_boosts', NULL); + $conf['solrsearch_search_comment_boost'] = variable_get('solrsearch_search_comment_boost', NULL); + $conf['solrsearch_search_changed_boost'] = variable_get('solrsearch_search_changed_boost', NULL); + $conf['solrsearch_search_sticky_boost'] = variable_get('solrsearch_search_sticky_boost', NULL); + $conf['solrsearch_search_promote_boost'] = variable_get('solrsearch_search_promote_boost', NULL); + $conf['solrsearch_search_excluded_types'] = variable_get('solrsearch_search_excluded_types', NULL); + foreach ($conf as $name => $value) { + if ($value !== NULL) { + db_merge('solrsearch_server_variable') + ->key(array('server_id' => $server_id, 'name' => $name)) + ->fields(array('value' => serialize($value))) + ->execute(); + } + variable_del($name); + } +} + +/** + * Move excluded comment types into a new variable. + */ +function solrsearch_update_7003() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + // Same as solrsearch_update_6006() + $exclude_comment_types = variable_get('solrsearch_exclude_comments_types', NULL); + if (is_array($exclude_comment_types)) { + $exclude = array(); + foreach ($exclude_comment_types as $type) { + $exclude[$type]['comment'] = TRUE; + } + variable_set('solrsearch_exclude_nodeapi_types', $exclude); + } + variable_del('solrsearch_exclude_comments_types'); +} + +/** + * Update solrsearch_failure variable. + */ +function solrsearch_update_7004() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + $failure = variable_get('solrsearch_failure', NULL); + switch ($failure) { + case 'show_error': + variable_set('solrsearch_failure', 'solrsearch:show_error'); + break; + case 'show_drupal_results': + variable_set('solrsearch_failure', 'node'); + break; + case 'show_no_results': + variable_set('solrsearch_failure', 'solrsearch:show_no_results'); + break; + } +} + +/** + * Re-jigger the schema to use just a url column. + */ +function solrsearch_update_7005() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + if (db_field_exists('solrsearch_server', 'port')) { + // You installed the beta3 and need to be fixed up. + $servers = db_query('SELECT * FROM {solrsearch_server}')->fetchAllAssoc('server_id', PDO::FETCH_ASSOC); + db_drop_field('solrsearch_server', 'scheme'); + db_drop_field('solrsearch_server', 'port'); + db_drop_field('solrsearch_server', 'path'); + db_change_field('solrsearch_server', 'host', 'url', + array( + 'description' => 'Full url for the server', + 'type' => 'varchar', + 'length' => 1000, + 'not null' => TRUE, + ) + ); + foreach ($servers as $id => $server) { + $port = $server['port'] ? ':' . $server['port'] : ''; + $url = $server['scheme'] . '://' . $server['host'] . $port . $server['path']; + db_update('solrsearch_server') + ->fields(array('url' => $url)) + ->condition('server_id', $id) + ->execute(); + } + } +} + +/** + * Remove facet-related variable deprecated by the Facet API integration. + */ +function solrsearch_update_7006() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + variable_del('solrsearch_facetstyle'); + variable_del('solrsearch_facet_show_children'); + variable_del('solrsearch_facet_query_limits'); + variable_del('solrsearch_facet_query_limit_default'); +} + +/** + * Rename tables to make them more generic. + */ +function solrsearch_update_7007() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_primary_key('solrsearch_server'); + db_drop_primary_key('solrsearch_server_variable'); + db_rename_table('solrsearch_server', 'solrsearch_environment'); + db_rename_table('solrsearch_server_variable', 'solrsearch_environment_variable'); + db_change_field('solrsearch_environment', 'server_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_change_field('solrsearch_environment_variable', 'server_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_add_primary_key('solrsearch_environment', array('env_id')); + db_add_primary_key('solrsearch_environment_variable', array('env_id', 'name')); + $id = variable_get('solrsearch_default_server', NULL); + if (isset($id)) { + variable_set('solrsearch_default_environment', $id); + } + variable_del('solrsearch_default_server'); +} + +/** + * Remove more facet-related variable deprecated by the Facet API integration. + */ +function solrsearch_update_7008() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + variable_del('solrsearch_facet_missing'); + variable_del('solrsearch_facet_query_initial_limits'); + variable_del('solrsearch_facet_query_sorts'); + variable_del('solrsearch_facet_sort_active'); + variable_del('solrsearch_operator'); +} + +/** + * Update Facet API block deltas to account for removal of numeric ID from field names. + */ +function solrsearch_update_7009() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + // Only run when facetapi is available and/or installed + if (module_exists('facetapi')) { + module_load_include('inc', 'facetapi', 'facetapi.block'); + // Get all searchers + $searchers = facetapi_get_searcher_info(); + $realms = facetapi_get_realm_info(); + foreach ($searchers as $searcher_id => $searcher) { + foreach ($realms as $realm_id => $realm) { + foreach (field_info_fields() as $field_name => $field) { + // Generate the old delta + $facet_name_old = $field['id'] . '_' . $field['field_name']; + $delta_old = facetapi_build_delta($searcher['name'], $realm['name'], $facet_name_old); + $delta_old = substr(drupal_hash_base64($delta_old), 0, 32); + // Generate the new delta + $facet_name = $field['field_name']; + $delta = facetapi_build_delta($searcher['name'], $realm['name'], $facet_name); + $delta = substr(drupal_hash_base64($delta), 0, 32); + db_update('block') + ->fields(array('delta' => $delta)) + ->condition('module', 'facetapi') + ->condition('delta', $delta_old) + ->execute(); + } + } + } + } +} + +/** + * Update cache table schema for Drupal 7. + */ +function solrsearch_update_7010() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_field('cache_solrsearch', 'headers'); + return 'Updated cache table schema for Drupal 7.'; +} + +/** + * Change the namespace for the indexer from solrsearch_search to solrsearch + */ +function solrsearch_update_7011() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + $stored = variable_get('solrsearch_index_last', array()); + if (isset($stored['solrsearch_search'])) { + $stored['solrsearch'] = $stored['solrsearch_search']; + unset($stored['solrsearch_search']); + variable_set('solrsearch_index_last', $stored); + } + return 'Updated the namespace variable for the index process.'; +} + +/** + * Rename some variables and update the database tables + */ +function solrsearch_update_7012() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + // @see: drupal_load() + if (!function_exists('solrsearch_default_environment')) { + include_once dirname(__FILE__) . '/solrsearch.module'; + } + + $env_id = solrsearch_default_environment(); + + // Variable changed from integer to array with environment integers + $stored = variable_get('solrsearch_index_last', array()); + if (isset($stored['solrsearch'])) { + $stored[$env_id]['node']['last_entity_id'] = $stored['solrsearch']['last_nid']; + $stored[$env_id]['node']['last_changed'] = $stored['solrsearch']['last_change']; + unset($stored['solrsearch']); + variable_set('solrsearch_index_last', $stored); + } + $last = variable_get('solrsearch_index_updated', NULL); + if (isset($last)) { + variable_set('solrsearch_index_updated', array($env_id => (int) $last)); + } + + // Change namespace to environment id + $excluded_types = solrsearch_environment_variable_get('solrsearch', 'solrsearch_search_excluded_types', array()); + if (!empty($excluded_types)) { + solrsearch_environment_variable_set($env_id, 'solrsearch_search_excluded_types', $excluded_types); + solrsearch_environment_variable_del('solrsearch', 'solrsearch_search_excluded_types'); + } + + // Install the new schema + //Predefine an amount of types that get their own table + $types = array( + 'other' => 'solrsearch_index_entities', + 'node' => 'solrsearch_index_entities_node', + ); + foreach ($types as $type => $table) { + $schema[$table] = array( + 'description' => 'Stores a record of when an entity changed to determine if it needs indexing by Solr.', + 'fields' => array( + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'entity_id' => array( + 'description' => 'The primary identifier for an entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to which this entity belongs.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + 'status' => array( + 'description' => 'Boolean indicating whether the entity is visible to non-administrators (eg, published for nodes).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'changed' => array( + 'description' => 'The Unix timestamp when an entity was changed.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'changed' => array('bundle', 'status', 'changed'), + ), + 'primary key' => array('entity_id'), + ); + if ($type == 'other') { + // Need the entity type also in the pkey for multiple entities in one table. + $schema[$table]['primary key'][] = 'entity_type'; + } + // Create the table + db_create_table($table, $schema[$table]); + } + + $schema['solrsearch_index_bundles'] = array( + 'description' => 'Records what bundles we should be indexing for a given environment.', + 'fields' => array( + 'env_id' => array( + 'description' => 'The name of the environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'entity_type' => array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'The bundle to index.', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + ), + ), + 'primary key' => array('env_id', 'entity_type', 'bundle'), + ); + db_create_table('solrsearch_index_bundles', $schema['solrsearch_index_bundles']); + + + // Move the data from solrsearch_search_node to solrsearch_index_entities_node + $select = db_select('solrsearch_search_node', 'asn'); + $select->join('node', 'n', 'asn.nid = n.nid'); + $select->addField('n', 'nid', 'entity_id'); + $select->addField('n', 'type', 'bundle'); + $select->addField('asn', 'status', 'status'); + $select->addField('asn', 'changed', 'changed'); + $select->addExpression("'node'", 'entity_type'); + $return_value = db_insert('solrsearch_index_entities_node') + ->fields(array('entity_id', 'bundle', 'status', 'changed', 'entity_type')) + ->from($select) + ->execute(); + // Drop the table solrsearch_search_node + db_drop_table('solrsearch_search_node'); + + $environments = solrsearch_load_all_environments(); + foreach ($environments as $env_id => $environment) { + $excluded_types = solrsearch_environment_variable_get($env_id, 'solrsearch_search_excluded_types', array()); + // Get indexable entity types + $options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if ($entity_type == 'node') { + foreach ($entity_info['bundles'] as $key => $info) { + // See if it was excluded & only of entity node. We will not enable + // other entity types by default + if (empty($excluded_types[$key])) { + $options[$entity_type][$key] = $key; + } + } + } + } + // Set all except the excluded types + // @see solrsearch_index_set_bundles() + foreach ($options as $entity_type => $bundles) { + db_delete('solrsearch_index_bundles') + ->condition('env_id', $env_id) + ->condition('entity_type', $entity_type) + ->execute(); + + if ($bundles) { + $insert = db_insert('solrsearch_index_bundles') + ->fields(array('env_id', 'entity_type', 'bundle')); + + foreach ($bundles as $bundle) { + $insert->values(array( + 'env_id' => $env_id, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + )); + } + $insert->execute(); + } + } + // Remove the excluded types + solrsearch_environment_variable_del($env_id, 'solrsearch_search_excluded_types'); + } +} + +/** + * Make consistent (and reduce) field lengths which cause excess pkey length. + */ +function solrsearch_update_7013() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + db_drop_primary_key('solrsearch_index_entities'); + db_change_field('solrsearch_index_entities', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_add_primary_key('solrsearch_index_entities', array('entity_id', 'entity_type')); + db_change_field('solrsearch_index_entities_node', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_drop_primary_key('solrsearch_index_bundles'); + db_change_field('solrsearch_index_bundles', 'env_id', 'env_id', array( + 'description' => 'Unique identifier for the environment', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE) + ); + db_change_field('solrsearch_index_bundles', 'entity_type', 'entity_type', array( + 'description' => 'The type of entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE) + ); + db_add_primary_key('solrsearch_index_bundles', array('env_id', 'entity_type', 'bundle')); +} + +/** + * Remove status from the key. + */ +function solrsearch_update_7014() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + $types = array( + 'other' => 'solrsearch_index_entities', + 'node' => 'solrsearch_index_entities_node', + ); + foreach ($types as $type => $table) { + db_drop_index($table, 'changed'); + db_add_index($table, 'bundle_changed', array('bundle', 'changed')); + } +} + + +/** + * Fix primary key schema mismatch for those who cleanly installed with beta16. + */ +function solrsearch_update_7015() { + if (variable_get('solrsearch_update_from_6303', FALSE)) { + return NULL; + } + + // Brand new installations since update_7013 have the wrong primary key. + db_drop_primary_key('solrsearch_index_entities'); + db_add_primary_key('solrsearch_index_entities', array('entity_id', 'entity_type')); +} + +/** + * Clean up solrsearch_update_from_6303. + * + * This variable had been used to bypass 7.x-1.x updates which are redundant + * with 6.x-3.x. + */ +function solrsearch_update_7016() { + variable_del('solrsearch_update_from_6303'); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.interface.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.interface.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,544 @@ +abort_search is TRUE. + * + * @param string $keys + * The search keys. + * + * @return + * A stdClass response object. + */ + function search($keys = NULL); + + /** + * Calls a method, without arguments, on the Solr object with which the query + * object was initialized. + * + * @param string $method + * The method to call on the Solr object. + * + * @return + * Any method return. + */ + function solr($method); +} + +/** + * The interface for all 'Service' objects. + */ +interface DrupalApacheSolrServiceInterface { + /** + * Call the /admin/ping servlet, to test the connection to the server. + * + * @param $timeout + * maximum time to wait for ping in seconds, -1 for unlimited (default 2). + * @return + * (float) seconds taken to ping the server, FALSE if timeout occurs. + */ + function ping($timeout = 2); + + /** + * Get information about the Solr Core. + * + * @return + * (string) system info encoded in json + */ + function getSystemInfo(); + + /** + * Get just the field meta-data about the index. + */ + function getFields($num_terms = 0); + + /** + * Get meta-data about the index. + */ + function getLuke($num_terms = 0); + + /** + * Get information about the Solr Core. + * + * Returns a Simple XMl document + */ + function getStats(); + + /** + * Get summary information about the Solr Core. + */ + function getStatsSummary(); + + /** + * Clear cached Solr data. + */ + function clearCache(); + + /** + * Constructor + * + * @param $url + * The URL to the Solr server, possibly including a core name. E.g. http://localhost:8983/solr/ + * or https://search.example.com/solr/core99/ + * @param $env_id + * The machine name of a corresponding saved configuration used for loading + * data like which facets are enabled. + */ + function __construct($url, $env_id = NULL); + + function getId(); + + /** + * Make a request to a servlet (a path) that's not a standard path. + * + * @param string $servlet + * A path to be added to the base Solr path. e.g. 'extract/tika' + * + * @param array $params + * Any request parameters when constructing the URL. + * + * @param array $options + * @see drupal_http_request() $options. + * + * @return + * response object + * + * @thows Exception + */ + function makeServletRequest($servlet, $params = array(), $options = array()); + + /** + * Get the Solr url + * + * @return string + */ + function getUrl(); + + /** + * Set the Solr url. + * + * @param $url + * + * @return $this + */ + function setUrl($url); + + /** + * Raw update Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed xml document. + * + * @param string $rawPost + * @param float $timeout Maximum expected duration (in seconds) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function update($rawPost, $timeout = FALSE); + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of ApacheSolrDocument instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * + * @return response objecte + * + * @throws Exception If an error occurs during the service call + */ + function addDocuments($documents, $overwrite = NULL, $commitWithin = NULL); + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $optimize Defaults to true + * @param boolean $waitFlush Defaults to true + * @param boolean $waitSearcher Defaults to true + * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function commit($optimize = TRUE, $waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600); + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteById($id, $timeout = 3600); + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByMultipleIds($ids, $timeout = 3600); + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return stdClass response object + * + * @throws Exception If an error occurs during the service call + */ + function deleteByQuery($rawQuery, $timeout = 3600); + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * @param boolean $waitSearcher + * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function optimize($waitFlush = TRUE, $waitSearcher = TRUE, $timeout = 3600); + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field) + * + * @return response object + * + * @throws Exception If an error occurs during the service call + */ + function search($query = '', array $params = array(), $method = 'GET'); + + /** + * Get the current solr version. This could be 1, 3 or 4 + * + * @return int + * 1, 3 or 4. Does not give a more details version, for that you need + * to get the system info. + */ + function getSolrVersion(); + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,2715 @@ + 'solrsearch_term_select_field', + 'access arguments' => array('access content'), + 'file' => 'solrsearch_terms.inc', + ); + + $items['solrsearch-terms'] =array( + 'page callback' => 'solrsearch_term_list', + 'access arguments' => array('access content'), + 'file' => 'solrsearch_terms.inc', + ); + $items['solrsearch/node'] = array( + 'title' => 'Search', + 'page callback' => 'solrsearch_view', + 'access arguments' => array('access content'), + //'access callback' => 'solrsearch_is_active', + 'type' => MENU_SUGGESTED_ITEM, + 'file' => 'solrsearch.pages.inc', + ); + $items['solrsearch/site'] = array( + 'title' => 'Search', + 'page callback' => 'solrsearch_view', + 'access arguments' => array('access content'), + //'access callback' => 'solrsearch_is_active', + 'type' => MENU_SUGGESTED_ITEM, + 'file' => 'solrsearch.pages.inc', + ); + $items['solrsearch'] = array( + 'title' => 'Search', + 'page callback' => 'solrsearch_view', + 'access arguments' => array('access content'), + //'access callback' => 'solrsearch_is_active', + 'type' => MENU_SUGGESTED_ITEM, + 'file' => 'solrsearch.pages.inc', + ); + $items['admin/config/search/solrsearch'] = array( + 'title' => 'Solr Search search', + 'description' => 'Administer Solr Search.', + 'page callback' => 'solrsearch_status_page', + 'access arguments' => array('administer search'), + 'weight' => -8, + 'file' => 'solrsearch.admin.inc', + ); + + + $items['admin/config/search/solrsearch/settings'] = array( + 'title' => 'Settings', + 'weight' => 10, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_settings'), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + + $settings_path = 'admin/config/search/solrsearch/settings/'; + + $items[$settings_path . '%solrsearch_environment/edit'] = array( + 'title' => 'Edit', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_environment_edit_form', 5), + 'description' => 'Edit Solr Search search environment.', + 'access arguments' => array('administer search'), + 'weight' => 10, + 'file' => 'solrsearch.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $items[$settings_path . '%solrsearch_environment/clone'] = array( + 'title' => 'Solr Search search environment clone', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_environment_clone_form', 5), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch.admin.inc', + ); + $items[$settings_path . '%solrsearch_environment/delete'] = array( + 'title' => 'Solr Search search environment delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_environment_delete_form', 5), + 'access callback' => 'solrsearch_environment_delete_page_access', + 'access arguments' => array('administer search', 5), + 'file' => 'solrsearch.admin.inc', + ); + $items[$settings_path . 'add'] = array( + 'title' => 'Add search environment', + 'description' => 'Add Solr Search environment.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_environment_edit_form'), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch.admin.inc', + 'type' => MENU_LOCAL_ACTION, + ); + + + $reports_path = 'admin/reports/solrsearch'; + $items[$reports_path] = array( + 'title' => 'Solr Search search index', + 'description' => 'Information about the contents of the index on the server', + 'page callback' => 'solrsearch_index_report', + 'access arguments' => array('access site reports'), + 'file' => 'solrsearch.admin.inc', + ); + $items[$reports_path . '/%solrsearch_environment'] = array( + 'title' => 'Solr Search search index', + 'description' => 'Information about the contents of the index on the server', + 'page callback' => 'solrsearch_index_report', + 'page arguments' => array(3), + 'access arguments' => array('access site reports'), + 'file' => 'solrsearch.admin.inc', + ); + $items[$reports_path . '/%solrsearch_environment/index'] = array( + 'title' => 'Search index', + 'file' => 'solrsearch.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items[$reports_path . '/%solrsearch_environment/conf'] = array( + 'title' => 'Configuration files', + 'page callback' => 'solrsearch_config_files_overview', + 'access arguments' => array('access site reports'), + 'file' => 'solrsearch.admin.inc', + 'weight' => 5, + 'type' => MENU_LOCAL_TASK, + ); + $items[$reports_path . '/%solrsearch_environment/conf/%'] = array( + 'title' => 'Configuration file', + 'page callback' => 'solrsearch_config_file', + 'page arguments' => array(5, 3), + 'access arguments' => array('access site reports'), + 'file' => 'solrsearch.admin.inc', + 'type' => MENU_CALLBACK, + ); + if (module_exists('devel')) { + $items['node/%node/devel/solrsearch'] = array( + 'title' => 'Solr Search', + 'page callback' => 'solrsearch_devel', + 'page arguments' => array(1), + 'access arguments' => array('access devel information'), + 'file' => 'solrsearch.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 . '%solrsearch_environment/facets'] = array( + 'title' => 'Facets', + 'page callback' => 'solrsearch_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 . '%solrsearch_environment/facets/' . $realm_name] = array( + 'title' => $realm['label'], + 'page callback' => 'solrsearch_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 solrsearch_enabled_facets_page($realm_name, $environment = NULL) { + $page = array(); + + if (isset($environment['env_id'])) { + $env_id = $environment['env_id']; + } + else { + $env_id = solrsearch_default_environment(); + } + $searcher = 'solrsearch@' . $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['solrsearch_environment'] = array( + '#theme' => 'solrsearch_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 solrsearch_facetapi_searcher_info() { + $info = array(); + // TODO: is it needed to return all of them here? + foreach (solrsearch_load_all_environments() as $id => $environment) { + $info['solrsearch@' . $id] = array( + 'label' => t('Solr Search environment: @environment', array('@environment' => $environment['name'])), + 'adapter' => 'solrsearch', + 'instance' => $id, + 'path' => '', + 'supports facet mincount' => TRUE, + 'supports facet missing' => TRUE, + 'include default facets' => FALSE, + ); + } + return $info; +} + +/** + * Implements hook_facetapi_adapters(). + */ +function solrsearch_facetapi_adapters() { + return array( + 'solrsearch' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiAdapter', + ), + ), + ); +} + +/** + * Implements hook_facetapi_query_types(). + */ +function solrsearch_facetapi_query_types() { + return array( + 'solrsearch_term' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiTerm', + 'adapter' => 'solrsearch', + ), + ), + 'solrsearch_date' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiDate', + 'adapter' => 'solrsearch', + ), + ), + 'solrsearch_numeric_range' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiNumericRange', + 'adapter' => 'solrsearch', + ), + ), + 'solrsearch_integer' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiInteger', + 'adapter' => 'solrsearch', + ), + ), + 'solrsearch_geo' => array( + 'handler' => array( + 'class' => 'solrsearchFacetapiGeo', + 'adapter' => 'solrsearch', + ), + ), + ); +} + +/** + * Implements hook_facetapi_facet_info(). + * Currently it only supports the node entity type + */ +function solrsearch_facetapi_facet_info($searcher_info) { + $facets = array(); + //dpm("sorlsearch_facetapi"); + if ('solrsearch' == $searcher_info['adapter']) { + $environment = solrsearch_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 = solrsearch_default_node_facet_info(); + } + } + + return $facets; +} + +/** + * Returns an array of facets for node fields and attributes. + * + * @return + * An array of node facets. + */ +function solrsearch_default_node_facet_info() { + //dpm("sorlsearch_default_facetapi"); + return array_merge(solrsearch_common_node_facets(), solrsearch_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 solrsearch_entity_field_facets($entity_type) { + $facets = array(); + //dpm("solrsearch_entity_field_facets"); + foreach (solrsearch_entity_fields($entity_type) as $field_nm => $entity_fields) { + foreach ($entity_fields as $field_info) { + if (!empty($field_info['facets'])) { + $field = solrsearch_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 of type @type.', array('@type' => $field_info['field']['type'])), + '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 solrsearch_common_node_facets() { + + /*$facets['docType'] = array( + 'label' => t('Document type'), + 'description' => t('Filter by document type.'), + 'field' => 'doc-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' => 'author_c', + 'facet mincount allowed' => TRUE, + + ); + + + $facets['title'] = array( + 'label' => t('Title'), + 'description' => t('Filter by title.'), + 'field' => 'title_s', + 'facet mincount allowed' => TRUE, + ); + + + $facets['doc-type'] = array( + 'label' => t('Collection type (online or not)'), + 'description' => t('Filter by Collection Type.'), + 'field' => 'doc-type', + 'facet mincount allowed' => TRUE, + ); + + $facets['access-type'] = array( + 'label' => t('Access restrictions'), + 'description' => t('Filter by Access Type.'), + 'field' => 'access-type', + 'facet mincount allowed' => TRUE, + ); + + + + + $facets['year'] = array( + 'label' => t('Year'), + 'description' => t('Filter by the year.'), + 'field' => 'year', + 'query types' => array('integer'), + 'allowed operators' => array(FACETAPI_OPERATOR_AND => TRUE), + 'min callback' => 'solrsearch_get_min_integer', + 'max callback' => 'solrsearch_get_max_integer', + 'gap callback' => 'solrsearch_get_gap_integer', + 'default sorts' => array( + array('active', SORT_DESC), + array('indexed', SORT_ASC), + ), + ); + + + //dpm($facets); + return $facets; +} + + +/** + * Callback that returns the gap + * + * @param $facet + * An array containing the facet definition. + * + * @return + * + * + * @todo Cache this value. + */ +function solrsearch_get_gap_integer(array $facet) { + //TODO: should be dynamic or configurable + return 20; +} + +/** + * Callback that returns the minimum integer + * + * @param $facet + * An array containing the facet definition. + * + * @return + * + * + * @todo Cache this value. + */ +function solrsearch_get_min_integer(array $facet) { + //TODO: should be dynamic or configurable + return 1; +} + +/** + * Callback that returns the maximum integer + * + * @param $facet + * An array containing the facet definition. + * + * @return + * + * + * @todo Cache this value. + */ +function solrsearch_get_max_integer(array $facet) { + //TODO: should be dynamic or configurable + return 2020; +} + + +/** + * Determines Solr Search'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 solrsearch_failure($search_name, $querystring) { + $fail_rule = variable_get('solrsearch_failure', 'solrsearch:show_error'); + + switch ($fail_rule) { + case 'solrsearch:show_error': + drupal_set_message(t('Search is temporarily unavailable. If the problem persists, please contact the site administrator.'), 'error'); + break; + case 'solrsearch: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 solrsearch_site_hash() { + if (!($hash = variable_get('solrsearch_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('solrsearch_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 solrsearch_document_id($id, $entity_type = 'node') { + return solrsearch_site_hash() . "/{$entity_type}/" . $id; +} + +/** + * Mark one entity as needing re-indexing. + */ +function solrsearch_mark_entity($entity_type, $entity_id) { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $table = solrsearch_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 solrsearch_user_update(&$edit, $account, $category) { + if (isset($account->name) && isset($account->original) && isset($account->original->name) && $account->name != $account->original->name) { + $table = solrsearch_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 solrsearch_taxonomy_term_update($term) { + $table = solrsearch_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(); + } +} + + + +/** + * Convert date from timestamp into ISO 8601 format. + * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html + */ +function solrsearch_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 solrsearch_flatten_documents_array($documents, &$tmp) { + foreach ($documents AS $index => $item) { + if (is_array($item)) { + solrsearch_flatten_documents_array($item, $tmp); + } + elseif (is_object($item)) { + $tmp[] = $item; + } + } +} + +/** + * Implements hook_flush_caches(). + */ +function solrsearch_flush_caches() { + return array('cache_solrsearch'); +} + +/** + * A wrapper for cache_clear_all to be used as a submit handler on forms that + * require clearing Luke cache etc. + */ +function solrsearch_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 = solrsearch_get_solr($env_id); + $solr->clearCache(); + } + catch (Exception $e) { + watchdog('Solr Search', 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 solrsearch_set_stats_message($text, $type = 'status', $repeat = FALSE) { + try { + $solr = solrsearch_get_solr(); + $stats_summary = $solr->getStatsSummary(); + drupal_set_message(check_plain(t($text, $stats_summary)), $type, FALSE); + } + catch (Exception $e) { + watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +/** + * Returns last changed and last ID for an environment and entity type. + */ +function solrsearch_get_last_index_position($env_id, $entity_type) { + $stored = variable_get('solrsearch_index_last', array()); + return isset($stored[$env_id][$entity_type]) ? $stored[$env_id][$entity_type] : array('last_changed' => 0, 'last_entity_id' => 0); +} + +/** + * Sets last changed and last ID for an environment and entity type. + */ +function solrsearch_set_last_index_position($env_id, $entity_type, $last_changed, $last_entity_id) { + $stored = variable_get('solrsearch_index_last', array()); + $stored[$env_id][$entity_type] = array('last_changed' => $last_changed, 'last_entity_id' => $last_entity_id); + variable_set('solrsearch_index_last', $stored); +} + +/** + * Clear a specific environment, or clear all. + */ +function solrsearch_clear_last_index_position($env_id = NULL, $entity_type = NULL) { + $stored = variable_get('solrsearch_index_last', array()); + if (empty($env_id)) { + $stored = array(); + } + elseif ($entity_type) { + unset($stored[$env_id][$entity_type]); + } + else { + unset($stored[$env_id]); + } + variable_set('solrsearch_index_last', $stored); +} + +/** + * Set the timestamp of the last index update + * @param $timestamp + * A timestamp or zero. If zero, the variable is deleted. + */ +function solrsearch_set_last_index_updated($env_id, $timestamp = 0) { + $updated = variable_get('solrsearch_index_updated', array()); + if ($timestamp > 0) { + $updated[$env_id] = $timestamp; + } + else { + unset($updated[$env_id]); + } + variable_set('solrsearch_index_updated', $updated); +} + +/** + * Get the timestamp of the last index update. + * @return integer (timestamp) + */ +function solrsearch_get_last_index_updated($env_id) { + $updated = variable_get('solrsearch_index_updated', array()); + return isset($updated[$env_id]) ? $updated[$env_id] : 0; +} + + + +/** + * Implements hook_form_[form_id]_alter(). + * + * Make sure to flush cache when content types are changed. + */ +function solrsearch_form_node_type_form_alter(&$form, $form_state) { + $form['#submit'][] = 'solrsearch_clear_cache'; +} + +/** + * Implements hook_form_[form_id]_alter(). (D7) + * + * Make sure to flush cache when fields are added. + */ +function solrsearch_form_field_ui_field_overview_form_alter(&$form, $form_state) { + $form['#submit'][] = 'solrsearch_clear_cache'; +} + +/** + * Implements hook_form_[form_id]_alter(). (D7) + * + * Make sure to flush cache when fields are updated. + */ +function solrsearch_form_field_ui_field_edit_form_alter(&$form, $form_state) { + $form['#submit'][] = 'solrsearch_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 solrsearch_set_facetapi_breadcrumb(FacetapiAdapter $adapter, array $realm) { + if ('solrsearch' == $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('Solr Search search environment edit'), 'admin/config/search/solrsearch/settings/' . $env_id); + $breadcrumb[] = l($realm['label'], 'admin/config/search/solrsearch/settings/' . $env_id . '/facets/' . $realm['name']); + drupal_set_breadcrumb($breadcrumb); + } +} + +/** + * Implements hook_form_[form_id]_alter(). (D7) + */ +function solrsearch_form_facetapi_facet_settings_form_alter(&$form, $form_state) { + solrsearch_set_facetapi_breadcrumb($form['#facetapi']['adapter'], $form['#facetapi']['realm']); +} + +/** + * Implements hook_form_[form_id]_alter(). (D7) + */ +function solrsearch_form_facetapi_facet_dependencies_form_alter(&$form, $form_state) { + solrsearch_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 solrsearch_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 solrsearch_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 solrsearch_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('solrsearch_default_environment', 'echosearch'); + } + return $default_env_id; +} + +/** + * Set the default environment and let other modules know about the change. + */ +function solrsearch_set_default_environment($env_id) { + $old_env_id = variable_get('solrsearch_default_environment', 'solr'); + variable_set('solrsearch_default_environment', $env_id); + module_invoke_all('solrsearch_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 = solrsearch_get_solr(); + * } + * catch (Exception $e) { + * watchdog('Solr Search', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + * } + * + * + * @param string $env_id + * + * @return DrupalApacheSolrServiceInterface $solr + * + * @throws Exception + */ +function solrsearch_get_solr($env_id = NULL) { + $solr_cache = &drupal_static(__FUNCTION__); + $environments = solrsearch_load_all_environments(); + + if (!interface_exists('DrupalApacheSolrServiceInterface')) { + require_once(dirname(__FILE__) . '/solrsearch.interface.inc'); + } + + if (empty($env_id)) { + $env_id = solrsearch_default_environment(); + } + elseif (empty($environments[$env_id])) { + throw new Exception(t('Invalid Solr Search 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('solrsearch_service_class', 'DrupalsolrsearchService'); + } + // Takes advantage of auto-loading. + $solr = new $class($environments[$env_id]['url'], $env_id); + $solr_cache[$env_id] = $solr; + } + return $solr_cache[$env_id]; + } + else { + throw new Exception('No default Solr Search environment.'); + } +} + +/** + * Function that loads all the environments + * + * @return $environments + * The environments in the database + */ +function solrsearch_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('solrsearch:environments', 'cache_solrsearch'); + if (isset($cache->data)) { + $environments = $cache->data; + } + elseif (!db_table_exists('solrsearch_index_bundles') || !db_table_exists('solrsearch_environment')) { + // Sometimes this function is called when the 'solrsearch_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('solrsearch_environment', 'all'); + // Convert environments to array. + foreach ($environments as &$environment) { + $environment = (array) $environment; + } + } + else { + $environments = db_query('SELECT * FROM {solrsearch_environment}')->fetchAllAssoc('env_id', PDO::FETCH_ASSOC); + } + + // Load conf and index bundles. We don't use 'subrecords callback' property + // of ctools export API. + solrsearch_environment_load_subrecords($environments); + + cache_set('solrsearch:environments', $environments, 'cache_solrsearch'); + } + + // Allow overrides of environments from settings.php + $conf_environments = variable_get('solrsearch_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 solrsearch_environment_load($env_id) { + $environments = solrsearch_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 solrsearch_environment_delete_page_access($permission, $environment) { + $is_default = $environment['env_id'] == solrsearch_default_environment(); + if ($is_default && !user_access($permission)) { + return FALSE; + } + return TRUE; +} + +/** + * Function that deletes an environment + * + * @param $env_id + * The environment ID it needs to delete. + * + */ +function solrsearch_environment_delete($env_id) { + $environment = solrsearch_environment_load($env_id); + if ($environment) { + db_delete('solrsearch_environment') + ->condition('env_id', $env_id) + ->execute(); + db_delete('solrsearch_environment_variable') + ->condition('env_id', $env_id) + ->execute(); + db_delete('solrsearch_index_bundles') + ->condition('env_id', $env_id) + ->execute(); + + module_invoke_all('solrsearch_environment_delete', $environment); + solrsearch_environments_clear_cache(); + } +} + +/** + * Function that clones an environment + * + * @param $env_id + * The environment ID it needs to clone. + * + */ +function solrsearch_environment_clone($env_id) { + $environment = solrsearch_environment_load($env_id); + $environments = solrsearch_load_all_environments(); + $environment['env_id'] = solrsearch_create_unique_id($environments, $env_id); + $environment['name'] = $environment['name'] . ' [cloned]'; + solrsearch_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 solrsearch_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 solrsearch_environment_save($environment) { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $default = array('env_id' => '', 'name' => '', 'url' => '', 'service_class' => ''); + + $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('solrsearch_environment') + ->key(array('env_id' => $environment['env_id'])) + ->fields($environment) + ->execute(); + // Update the environment variables (if any). + foreach ($conf as $name => $value) { + db_merge('solrsearch_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) { + solrsearch_index_set_bundles($environment['env_id'], $entity_type, $bundles); + } + solrsearch_environments_clear_cache(); +} + +/** + * Clear all caches for environments. + */ +function solrsearch_environments_clear_cache() { + cache_clear_all('solrsearch:environments', 'cache_solrsearch'); + drupal_static_reset('solrsearch_load_all_environments'); + drupal_static_reset('solrsearch_get_solr'); + if (module_exists('ctools')) { + ctools_include('export'); + ctools_export_load_object_reset('solrsearch_environment'); + } +} + +/** + * Get a named variable, or return the default. + * + * @see variable_get() + */ +function solrsearch_environment_variable_get($env_id, $name, $default = NULL) { + $environment = solrsearch_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 solrsearch_environment_variable_set($env_id, $name, $value) { + db_merge('solrsearch_environment_variable') + ->key(array('env_id' => $env_id, 'name' => $name)) + ->fields(array('value' => serialize($value))) + ->execute(); + solrsearch_environments_clear_cache(); +} + +/** + * Get a named variable, or return the default. + * + * @see variable_del() + */ +function solrsearch_environment_variable_del($env_id, $name) { + db_delete('solrsearch_environment_variable') + ->condition('env_id', $env_id) + ->condition('name', $name) + ->execute(); + solrsearch_environments_clear_cache(); +} + +/** + * Checks if a specific Solr Search server is available. + * + * @return boolean TRUE if the server can be pinged, FALSE otherwise. + */ +function solrsearch_server_status($url, $class = NULL) { + + $status = &drupal_static(__FUNCTION__, array()); + + if (!interface_exists('DrupalApacheSolrServiceInterface')) { + require_once(dirname(__FILE__) . '/solrsearch.interface.inc'); + } + + if (empty($class)) { + $class = variable_get('solrsearch_service_class', 'DrupalsolrsearchService'); + } + + $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('solrsearch_ping_timeout', 4)); + } + catch (Exception $e) { + watchdog('Solr Search', 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_solrsearch_query_prepare() and hook_solrsearch_query_alter(). + * + * @param $current_query + * A query object from solrsearch_drupal_query(). It will be modified by + * hook_solrsearch_query_prepare() and then cached in solrsearch_current_query(). + * @param $page + * For paging into results, using $current_query->params['rows'] results per page. + * + * @return array($final_query, $response) + * + * @throws Exception + */ +function solrsearch_do_query(DrupalSolrQueryInterface $current_query) { + + if (!is_object($current_query)) { + throw new Exception(t('NULL query object in function solrsearch_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); + } + } + + foreach (module_implements('solrsearch_query_prepare') as $module) { + $function_name = $module . '_solrsearch_query_prepare'; + $function_name($current_query); + } + + // Cache the original query. Since all the built queries go through + // this process, all the hook_invocations will happen later + $env_id = $current_query->solr('getId'); + + // Add our defType setting here. Normally this would be dismax or the setting + // from the solrconfig.xml. This allows the setting to be overridden. + $defType = solrsearch_environment_variable_get($env_id, 'solrsearch_query_type'); + if (!empty($defType)) { + $current_query->addParam('defType', $defType); + } + + $query = solrsearch_current_query($env_id, $current_query); + + // Verify if this query was already executed in the same page load + if ($response = solrsearch_static_response_cache($searcher)) { + // Return cached query object + + return array($query, $response); + } + + $query->addParam('start', $query->page * $query->getParam('rows')); + + // This hook allows modules to modify the query and params objects. + drupal_alter('solrsearch_query', $query); + + if ($query->abort_search) { + // A module implementing HOOK_solrsearch_query_alter() aborted the search. + return array(NULL, array()); + } + + + $keys = $query->getParam('q'); + + if (strlen($keys) == 0 && ($filters = $query->getFilters())) { + // Move the fq params to q.alt for better performance. Only suitable + // when using dismax or edismax, so we keep this out of the query class itself + // for now. + $qalt = array(); + foreach ($filters as $delta => $filter) { + // Move the fq param if it has no local params and is not negative. + if (!$filter['#exclude'] && !$filter['#local']) { + $qalt[] = '(' . $query->makeFilterQuery($filter) . ')'; + $query->removeFilter($filter['#name'], $filter['#value'], $filter['#exclude']); + } + } + if ($qalt) { + $query->addParam('q.alt', implode(' ', $qalt)); + } + } + // We must run htmlspecialchars() here since converted entities are in the index. + // and thus bare entities &, > or < won't match. Single quotes are converted + // too, but not double quotes since the dismax parser looks at them for + // phrase queries. + $keys = htmlspecialchars($keys, ENT_NOQUOTES, 'UTF-8'); + $keys = str_replace("'", ''', $keys); + + + $response = $query->search($keys); + + + // The response is cached so that it is accessible to the blocks and anything + // else that needs it beyond the initial search. + solrsearch_static_response_cache($searcher, $response); + return array($query, $response); +} + +/** + * It is important to hold on to the Solr response object for the duration of the + * page request so that we can use it for things like building facet blocks. + * + * @param $searcher + * Name of the searcher - e.g. from $query->getSearcher(). + */ +function solrsearch_static_response_cache($searcher, $response = NULL) { + $_response = &drupal_static(__FUNCTION__, array()); + + if (is_object($response)) { + $_response[$searcher] = clone $response; + } + if (!isset($_response[$searcher])) { + $_response[$searcher] = NULL; + } + return $_response[$searcher]; +} + +/** + * Factory function for query objects. + * + * @param string $name + * The search name, used for finding the correct blocks and other config. + * Typically "solrsearch". + * @param array $params + * Array of params , such as 'q', 'fq' to be applied. + * @param string $solrsort + * Visible string telling solr how to sort. + * @param string $base_path + * The search base path (without the keywords) for this query. + * @param DrupalApacheSolrServiceInterface $solr + * An instance of DrupalApacheSolrServiceInterface. + * + * @return DrupalSolrQueryInterface + * DrupalSolrQueryInterface object. + * + * @throws Exception + */ +function solrsearch_drupal_query($name, array $params = array(), $solrsort = '', $base_path = '', DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) { + if (!interface_exists('DrupalSolrQueryInterface')) { + require_once(dirname(__FILE__) . '/solrsearch.interface.inc'); + } + $class_info = variable_get('solrsearch_query_class', array( + 'file' => 'Solr_Base_Query', + 'module' => 'solrsearch', + 'class' => 'SolrBaseQuery')); + $class = $class_info['class']; + if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) { + module_load_include('php', $class_info['module'], $class_info['file']); + } + if (empty($solr)) { + $solr = solrsearch_get_solr(); + } + return new $class($name, $solr, $params, $solrsort, $base_path, $context); +} + +/** + * Factory function for query objects. + * + * @param $operator + * Whether the subquery should be added to another query as OR or AND + * + * @return DrupalSolrQueryInterface|false + * Subquery or error. + * + * @throws Exception + */ +function solrsearch_drupal_subquery($operator = 'OR') { + if (!interface_exists('DrupalSolrQueryInterface')) { + require_once(dirname(__FILE__) . '/solrsearch.interface.inc'); + } + + $class_info = variable_get('solrsearch_subquery_class', array( + 'file' => 'Solr_Base_Query', + 'module' => 'solrsearch', + 'class' => 'SolrFilterSubQuery')); + $class = $class_info['class']; + if (!class_exists($class_info['class']) && isset($class_info['file']) && isset($class_info['module'])) { + module_load_include('php', $class_info['module'], $class_info['file']); + } + $query = new $class($operator); + return $query; +} + +/** + * Static getter/setter for the current query. Only set once per page. + * + * @param $env_id + * Environment from which to save or get the current query + * @param DrupalSolrQueryInterface $query + * $query object to save in the static + * + * @return DrupalSolrQueryInterface|null + * return the $query object if it is available in the drupal_static or null otherwise + */ +function solrsearch_current_query($env_id, DrupalSolrQueryInterface $query = NULL) { + $saved_query = &drupal_static(__FUNCTION__, NULL); + if (is_object($query)) { + $saved_query[$env_id] = clone $query; + } + if (empty($saved_query[$env_id])) { + return NULL; + } + return is_object($saved_query[$env_id]) ? clone $saved_query[$env_id] : NULL; +} + +/** + * + */ + +/** + * Construct a dynamic index name based on information about a field. + * + * @param array $field + * array( + * 'index_type' => 'integer', + * 'multiple' => TRUE, + * 'name' => 'fieldname', + * ), + * @return string + * Fieldname as it appears in the solr index + */ +function solrsearch_index_key($field) { + $index_type = !empty($field['index_type']) ? $field['index_type'] : NULL; + switch ($index_type) { + case 'text': + $type_prefix = 't'; + break; + case 'text-omitNorms': + $type_prefix = 'to'; + break; + case 'text-unstemmed': + $type_prefix = 'tu'; + break; + case 'text-edgeNgram': + $type_prefix = 'te'; + break; + case 'text-whiteSpace': + $type_prefix = 'tw'; + break; + case 'integer': + $type_prefix = 'i'; // long integer + break; + case 'half-int': + $type_prefix = 'h'; // 32 bit integer + break; + case 'float': + $type_prefix = 'f'; // float; sortable. + break; + case 'double': + $type_prefix = 'p'; // double; sortable d was used for date. + break; + case 'boolean': + $type_prefix = 'b'; + break; + case 'tint': + $type_prefix = 'it'; // long integer trie; sortable, best for range queries + break; + case 'thalf-int': + $type_prefix = 'ht'; // 32 bit integer trie (sortable) + break; + case 'tfloat': + $type_prefix = 'ft'; // float trie; sortable, best for range queries. + break; + case 'tdouble': + $type_prefix = 'pt'; // double trie; + break; + case 'sint': + $type_prefix = 'is'; // long integer sortable (deprecated) + break; + case 'half-sint': + $type_prefix = 'hs'; // 32 bit integer long sortable (deprecated) + break; + case 'sfloat': + $type_prefix = 'fs'; // float, sortable (use for sorting missing last) (deprecated). + break; + case 'sdouble': + $type_prefix = 'ps'; // double sortable; (use for sorting missing last) (deprecated). + break; + case 'date': + $type_prefix = 'd'; // date trie (sortable) + break; + case 'date-deprecated': + $type_prefix = 'dd'; // date (regular) + break; + case 'binary': + $type_prefix = 'x'; // Anything that is base64 encoded + break; + case 'storage': + $type_prefix = 'z'; // Anything that just need to be stored, not indexed + break; + case 'point': + $type_prefix = 'point'; // PointType. "52.3672174,4.9126891" + break; + case 'location': + $type_prefix = 'loc'; // LatLonType. "52.3672174,4.9126891" + break; + case 'geohash': + $type_prefix = 'geo'; // GeohashField. "42.6" http://en.wikipedia.org/wiki/Geohash + break; + case 'string': + default: + $type_prefix = 's'; // String + } + $sm = !empty($field['multiple']) ? 'm_' : 's_'; + // Block deltas are limited to 32 chars. + return substr($type_prefix . $sm . $field['name'], 0, 32); +} + +/** + * Try to map a schema field name to a human-readable description. + */ +function solrsearch_field_name_map($field_name) { + $map = &drupal_static(__FUNCTION__); + + if (!isset($map)) { + $map = array( + 'content' => t('The full, rendered content (e.g. the rendered node body)'), + 'ts_comments' => t('The rendered comments associated with a node'), + 'tos_content_extra' => t('Extra rendered content or keywords'), + 'tos_name_formatted' => t('Author name (Formatted)'), + 'label' => t('Title or label'), + 'teaser' => t('Teaser or preview'), + 'tos_name' => t('Author name'), + 'path_alias' => t('Path alias'), + 'taxonomy_names' => t('All taxonomy term names'), + 'tags_h1' => t('Body text inside H1 tags'), + 'tags_h2_h3' => t('Body text inside H2 or H3 tags'), + 'tags_h4_h5_h6' => t('Body text inside H4, H5, or H6 tags'), + 'tags_inline' => t('Body text in inline tags like EM or STRONG'), + 'tags_a' => t('Body text inside links (A tags)'), + 'tid' => t('Taxonomy term IDs'), + 'is_uid' => t('User IDs'), + 'bundle' => t('Content type names eg. article'), + 'entity_type' => t('Entity type names eg. node'), + 'ss_language' => t('Language type eg. en or und (undefinded)'), + ); + if (module_exists('taxonomy')) { + foreach (taxonomy_get_vocabularies() as $vocab) { + $map['tm_vid_' . $vocab->vid . '_names'] = t('Taxonomy term names only from the %name vocabulary', array('%name' => $vocab->name)); + $map['im_vid_' . $vocab->vid] = t('Taxonomy term IDs from the %name vocabulary', array('%name' => $vocab->name)); + } + } + foreach (solrsearch_entity_fields('node') as $field_nm => $nodefields) { + foreach ($nodefields as $field_info) { + $map[solrsearch_index_key($field_info)] = t('Field of type @type: %label', array('@type' => $field_info['field']['type'], '%label' => $field_info['display_name'])); + } + } + drupal_alter('solrsearch_field_name_map', $map); + } + return isset($map[$field_name]) ? $map[$field_name] : $field_name; +} + +/** + * Validation function for the Facet API facet settings form. + * + * Solr Search does not support the combination of OR facets + * and facet missing, so catch that at validation. + */ +function solrsearch_facet_form_validate($form, &$form_state) { + if (($form_state['values']['global']['operator'] == FACETAPI_OPERATOR_OR) && $form_state['values']['global']['facet_missing']) { + form_set_error('operator', t('Solr Search does not support facet missing in combination with the OR operator.')); + } +} + +/** + * Implements hook_entity_info_alter(). + */ +function solrsearch_entity_info_alter(&$entity_info) { + // Load all environments + $environments = solrsearch_load_all_environments(); + + // Set those values that we know. Other modules can do so + // for their own entities if they want. + $default_entity_info = array(); + $default_entity_info['node']['indexable'] = TRUE; + $default_entity_info['node']['status callback'][] = 'solrsearch_index_node_status_callback'; + $default_entity_info['node']['document callback'][] = 'solrsearch_index_node_solr_document'; + $default_entity_info['node']['reindex callback'] = 'solrsearch_index_node_solr_reindex'; + $default_entity_info['node']['bundles changed callback'] = 'solrsearch_index_node_bundles_changed'; + $default_entity_info['node']['index_table'] = 'solrsearch_index_entities_node'; + $default_entity_info['node']['cron_check'] = 'solrsearch_index_node_check_table'; + // solrsearch_search implements a new callback for every entity type + // $default_entity_info['node']['solrsearch']['result callback'] = 'solrsearch_search_node_result'; + //Allow implementations of HOOK_solrsearch_entity_info to modify these default indexers + drupal_alter('solrsearch_entity_info', $default_entity_info); + + // First set defaults so that we don't need to worry about NULL keys. + foreach (array_keys($entity_info) as $type) { + if (!isset($entity_info[$type]['solrsearch'])) { + $entity_info[$type]['solrsearch'] = array(); + } + if (isset($default_entity_info[$type])) { + $entity_info[$type]['solrsearch'] += $default_entity_info[$type]; + } + $default = array( + 'indexable' => FALSE, + 'status callback' => '', + 'document callback' => '', + 'reindex callback' => '', + 'bundles changed callback' => '', + ); + $entity_info[$type]['solrsearch'] += $default; + } + + // For any supported entity type and bundle, flag it for indexing. + foreach ($entity_info as $entity_type => $info) { + if ($info['solrsearch']['indexable']) { + // Loop over each environment and check if any of them have other entity + // bundles of any entity type enabled and set the index value to TRUE + foreach ($environments as $env) { + // Skip if the environment is set to read only + if (empty($env['env_id']['conf']['solrsearch_read_only'])) { + // Get the supported bundles + $supported = solrsearch_get_index_bundles($env['env_id'], $entity_type); + // For each bundle in drupal, compare to the supported solrsearch + // bundles and enable where possible + foreach (array_keys($info['bundles']) as $bundle) { + if (in_array($bundle, $supported)) { + $entity_info[$entity_type]['bundles'][$bundle]['solrsearch']['index'] = TRUE; + } + } + } + } + } + } +} + +/** + * Gets a list of the bundles on the specified entity type that should be indexed. + * + * @param string $core + * The Solr environment for which to index entities. + * @param string $entity_type + * The entity type to index. + * @return array + * The bundles that should be indexed. + */ +function solrsearch_get_index_bundles($env_id, $entity_type) { + $environment = solrsearch_environment_load($env_id); + return !empty($environment['index_bundles'][$entity_type]) ? $environment['index_bundles'][$entity_type] : array(); +} + +/** + * Implements hook_entity_insert(). + */ +function solrsearch_entity_insert($entity, $type) { + // For our purposes there's really no difference between insert and update. + return solrsearch_entity_update($entity, $type); +} + +/** + * Determines if we should index the provided entity. + * + * Whether or not a given entity is indexed is determined on a per-bundle basis. + * Entities/Bundles that have no index flag are presumed to not get indexed. + * + * @param stdClass $entity + * The entity we may or may not want to index. + * @param string $type + * The type of entity. + * @return boolean + * TRUE if this entity should be indexed, FALSE otherwise. + */ +function solrsearch_entity_should_index($entity, $type) { + $info = entity_get_info($type); + list($id, $vid, $bundle) = entity_extract_ids($type, $entity); + + if ($bundle && isset($info['bundles'][$bundle]['solrsearch']['index']) && $info['bundles'][$bundle]['solrsearch']['index']) { + return TRUE; + } + return FALSE; +} + +/** + * Implements hook_entity_update(). + */ +function solrsearch_entity_update($entity, $type) { + // Include the index file for the status callback + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + if (solrsearch_entity_should_index($entity, $type)) { + $info = entity_get_info($type); + list($id, $vid, $bundle) = entity_extract_ids($type, $entity); + + // Check status callback before sending to the index + $status_callbacks = solrsearch_entity_get_callback($type, 'status callback', $bundle); + + $status = TRUE; + if (is_array($status_callbacks)) { + foreach($status_callbacks as $status_callback) { + if (is_callable($status_callback)) { + // by placing $status in front we prevent calling any other callback + // after one status callback returned false + $status = $status && $status_callback($id, $type); + } + } + } + + // Delete the entity from our index if the status callback returns FALSE + if (!$status) { + solrsearch_entity_delete($entity, $type); + return NULL; + } + + $indexer_table = solrsearch_get_indexer_table($type); + + // If we haven't seen this entity before it may not be there, so merge + // instead of update. + db_merge($indexer_table) + ->key(array( + 'entity_type' => $type, + 'entity_id' => $id, + )) + ->fields(array( + 'bundle' => $bundle, + 'status' => 1, + 'changed' => REQUEST_TIME, + )) + ->execute(); + } +} + +/** + * Retrieve the indexer table for an entity type. + */ +function solrsearch_get_indexer_table($type) { + $entity_info = entity_get_info(); + if (isset($entity_info[$type]['solrsearch']['index_table'])) { + $indexer_table = $entity_info[$type]['solrsearch']['index_table']; + } + else { + $indexer_table = 'solrsearch_index_entities'; + } + return $indexer_table; +} + +/** + * Implements hook_entity_delete(). + */ +function solrsearch_entity_delete($entity, $entity_type) { + $env_id = solrsearch_default_environment(); + + // Delete the entity's entry from a fictional table of all entities. + $info = entity_get_info($entity_type); + list($entity_id) = entity_extract_ids($entity_type, $entity); + solrsearch_remove_entity($env_id, $entity_type, $entity_id); +} + +function solrsearch_remove_entity($env_id, $entity_type, $entity_id) { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + + $indexer_table = solrsearch_get_indexer_table($entity_type); + if (solrsearch_index_delete_entity_from_index($env_id, $entity_type, $entity_id)) { + // There was no exception, so delete from the table. + db_delete($indexer_table) + ->condition('entity_type', $entity_type) + ->condition('entity_id', $entity_id) + ->execute(); + } + else { + // Set status 0 so we try to delete from the index again in the future. + db_update($indexer_table) + ->condition('entity_id', $entity_id) + ->fields(array('changed' => REQUEST_TIME, 'status' => 0)) + ->execute(); + } +} + +/** + * Returns array containing information about node fields that should be indexed + */ +function solrsearch_entity_fields($entity_type = 'node') { + $fields = &drupal_static(__FUNCTION__, array()); + + if (!isset($fields[$entity_type])) { + $fields[$entity_type] = array(); + + $mappings = module_invoke_all('solrsearch_field_mappings'); + foreach (array_keys($mappings) as $key) { + // Set all values with defaults. + $defaults = array( + 'dependency plugins' => array('bundle', 'role'), + 'map callback' => FALSE, + 'name callback' => '', + 'hierarchy callback' => FALSE, + 'indexing_callback' => '', + 'index_type' => 'string', + 'facets' => FALSE, + 'facet missing allowed' => FALSE, + 'facet mincount allowed' => FALSE, + // Field API allows any field to be multi-valued. + 'multiple' => FALSE, + ); + if ($key !== 'per-field') { + $mappings[$key] += $defaults; + } + else { + foreach (array_keys($mappings[$key]) as $field_key) { + $mappings[$key][$field_key] += $defaults; + } + } + } + + // Allow other modules to add or alter mappings. + drupal_alter('solrsearch_field_mappings', $mappings, $entity_type); + + $modules = system_get_info('module'); + $instances = field_info_instances($entity_type); + foreach (field_info_fields() as $field_name => $field) { + $row = array(); + if (isset($field['bundles'][$entity_type]) && (isset($mappings['per-field'][$field_name]) || isset($mappings[$field['type']]))) { + // Find the mapping. + if (isset($mappings['per-field'][$field_name])) { + $row = $mappings['per-field'][$field_name]; + } + else { + $row = $mappings[$field['type']]; + } + // The field info array. + $row['field'] = $field; + + // Cardinality: The number of values the field can hold. Legal values + // are any positive integer or FIELD_CARDINALITY_UNLIMITED. + if ($row['field']['cardinality'] != 1) { + $row['multiple'] = TRUE; + } + + // @todo: for fields like taxonomy we are indexing multiple Solr fields + // per entity field, but are keying on a single Solr field name here. + $function = !empty($row['name callback']) ? $row['name callback'] : NULL; + if ($function && is_callable($function)) { + $row['name'] = $function($field); + } + else { + $row['name'] = $field['field_name']; + } + $row['module_name'] = $modules[$field['module']]['name']; + // Set display name + $display_name = array(); + foreach ($field['bundles'][$entity_type] as $bundle) { + if (empty($instances[$bundle][$field_name]['display']['search_index']) || $instances[$bundle][$field_name]['display']['search_index'] != 'hidden') { + $row['display_name'] = $instances[$bundle][$field_name]['label']; + $row['bundles'][] = $bundle; + } + } + // Only add to the $fields array if some instances are displayed for the search index. + if (!empty($row['bundles'])) { + // Use the Solr index key as the array key. + $fields[$entity_type][solrsearch_index_key($row)][] = $row; + } + } + } + } + return $fields[$entity_type]; +} + +/** + * Implements hook_solrsearch_index_document_build(). + */ +function field_solrsearch_index_document_build(solrsearchDocument $document, $entity, $entity_type) { + $info = entity_get_info($entity_type); + if ($info['fieldable']) { + // Handle fields including taxonomy. + $indexed_fields = solrsearch_entity_fields($entity_type); + foreach ($indexed_fields as $index_key => $nodefields) { + foreach ($nodefields as $field_info) { + $field_name = $field_info['field']['field_name']; + // See if the node has fields that can be indexed + if (isset($entity->{$field_name})) { + // Got a field. + $function = $field_info['indexing_callback']; + if ($function && function_exists($function)) { + // NOTE: This function should always return an array. One + // entity field may be indexed to multiple Solr fields. + $fields = $function($entity, $field_name, $index_key, $field_info); + foreach ($fields as $field) { + // It's fine to use this method also for single value fields. + $document->setMultiValue($field['key'], $field['value']); + } + } + } + } + } + } +} + +/** + * Implements hook_solrsearch_index_document_build_node(). + * + * Adds book module support + */ +function solrsearch_solrsearch_index_document_build_node(solrsearchDocument $document, $entity, $env_id) { + // Index book module data. + if (!empty($entity->book['bid'])) { + // Hard-coded - must change if solrsearch_index_key() changes. + $document->is_book_bid = (int) $entity->book['bid']; + } +} + +/** + * Strip html tags and also control characters that cause Jetty/Solr to fail. + */ +function solrsearch_clean_text($text) { + // Remove invisible content. + $text = preg_replace('@<(applet|audio|canvas|command|embed|iframe|map|menu|noembed|noframes|noscript|script|style|svg|video)[^>]*>.*@siU', ' ', $text); + // Add spaces before stripping tags to avoid running words together. + $text = filter_xss(str_replace(array('<', '>'), array(' <', '> '), $text), array()); + // Decode entities and then make safe any < or > characters. + $text = htmlspecialchars(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); + // Remove extra spaces. + $text = preg_replace('/\s+/s', ' ', $text); + // Remove white spaces around punctuation marks probably added + // by the safety operations above. This is not a world wide perfect solution, + // but a rough attempt for at least US and Western Europe. + // Pc: Connector punctuation + // Pd: Dash punctuation + // Pe: Close punctuation + // Pf: Final punctuation + // Pi: Initial punctuation + // Po: Other punctuation, including ¿?¡!,.:; + // Ps: Open punctuation + $text = preg_replace('/\s(\p{Pc}|\p{Pd}|\p{Pe}|\p{Pf}|!|\?|,|\.|:|;)/s', '$1', $text); + $text = preg_replace('/(\p{Ps}|¿|¡)\s/s', '$1', $text); + return $text; +} + +/** + * Use the list.module's list_allowed_values() to format the + * field based on its value ($facet). + * + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + */ +function solrsearch_fields_list_facet_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = list_allowed_values($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + * @see http://drupal.org/node/1059372 + */ +function solrsearch_nodereference_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = node_reference_potential_references($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * @param $facet string + * The indexed value + * @param $options + * An array of options including the hook_block $delta. + * @see http://drupal.org/node/1059372 + */ +function solrsearch_userreference_map_callback($facets, $options) { + $map = array(); + $allowed_values = array(); + // @see list_field_formatter_view() + $fields = field_info_fields(); + $field_name = $options['field']['field_name']; + if (isset($fields[$field_name])) { + $allowed_values = user_reference_potential_references($fields[$field_name]); + } + foreach ($facets as $key) { + if (isset($allowed_values[$key])) { + $map[$key]['#markup'] = field_filter_xss($allowed_values[$key]['title']); + } + elseif ($key == '_empty_' && $options['facet missing allowed']) { + // Facet missing. + $map[$key]['#markup'] = theme('facetapi_facet_missing', array('field_name' => $options['display_name'])); + } + else { + $map[$key]['#markup'] = field_filter_xss($key); + } + // The value has already been filtered. + $map[$key]['#html'] = TRUE; + } + return $map; +} + +/** + * Mapping callback for entity references. + */ +function solrsearch_entityreference_facet_map_callback(array $values, array $options) { + $map = array(); + // Gathers entity ids so we can load multiple entities at a time. + $entity_ids = array(); + foreach ($values as $value) { + list($entity_type, $id) = explode(':', $value); + $entity_ids[$entity_type][] = $id; + } + // Loads and maps entities. + foreach ($entity_ids as $entity_type => $ids) { + $entities = entity_load($entity_type, $ids); + foreach ($entities as $id => $entity) { + $key = $entity_type . ':' . $id; + $map[$key] = entity_label($entity_type, $entity); + } + } + return $map; +} + +/** + * Returns the callback function appropriate for a given entity type/bundle. + * + * @param string $entity_type + * The entity type for which we want to know the approprite callback. + * @param string $callback + * The callback for which we want the appropriate function. + * @param string $bundle + * If specified, the bundle of the entity in question. Some callbacks may + * be overridden on a bundle-level. Not specified only the entity-level + * callback will be checked. + * @return string + * The function name for this callback, or NULL if not specified. + */ +function solrsearch_entity_get_callback($entity_type, $callback, $bundle = NULL) { + $info = entity_get_info($entity_type); + + // A bundle-specific callback takes precedence over the generic one for the + // entity type. + if ($bundle && isset($info['bundles'][$bundle]['solrsearch'][$callback])) { + $callback_function = $info['bundles'][$bundle]['solrsearch'][$callback]; + } + elseif (isset($info['solrsearch'][$callback])) { + $callback_function = $info['solrsearch'][$callback]; + } + else { + $callback_function = NULL; + } + return $callback_function; +} + + +/** + * Function to retrieve all the nodes to index. + * Deprecated but kept for backwards compatibility + * @param String $namespace + * @param type $limit + */ +function solrsearch_get_nodes_to_index($namespace, $limit) { + $env_id = solrsearch_default_environment(); + // Hardcode node as an entity type + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + solrsearch_index_get_entities_to_index($env_id, 'node', $limit); +} + + +/** + * Implements hook_theme(). + */ +function solrsearch_theme() { + return array( + 'solrsearch_result' => array( + 'variables' => array('result' => NULL, 'module' => NULL), + 'file' => 'solrsearch.pages.inc', + 'template' => 'solr-search-result', + ), + 'solrsearch_results' => array( + 'variables' => array('results' => NULL, 'module' => NULL), + 'file' => 'solrsearch.pages.inc', + 'template' => 'solr-search-results', + ), + 'solrsearch_term_list_author' => array( + 'variables' => array('authors' => null), + 'file' => 'solrsearch_terms.inc', + 'template' => 'solrsearch-term-list-author', + ), + + 'solrsearch_term_list_title' => array( + 'variables' => array('authors' => null), + 'file' => 'solrsearch_terms.inc', + 'template' => 'solrsearch-term-list-title', + ), + + 'solrsearch_term_selection_form' => array( + 'variables' => array('authors' => null,'cnt' => null, 'letter' => null), + 'file' => 'solrsearch_terms.inc', + 'template' => 'solrsearch-term-selection-form', + ), + + + /** + * Returns a list of links generated by solrsearch_sort_link + */ + 'solrsearch_sort_list' => array( + 'variables' => array('items' => NULL), + ), + /** + * Returns a link which can be used to search the results. + */ + 'solrsearch_sort_link' => array( + 'variables' => array('text' => NULL, 'path' => NULL, 'options' => NULL, 'active' => FALSE, 'direction' => ''), + ), + /** + * Themes the title links in admin settings pages. + */ + 'solrsearch_settings_title' => array( + 'variables' => array('env_id' => NULL), + ), + ); +} + +/** + * Implements hook_hook_info(). + */ +function solrsearch_hook_info() { + $hooks = array( + 'solrsearch_field_mappings' => array( + 'group' => 'solrsearch', + ), + 'solrsearch_field_mappings_alter' => array( + 'group' => 'solrsearch', + ), + 'solrsearch_query_prepare' => array( + 'group' => 'solrsearch', + ), + 'solrsearch_query_alter' => array( + 'group' => 'solrsearch', + ), + 'solrsearch_search_result_alter' => array( + 'group' => 'solrsearch', + ), + 'solrsearch_environment_delete' => array( + 'group' => 'solrsearch', + ) + ); + $hooks['solrsearch_index_document_build'] = array( + 'group' => 'solrsearch', + ); + return $hooks; +} + +/** + * Implements hook_solrsearch_field_mappings(). + */ +function field_solrsearch_field_mappings() { + $mappings = array( + 'list_integer' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'map callback' => 'solrsearch_fields_list_facet_map_callback', + 'index_type' => 'integer', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet missing allowed' => TRUE, + ), + 'list_float' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'map callback' => 'solrsearch_fields_list_facet_map_callback', + 'index_type' => 'float', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet missing allowed' => TRUE, + ), + 'list_text' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'map callback' => 'solrsearch_fields_list_facet_map_callback', + 'index_type' => 'string', + 'facets' => TRUE, + 'facet missing allowed' => TRUE, + ), + 'list_boolean' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'map callback' => 'solrsearch_fields_list_facet_map_callback', + 'index_type' => 'boolean', + 'facets' => TRUE, + 'facet missing allowed' => TRUE, + ), + 'number_integer' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'index_type' => 'tint', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'number_decimal' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'index_type' => 'tfloat', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'number_float' => array( + 'indexing_callback' => 'solrsearch_fields_default_indexing_callback', + 'index_type' => 'tfloat', + 'facets' => TRUE, + 'query types' => array('term', 'numeric_range'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + 'taxonomy_term_reference' => array( + 'map callback' => 'facetapi_map_taxonomy_terms', + 'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy', + 'indexing_callback' => 'solrsearch_term_reference_indexing_callback', + 'index_type' => 'integer', + 'facet_block_callback' => 'solrsearch_search_taxonomy_facet_block', + 'facets' => TRUE, + 'query types' => array('term'), + 'query type' => 'term', + 'facet mincount allowed' => TRUE, + ), + ); + + return $mappings; +} + +/** + * Implements hook_solrsearch_field_mappings() on behalf of date module. + */ +function date_solrsearch_field_mappings() { + $mappings = array(); + $default = array( + 'indexing_callback' => 'solrsearch_date_default_indexing_callback', + 'index_type' => 'date', + 'facets' => TRUE, + 'query types' => array('date'), + 'query type' => 'date', + 'min callback' => 'solrsearch_get_min_date', + 'max callback' => 'solrsearch_get_max_date', + 'map callback' => 'facetapi_map_date', + ); + + // DATE and DATETIME fields can use the same indexing callback. + $mappings['date'] = $default; + $mappings['datetime'] = $default; + + // DATESTAMP fields need a different callback. + $mappings['datestamp'] = $default; + $mappings['datestamp']['indexing_callback'] = 'solrsearch_datestamp_default_indexing_callback'; + + return $mappings; +} + + +/** + * Callback that returns the minimum date of the facet's datefield. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The minimum time in the node table. + * + * @todo Cache this value. + */ +function solrsearch_get_min_date(array $facet) { + // FieldAPI date fields. + $table = 'field_data_' . $facet['field api name']; + $column = $facet['field api name'] . '_value'; + $query = db_select($table, 't'); + $query->addExpression('MIN(' . $column . ')', 'min'); + $query_min = $query->execute()->fetch()->min; + // Update to unix timestamp if this is an ISO or other format. + if (!is_int($query_min)) { + $return = strtotime($query_min); + if ($return === FALSE) { + // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00'). + // Return default start date of 1 as the date query type getDateRange() + // function expects a non-0 integer. + $return = 1; + } + } + return $return; +} + +/** + * Callback that returns the maximum value of the facet's date field. + * + * @param $facet + * An array containing the facet definition. + * + * @return + * The maximum time of the field. + * + * @todo Cache this value. + */ +function solrsearch_get_max_date(array $facet) { + + // FieldAPI date fields. + $table = 'field_data_' . $facet['field api name']; + $column = $facet['field api name'] . '_value'; + $query = db_select($table, 't'); + $query->addExpression('MAX(' . $column . ')', 'max'); + $query_max = $query->execute()->fetch()->max; + // Update to unix timestamp if this is an ISO or other format. + if (!is_int($query_max)) { + $return = strtotime($query_max); + if ($return === FALSE) { + // Not a string that strtotime accepts (ex. '0000-00-00T00:00:00'). + // Return default end date of 1 year from now. + $return = time() + (52 * 7 * 24 * 60 * 60); + } + } + return $return; +} + +/** + * Implements hook_solrsearch_field_mappings() on behalf of References (node_reference). + * @see http://drupal.org/node/1059372 + */ +function node_reference_solrsearch_field_mappings() { + $mappings = array( + 'node_reference' => array( + 'indexing_callback' => 'solrsearch_nodereference_indexing_callback', + 'index_type' => 'integer', + 'map callback' => 'solrsearch_nodereference_map_callback', + 'facets' => TRUE, + ) + ); + + return $mappings; +} + +/** + * Implements hook_solrsearch_field_mappings() on behalf of References (user_reference). + * @see http://drupal.org/node/1059372 + */ +function user_reference_solrsearch_field_mappings() { + $mappings = array( + 'user_reference' => array( + 'indexing_callback' => 'solrsearch_userreference_indexing_callback', + 'index_type' => 'integer', + 'map callback' => 'solrsearch_userreference_map_callback', + 'facets' => TRUE, + ), + ); + + return $mappings; +} +/** + * Implements hook_solrsearch_field_mappings() on behalf of EntityReferences (entityreference) + * @see http://drupal.org/node/1572722 + */ +function entityreference_solrsearch_field_mappings() { + $mappings = array( + 'entityreference' => array( + 'indexing_callback' => 'solrsearch_entityreference_indexing_callback', + 'map callback' => 'solrsearch_entityreference_facet_map_callback', + 'index_type' => 'string', + 'facets' => TRUE, + 'query types' => array('term'), + 'facet missing allowed' => TRUE, + ), + ); + + return $mappings; +} + +/** + * A replacement for l() + * - doesn't add the 'active' class + * - retains all $_GET parameters that solrsearch may not be aware of + * - if set, $options['query'] MUST be an array + * + * @see http://api.drupal.org/api/function/l/6 + * for parameters and options. + * + * @return + * an HTML string containing a link to the given path. + */ +function solrsearch_l($text, $path, $options = array()) { + // Merge in defaults. + $options += array( + 'attributes' => array(), + 'html' => FALSE, + 'query' => array(), + ); + + // Don't need this, and just to be safe. + unset($options['attributes']['title']); + + // Retain GET parameters that Solr Search knows nothing about. + $get = array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1), $options['query']); + $options['query'] += $get; + + return '' . ($options['html'] ? $text : check_plain(html_entity_decode($text))) . ''; +} + +function theme_solrsearch_sort_link($vars) { + $icon = ''; + if ($vars['direction']) { + $icon = ' ' . theme('tablesort_indicator', array('style' => $vars['direction'])); + } + if ($vars['active']) { + if (isset($vars['options']['attributes']['class'])) { + $vars['options']['attributes']['class'] .= ' active'; + } + else { + $vars['options']['attributes']['class'] = 'active'; + } + } + return $icon . solrsearch_l($vars['text'], $vars['path'], $vars['options']); +} + +function theme_solrsearch_sort_list($vars) { + // theme('item_list') expects a numerically indexed array. + $vars['items'] = array_values($vars['items']); + return theme('item_list', array('items' => $vars['items'])); +} + +/** + * Themes the title for settings pages. + */ +function theme_solrsearch_settings_title($vars) { + $output = ''; + + // Gets environment information, builds header with nested link to the environment's + // edit page. Skips building title if environment info could not be retrieved. + if ($environment = solrsearch_environment_load($vars['env_id'])) { + $url = url( + 'admin/config/search/solrsearch/settings/', + array('query' => array('destination' => current_path())) + ); + $output .= '

'; + $output .= t( + 'Settings for: @environment (Overview)', + array('@url' => $url, '@environment' => $environment['name']) + ); + $output .= "

\n"; + } + + return $output; +} + +/** + * Export callback to load the view subrecords, which are the index bundles. + */ +function solrsearch_environment_load_subrecords(&$environments) { + if (empty($environments)) { + // Nothing to do. + return NULL; + } + + $all_index_bundles = db_select('solrsearch_index_bundles', 'ib') + ->fields('ib', array('env_id', 'entity_type', 'bundle')) + ->condition('env_id', array_keys($environments), 'IN') + ->orderBy('env_id') + ->orderBy('entity_type') + ->orderBy('bundle') + ->execute() + ->fetchAll(PDO::FETCH_ASSOC); + + $all_index_bundles_keyed = array(); + foreach ($all_index_bundles as $env_info) { + extract($env_info); + $all_index_bundles_keyed[$env_id][$entity_type][] = $bundle; + } + + $all_variables = db_select('solrsearch_environment_variable', 'v') + ->fields('v', array('env_id', 'name', 'value')) + ->condition('env_id', array_keys($environments), 'IN') + ->orderBy('env_id') + ->orderBy('name') + ->orderBy('value') + ->execute() + ->fetchAll(PDO::FETCH_ASSOC); + + $variables = array(); + foreach ($all_variables as $variable) { + extract($variable); + $variables[$env_id][$name] = unserialize($value); + } + + foreach ($environments as $env_id => &$environment) { + $index_bundles = !empty($all_index_bundles_keyed[$env_id]) ? $all_index_bundles_keyed[$env_id] : array(); + $conf = !empty($variables[$env_id]) ? $variables[$env_id] : array(); + if (is_array($environment)) { + // Environment is an array. + // If we have different values in the database compared with what we + // have in the given environment argument we allow the admin to revert + // the db values so we can stick with a consistent system + if (!empty($environment['index_bundles']) && !empty($index_bundles) && $environment['index_bundles'] !== $index_bundles) { + unset($environment['in_code_only']); + $environment['type'] = 'Overridden'; + } + if (!empty($environment['conf']) && !empty($conf) && $environment['conf'] !== $conf) { + unset($environment['in_code_only']); + $environment['type'] = 'Overridden'; + } + $environment['index_bundles'] = (empty($environment['index_bundles']) || !empty($index_bundles)) ? $index_bundles : $environment['index_bundles']; + $environment['conf'] = (empty($environment['conf']) || !empty($conf)) ? $conf : $environment['conf']; + } + elseif (is_object($environment)) { + // Environment is an object. + if ($environment->index_bundles !== $index_bundles && !empty($index_bundles)) { + unset($environment->in_code_only); + $environment->type = 'Overridden'; + } + if ($environment->conf !== $conf && !empty($conf)) { + unset($environment->in_code_only); + $environment->type = 'Overridden'; + } + $environment->index_bundles = (empty($environment->index_bundles) || !empty($index_bundles)) ? $index_bundles : $environment->index_bundles; + $environment->conf = (empty($environment->conf) || !empty($conf)) ? $conf : $environment->conf; + } + } +} + +/** + * Callback for saving Solr Search environment CTools exportables. + * + * CTools uses objects, while Solr Search uses arrays; turn CTools value into an + * array, then call the normal save function. + * + * @param stdclass $environment + * An environment object. + */ +function solrsearch_ctools_environment_save($environment) { + solrsearch_environment_save((array) $environment); +} + +/** + * Callback for reverting Solr Search environment CTools exportables. + * + * @param mixed $env_id + * An environment machine name. CTools may provide an id OR a complete + * environment object; Since Solr Search loads environments as arrays, this + * may also be an environment array. + */ +function solrsearch_ctools_environment_delete($env_id) { + if (is_object($env_id) || is_array($env_id)) { + $env_id = (object) $env_id; + $env_id = $env_id->env_id; + } + solrsearch_environment_delete($env_id); +} + +/** + * Callback for exporting Solr Search environments as CTools exportables. + * + * @param array $environment + * An environment array from Solr Search. + * @param string $indent + * White space for indentation from CTools. + */ +function solrsearch_ctools_environment_export($environment, $indent) { + ctools_include('export'); + $environment = (object) $environment; + // Re-load the enviroment, since in some cases the conf + // is stripped since it's not in the actual schema. + $environment = (object) solrsearch_environment_load($environment->env_id); + + $index_bundles = array(); + foreach (entity_get_info() as $type => $info) { + if ($bundles = solrsearch_get_index_bundles($environment->env_id, $type)) { + $index_bundles[$type] = $bundles; + } + } + $additions_top = array(); + $additions_bottom = array('conf' => $environment->conf, 'index_bundles' => $index_bundles); + return ctools_export_object('solrsearch_environment', $environment, $indent, NULL, $additions_top, $additions_bottom); +} + + + + +/** +* Performs a search by calling hook_search_execute(). +* +* @param $keys +* Keyword query to search on. +* @param $module +* Search module to search. +* @param $conditions +* Optional array of additional search conditions. +* +* @return +* Renderable array of search results. No return value if $keys are not +* supplied or if the given search module is not active. +*/ +function solrsearch_data($keys, $module, $conditions = NULL) { + if (module_hook($module, 'search_execute')) { + + $results = module_invoke($module, 'search_execute', $keys, $conditions); + + #$params = $query ->getParams(); + #$description = t('Showing items @start through @end of @total.', array( + # '@start' => $params['start'] + 1, + # '@end' => $params['start'] + $params['rows'] - 1, + # '@total' => $total, + #)); + + if (module_hook($module, 'search_page')) { + return module_invoke($module, 'search_page', $results); + } + else { + return array( + '#theme' => 'solrsearch_results', + '#results' => $results, + '#module' => $module, + ); + } + } +} + +function solrsearch_callback_user_values(array $facet){ + #ToDO is this used? +} + + +function solrsearch_permission() { + return array( + 'view restricted content' => array( + 'title' => t('View restricted content'), + 'description' => t('Allow users to view content which is restricted.'), + ), + ); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch.pages.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch.pages.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,269 @@ + ''); + // Process the search form. Note that if there is $_POST data, + // search_form_submit() will cause a redirect to search/[module path]/[keys], + // which will get us back to this page callback. In other words, the search + // form submits with POST but redirects to GET. This way we can keep + // the search query URL clean as a whistle. + + if (empty($_POST['form_id']) || $_POST['form_id'] != 'solrsearch_form') { + + $conditions = NULL; + + $conditions = solrsearch_search_conditions($keys); + + // Only search if there are keywords or non-empty conditions. + if ($keys || !empty($conditions)) { + + // Log the search keys. + //watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys)); + + // Collect the search results. + $results = solrsearch_data($keys, 'solrsearch_search', $conditions); + + } + } + // The form may be altered based on whether the search was run. + + $wrapper = array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('tool','box'), + ), + ); + + + + $wrapper['form'] = drupal_get_form('solrsearch_form', NULL, $keys); + + $build['search_form'] = $wrapper; + + + $build['search_results'] = $results; + + + return $build; +} + +/** + * Process variables for search-results.tpl.php. + * + * The $variables array contains the following arguments: + * - $results: Search results array. + * - $module: Module the search results came from (module implementing + * hook_search_info()). + * + * @see search-results.tpl.php + */ +function template_preprocess_solrsearch_results(&$variables) { + $variables['search_results'] = ''; + if (!empty($variables['module'])) { + $variables['module'] = check_plain($variables['module']); + } + + + + + if (user_access("manage private collections")){ + $variables['digitalobjects_items_select']=base_path()."digitalcollections/manageCurrent"; + $variables['search_results'] .=''; + $variables['search_results'] .=''; + } + + foreach ($variables['results'] as $result) { + + + /* add checkboxes for the item */ + + $variables['search_results'] .= ''; + + if (user_access("manage private collections")){ + $variables['search_results'] .= ''; + } + + /* add tools for the item */ + $variables['search_results'] .= theme('digitalobjects_item_tools', array('objid' => $result['mpiwg-dri'])) . ""; + + + $variables['search_results'] .= theme('solrsearch_result', array('result' => $result, 'module' => $variables['module'])); + + } + $variables['pager'] = theme('pager', array('tags' => array('<<','<','...','>','>>'),'quantity' => 5)); + + $variables['description'] = ''; + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module']; +} + + +function check_array_plain($result,$field){ + if (!isset($result[$field])){ + return null; + } + + $string = $result[$field]; + + if (is_array($string)){ + return check_plain(implode($string)); + } else { + return check_plain($string); + } +} +/** + * Process variables for search-result.tpl.php. + * + * The $variables array contains the following arguments: + * - $result + * - $module + * + * @see search-result.tpl.php + */ +function template_preprocess_solrsearch_result(&$variables) { + global $language; + + $result = $variables['result']; + + $variables['url'] = create_url_from_dri($result['mpiwg-dri'],$result['access-type']); + $variables['access_type'] = $result['access-type']!="free" ? "restricted" :$result['access-type']; + $variables['title'] = check_plain($result['title']); + $variables['author'] = check_plain($result['author']); + $variables['date'] = check_array_plain($result,'date'); + $variables['signature'] = check_array_plain($result,'signature'); + $variables['author'] = check_plain($result['author']); + $variables['year'] = is_array($result['year']) ? implode($result['year']) :$result['year']; + $variables['thumburl'] = create_thumburl_from_dri($result['mpiwg-dri']); + // Check for existence. User search does not include snippets. + $variables['snippet'] = isset($result['snippet']) ? $result['snippet'] : ''; + + $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module']; +} + +/** + * As the search form collates keys from other modules hooked in via + * hook_form_alter, the validation takes place in _submit. + * search_form_validate() is used solely to set the 'processed_keys' form + * value for the basic search form. + */ +function solrsearch_form_validate($form, &$form_state) { + form_set_value($form['basic']['processed_keys'], trim($form_state['values']['keys']), $form_state); +} + +/** + * Process a search form submission. + */ +function solrsearch_form_submit($form, &$form_state) { + + + $keys = $form_state['values']['processed_keys']; + if ($keys == '') { + form_set_error('keys', t('Please enter some keywords.')); + // Fall through to the form redirect. + } + + $exact = $form_state['values']['exact']; + if ($exact==0) { #nicht genaue suche dann trunkiere das wort + $keys .="*"; + } + $form_state['redirect'] = $form_state['action'] . '/' . $keys; +} + + +/** + * Builds a search form. + * + * @param $action + * Form action. Defaults to "search/$path", where $path is the search path + * associated with the module in its hook_search_info(). This will be + * run through url(). + * @param $keys + * The search string entered by the user, containing keywords for the search. + * @param $module + * The search module to render the form for: a module that implements + * hook_search_info(). If not supplied, the default search module is used. + * @param $prompt + * Label for the keywords field. Defaults to t('Enter your keywords') if NULL. + * Supply '' to omit. + * + * @return + * A Form API array for the search form. + */ +function solrsearch_form($form, &$form_state, $action = '', $keys = '', $module = NULL, $prompt = NULL) { + $module_info = FALSE; + + if (!$action) { + $action = 'solrsearch'; + } + if (!isset($prompt)) { + $prompt = t('Enter your keywords'); + + } + + if ($keys == ''){ + $keys=$prompt; + } + + $form['#action'] = url($action); + + // Record the $action for later use in redirecting. + $form_state['action'] = $action; + $form['#attributes']['class'][] = 'search-form'; + $form['module'] = array('#type' => 'value', '#value' => $module); + $form['basic'] = array('#type' => 'container', '#attributes' => array('class' => array('container-inline','searchbox'))); + + $form['basic']['keys'] = array( + '#type' => 'textfield', + '#size' => $prompt ? 40 : 20, + '#attributes' =>array('placeholder' => t($keys),'class' => array('text')), + '#maxlength' => 255, + ); + // processed_keys is used to coordinate keyword passing between other forms + // that hook into the basic search form. + $form['basic']['processed_keys'] = array('#type' => 'value', '#value' => ''); + + + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => t('>'), + '#attributes' =>array('class' => array('submit')), + + ); + + $form['basic']['exact'] = array( + '#type' => 'checkbox', + '#title' => 'whole word', + + + + ); + + + return $form; +} + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search.admin.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1192 @@ +Facets will not be shown until you enable Facet API module.'); + } + else { + $description .= t('Remember to configure the facets on the search environment page and assign blocks to regions on the block settings page', array( + '!facetslink' => url('admin/config/search/solrsearch/settings/'), + '!blocklink' => url('admin/structure/block'), + )); + } + return array( + '#type' => 'radios', + '#title' => t('Behavior on empty search'), + '#options' => array( + 'none' => t("Show search box"), + 'browse' => t("Show enabled facets' blocks under the search box"), + 'blocks' => t("Show enabled facets' blocks in their configured regions"), + 'results' => t("Show enabled facets' blocks in their configured regions and first page of all available results"), + ), + '#default_value' => $default_value, + '#description' => $description, + ); +} + +/** + * Menu callback for the overview page showing custom search pages and blocks. + * @return array $build + */ +function solrsearch_search_page_list_all() { + + $build['pages'] = solrsearch_search_page_list_pages(); + $build['blocks'] = solrsearch_search_page_list_blocks(); + return $build; + +} + + +/** + * Listing of all the search pages + * @return array $build + */ +function solrsearch_search_page_list_pages() { + $build = array(); + $rows = array(); + $rows['core_solr_search'] = array(); + + // Build the sortable table header. + $header = array( + 'label' => array('data' => t('Name'), 'field' => 's.label'), + 'path' => array('data' => t('Path'), 'field' => 's.search_path'), + 'environment' => array('data' => t('Search environment')), + 'operations' => array('data' => t('Operations')), + ); + + $search_pages = solrsearch_search_load_all_search_pages(); + $default_search_page = solrsearch_search_default_search_page(); + foreach ($search_pages as $search_page) { + $row = array(); + + // Add the label + $label = check_plain($search_page['label']); + // Is this row our default environment? + if ($search_page['page_id'] == $default_search_page) { + $label = t('!search_page (Default)', array('!search_page' => $label)); + } + + $row[] = $label; + // Add the link + $row[] = array( + 'data' => array( + '#type' => 'link', + '#title' => $search_page['search_path'], + '#href' => $search_page['search_path'], + ), + ); + + // Add the search environment + $environment = solrsearch_environment_load($search_page['env_id']); + $row[] = $environment ? check_plain($environment['name']) : check_plain(t('')); + // Operations + $row[] = array('data' => l(t('Edit'), 'admin/config/search/solrsearch/search-pages/' . $search_page['page_id'] . '/edit')); + $row[] = array('data' => l(t('Clone'), 'admin/config/search/solrsearch/search-pages/' . $search_page['page_id'] . '/clone')); + + // Allow to revert a search page or to delete it + if (!isset($search_page['settings']['solrsearch_search_not_removable']) && !isset($search_page['in_code_only'])) { + if ((isset($search_page['type']) && $search_page['type'] == 'Overridden')) { + $row[] = array('data' => l(t('Revert'), 'admin/config/search/solrsearch/search-pages/' . $search_page['page_id'] . '/delete')); + } else { + $row[] = array('data' => l(t('Delete'), 'admin/config/search/solrsearch/search-pages/' . $search_page['page_id'] . '/delete')); + } + } + else { + $row[] = ''; + } + $rows[$search_page['page_id']] = $row; + } + + // Automatically enlarge our header with the operations size + $header['operations']['colspan'] = count(reset($rows)) - 3; + + $build['list'] = array( + '#prefix' => '

Pages

', + '#theme' => 'table', + '#header' => $header, + '#rows' => array_values($rows), + '#empty' => t('No available search pages.'), + ); + $build['pager'] = array( + '#theme' => 'pager', + '#quantity' => 20, + '#weight' => 10, + ); + + return $build; +} + +/** + * Listing of all the search blocks + * @return array $build + */ +function solrsearch_search_page_list_blocks() { + $build = array(); + $rows = array(); + + // Build the sortable table header. + $header = array( + 'label' => array('data' => t('Name'), 'field' => 's.label'), + 'environment' => array('data' => t('Search environment')), + 'operations' => array('data' => t('Operations')), + ); + + $search_blocks = variable_get('solrsearch_search_mlt_blocks', array()); + foreach ($search_blocks as $search_block_id => $search_block) { + $row = array(); + + // Add the label + $label = check_plain($search_block['name']); + $row[] = $label; + + // Add the search environment + $environment = solrsearch_environment_load($search_block['mlt_env_id']); + $row[] = $environment ? check_plain($environment['name']) : check_plain(t('')); + // Operations + if (module_exists('block')) { + $row[] = array('data' => l(t('Configure'), 'admin/structure/block/manage/solrsearch_search/' . $search_block_id . '/configure', array('query' => array('destination' => current_path())))); + } + $row[] = array('data' => l(t('Delete'), 'admin/config/search/solrsearch/search-pages/block/' . $search_block_id . '/delete')); + $rows[$search_block_id] = $row; + } + + // Automatically enlarge our header with the operations size + $header['operations']['colspan'] = count(reset($rows)) - 2; + + $build['list'] = array( + '#prefix' => '

Blocks "More Like This"

', + '#theme' => 'table', + '#header' => $header, + '#rows' => array_values($rows), + '#empty' => t('No available search blocks.'), + ); + $build['pager'] = array( + '#theme' => 'pager', + '#quantity' => 20, + '#weight' => 10, + ); + + return $build; +} + +/** + * Menu callback/form-builder for the form to create or edit a search page. + */ +function solrsearch_search_page_settings_form($form, &$form_state, $search_page = NULL) { + $environments = solrsearch_load_all_environments(); + $options = array('' => t('')); + foreach ($environments as $id => $environment) { + $options[$id] = $environment['name']; + } + // Validate the env_id. + if (!empty($search_page['env_id']) && !solrsearch_environment_load($search_page['env_id'])) { + $search_page['env_id'] = ''; + } + + // Initializes form with common settings. + $form['search_page'] = array( + '#type' => 'value', + '#value' => $search_page, + ); + + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#description' => '', + '#required' => TRUE, + '#size' => 30, + '#maxlength' => 32, + '#default_value' => !empty($search_page['label']) ? $search_page['label'] : '', + '#description' => t('The human-readable name of the search page configuration.'), + ); + + $form['page_id'] = array( + '#type' => 'machine_name', + '#maxlength' => 32, + '#required' => TRUE, + '#machine_name' => array( + 'exists' => 'solrsearch_search_page_exists', + 'source' => array('label'), + ), + '#description' => '', + '#default_value' => !empty($search_page['page_id']) ? $search_page['page_id'] : '', + '#disabled' => !empty($search_page), + '#description' => t('A unique machine-readable identifier for the search page configuration. It must only contain lowercase letters, numbers, and underscores.'), + ); + + $form['description_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Description'), + '#default_value' => !empty($search_page['description']) ? TRUE : FALSE + ); + + $form['description'] = array( + '#type' => 'textfield', + '#title' => t('Provide description'), + '#title_display' => 'invisible', + '#size' => 64, + '#default_value' => !empty($search_page['description']) ? $search_page['description'] : '', + '#dependency' => array( + 'edit-description-enable' => array(1), + ), + ); + + $is_default = FALSE; + if (!empty($search_page)) { + $is_default = $search_page['page_id'] == solrsearch_search_default_search_page(); + } + $form['make_default'] = array( + '#type' => 'checkbox', + '#title' => t('Make this Solr Search Page the default'), + '#description' => t('Useful for eg. making facets to link to this page when they are shown on non-search pages'), + '#default_value' => $is_default, + '#disabled' => $is_default, + ); + + $form['info'] = array( + '#title' => t('Search Page Information'), + '#type' => 'fieldset', + '#collapsible' => FALSE, + '#prefix' => '
', + '#suffix' => '
', + ); + + $core_solr_search = FALSE; + if (!empty($search_page['page_id']) && ($search_page['page_id'] == 'core_solr_search')) { + $core_solr_search = TRUE; + } + if ($core_solr_search) { + $description = t('This page always uses the current default search environment'); + } + else { + $description = t('The environment that is used by this search page. If no environment is selected, this page will be disabled.'); + } + + $form['info']['env_id'] = array( + '#title' => t('Search environment'), + '#type' => 'select', + '#options' => $options, + '#default_value' => !empty($search_page['env_id']) ? $search_page['env_id'] : '', + '#disabled' => $core_solr_search, + '#description' => $description, + ); + + $form['info']['page_title'] = array( + '#title' => t('Title'), + '#type' => 'textfield', + '#required' => TRUE, + '#maxlength' => 255, + '#description' => 'You can use %value to place the search term in the title', + '#default_value' => !empty($search_page['page_title']) ? $search_page['page_title'] : '', + ); + + $search_types = solrsearch_search_load_all_search_types(); + $options = array('custom' => t('Custom Field')); + foreach ($search_types as $id => $search_type) { + $options[$id] = $search_type['name']; + } + + $form['info']['search_type'] = array( + '#title' => t('Search Type'), + '#type' => 'select', + '#options' => $options, + '#default_value' => !empty($search_page['settings']['solrsearch_search_search_type']) ? $search_page['settings']['solrsearch_search_search_type'] : '', + '#access' => !$core_solr_search, + '#description' => t('Use this only when filtering on a value from the search path. + For example, select Taxonomy Term to filter on a term ID (search/taxonomy/%).'), + '#ajax' => array( + 'callback' => 'solrsearch_search_ajax_search_page_default', + 'wrapper' => 'dynamic-search-page', + 'method' => 'replace', + ), + ); + + // Token element validate is added to validate the specific + // tokens that are allowed + $form['info']['search_path'] = array( + '#title' => t('Path'), + '#type' => 'textfield', + '#required' => TRUE, + '#maxlength' => 255, + '#description' => t('For example: search/my-search-page. Search keywords will appear at the end of the path.'), + '#default_value' => !empty($search_page['search_path']) ? $search_page['search_path'] : '', + ); + if (!$core_solr_search) { + $form['info']['search_path']['#description'] .= ' ' . t('You can use one % to make the search page dynamic.'); + } + + $form['info']['custom_filter_enable'] = array( + '#type' => 'checkbox', + '#title' => t('Custom Filter'), + '#default_value' => !empty($search_page['settings']['solrsearch_search_custom_enable']) ? TRUE : FALSE + ); + + $form['info']['filters'] = array( + '#title' => t('Custom filters'), + '#type' => 'textfield', + '#required' => FALSE, + '#maxlength' => 255, + '#description' => t('A comma-separated list of lucene filter queries to apply by default.'), + '#default_value' => !empty($search_page['settings']['fq']) ? implode(', ', $search_page['settings']['fq']) : '', + '#dependency' => array( + 'edit-custom-filter-enable' => array(1), + 'edit-search-type' => array('custom'), + ), + ); + if (!$core_solr_search) { + $form['info']['filters']['#description'] .= ' ' . t('E.g. "bundle:blog, is_uid:(1 OR 2 OR %). % will be replaced by the value of % in the path"'); + } + + $form['advanced'] = array( + '#title' => t('Advanced Search Page Options'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + ); + + // Results per page per search page + $default_value = isset($search_page['settings']['solrsearch_search_per_page']) ? $search_page['settings']['solrsearch_search_per_page'] : '10'; + $form['advanced']['solrsearch_search_per_page'] = array( + '#type' => 'textfield', + '#size' => 3, + '#title' => t('Results per page'), + '#description' => t('How many items will be displayed on one page of the search result.'), + '#default_value' => $default_value, + ); + + // Enable/disable spellcheck on pages + $default_value = isset($search_page['settings']['solrsearch_search_spellcheck']) ? $search_page['settings']['solrsearch_search_spellcheck'] : TRUE; + $form['advanced']['solrsearch_search_spellcheck'] = array( + '#type' => 'checkbox', + '#title' => t('Enable spell check'), + '#description' => t('Display "Did you mean … ?" above search results.'), + '#default_value' => $default_value, + ); + + // Enable/disable search form on search page (replaced by a block perhaps) + $default_value = isset($search_page['settings']['solrsearch_search_search_box']) ? $search_page['settings']['solrsearch_search_search_box'] : TRUE; + $form['advanced']['solrsearch_search_search_box'] = array( + '#type' => 'checkbox', + '#title' => t('Enable the search box on the page'), + '#description' => t('Display a search box on the page.'), + '#default_value' => $default_value, + ); + + // Enable/disable search form on search page (replaced by a block perhaps) + $default_value = isset($search_page['settings']['solrsearch_search_allow_user_input']) ? $search_page['settings']['solrsearch_search_allow_user_input'] : FALSE; + $form['advanced']['solrsearch_search_allow_user_input'] = array( + '#type' => 'checkbox', + '#title' => t('Allow user input using the URL'), + '#description' => t('Allow users to use the URL for manual facetting via fq[] params (e.g. http://example.com/search/site/test?fq[]=uid:1&fq[]=tid:99). This will only work in combination with a keyword search. The recommended value is unchecked'), + '#default_value' => $default_value, + ); + + // Use the main search page setting as the default for new pages. + $default_value = isset($search_page['settings']['solrsearch_search_browse']) ? $search_page['settings']['solrsearch_search_browse'] : 'browse'; + $form['advanced']['solrsearch_search_browse'] = _solrsearch_search_browse_form($default_value); + + // Button for the corresponding actions + $form['actions'] = array( + '#type' => 'actions', + ); + + $form['actions']['submit'] = array( + '#type' => 'submit', + '#redirect' => 'admin/config/search/solrsearch/search-pages', + '#value' => t('Save'), + ); + $form['actions']['submit_edit'] = array( + '#type' => 'submit', + '#value' => t('Save and edit'), + ); + + $form['actions']['cancel'] = array( + '#type' => 'link', + '#title' => t('Cancel'), + '#href' => 'admin/config/search/solrsearch/search-pages', + ); + + $form['#submit'][] = 'solrsearch_search_page_settings_form_submit'; + + return $form; +} + +/** + * Callback element needs only select the portion of the form to be updated. + * Since #ajax['callback'] return can be HTML or a renderable array (or an + * array of commands), we can just return a piece of the form. + */ +function solrsearch_search_ajax_search_page_default($form, $form_state, $search_page = NULL) { + + $search_page = $form_state['values']['search_page']; + $search_types = solrsearch_search_load_all_search_types(); + + // Helping with sensible defaults for the search path + $default_search_path = ''; + if (!empty($form_state['values']['search_type']) && $form_state['values']['search_type'] != 'custom') { + $default_search_path = $search_types[$form_state['values']['search_type']]['default menu']; + $form['info']['search_path']['#value'] = $default_search_path; + } + + // Helping with sensible defaults for the search title + $default_search_title = ''; + + if (empty($form_state['values']['page_title']) && $form_state['values']['search_type'] != 'custom') { + $default_search_title_callback = $search_types[$form_state['values']['search_type']]['title callback']; + $default_search_title = $default_search_title_callback(); + $form['info']['page_title']['#value'] = $default_search_title; + } + return $form['info']; +} + +function solrsearch_search_page_settings_form_validate($form, &$form_state) { + // Performs basic validation of the menu path. + if (url_is_external($form_state['values']['search_path'])) { + form_set_error('search_path', t('Path must be local.')); + } + $form_state['values']['search_path'] = trim($form_state['values']['search_path'], '/'); + if (empty($form_state['values']['search_path'])) { + form_set_error('search_path', t('Path required.')); + } + if (!is_numeric($form_state['values']['advanced']['solrsearch_search_per_page'])) { + form_set_error('advanced][solrsearch_search_per_page', t('The amount of search results must be an integer.')); + } + $form_state['values']['advanced']['solrsearch_search_per_page'] = (int) $form_state['values']['advanced']['solrsearch_search_per_page']; + if (empty($form_state['values']['advanced']['solrsearch_search_per_page'])) { + form_set_error('advanced][solrsearch_search_per_page', t('The amount of search results cannot be empty.')); + } + if (count(explode('%', $form_state['values']['search_path'])) > 2) { + form_set_error('search_path', t('Only one % placeholder is allowed.')); + } +} + +/** + * Processes solrsearch_search_page_settings_form form submissions. + */ +function solrsearch_search_page_settings_form_submit($form, &$form_state) { + $settings = array(); + $settings['fq'] = array(); + if ($form_state['values']['filters']) { + foreach (explode(',', $form_state['values']['filters']) as $string) { + $string = trim($string); + // Minimal validation. ':' must exist and can't be the 1st char.. + if (strpos($string, ':')) { + $settings['fq'][] = $string; + } + } + } + $settings['solrsearch_search_custom_enable'] = $form_state['values']['custom_filter_enable']; + $settings['solrsearch_search_search_type'] = $form_state['values']['search_type']; + // Add all advanced settings. + $settings += $form_state['values']['advanced']; + + // Set the default search page settings + if (!empty($form_state['values']['make_default']) && isset($form_state['values']['page_id'])) { + variable_set('solrsearch_search_default_search_page', $form_state['values']['page_id']); + } + + $search_page = array(); + $search_page['page_id'] = $form_state['values']['page_id']; + $search_page['label'] = $form_state['values']['label']; + $search_page['description'] = $form_state['values']['description']; + $search_page['env_id'] = $form_state['values']['env_id']; + $search_page['search_path'] = $form_state['values']['search_path']; + $search_page['page_title'] = $form_state['values']['page_title']; + $search_page['settings'] = $settings; + solrsearch_search_page_save($search_page); + + // Saves our values in the database, sets redirect path on success. + drupal_set_message(t('The configuration options have been saved for %page.', array('%page' => $form_state['values']['label']))); + if (isset($form_state['clicked_button']['#redirect'])) { + $form_state['redirect'] = $form_state['clicked_button']['#redirect']; + } + else { + $form_state['redirect'] = current_path(); + } + // Regardlessly of the destination parameter we want to go to another page + unset($_GET['destination']); + drupal_static_reset('drupal_get_destination'); + drupal_get_destination(); + // Menu rebuild needed to pick up search path. + menu_rebuild(); +} + +/** + * Deletes a single search page configuration. + */ +function solrsearch_search_delete_search_page_confirm($form, &$form_state, $search_page) { + + // Sets values required for deletion. + $form['page_id'] = array('#type' => 'value', '#value' => $search_page['page_id']); + $form['label'] = array('#type' => 'value', '#value' => $search_page['label']); + + if (isset($search_page['export_type']) && $search_page['export_type'] == '3') { + $verb = t('Revert'); + } + else { + $verb = t('Delete'); + } + + // Sets the message, or the title of the page. + $message = t( + 'Are you sure you want to !verb the %label search page configuration?', + array('%label' => $form['label']['#value'], '!verb' => strtolower($verb)) + ); + + + // Builds caption. + $caption = '

'; + $caption .= t( + 'The %label search page configuration will be deleted.', + array('%label' => $form['label']['#value']) + ); + $caption .= '

'; + $caption .= '

' . t('This action cannot be undone.') . '

'; + + // Finalizes and returns the confirmation form. + $return_path = 'admin/config/search/solrsearch/search-pages'; + $button_text = $verb; + if (!isset($search_page['settings']['solrsearch_search_not_removable'])) { + return confirm_form($form, filter_xss($message), $return_path, filter_xss($caption), check_plain($button_text)); + } + else { + // Maybe this should be solved somehow else + drupal_access_denied(); + } +} + +/** + * Process content type delete confirm submissions. + */ +function solrsearch_search_delete_search_page_confirm_submit($form, &$form_state) { + // Deletes the index configuration settings. + // @todo Invoke a hook that allows backends and indexers to delete their stuff. + db_delete('solrsearch_search_page') + ->condition('page_id', $form_state['values']['page_id']) + ->execute(); + + // Sets message, logs action. + drupal_set_message(t( + 'The %label search page configuration has been deleted.', + array('%label' => $form_state['values']['label']) + )); + watchdog('solrsearch_search', 'Deleted search page configuration "@page_id".', array('@page_id' => $form_state['values']['page_id']), WATCHDOG_NOTICE); + + // Rebuilds the menu cache. + menu_rebuild(); + + // Returns back to search page list page. + $form_state['redirect'] = 'admin/config/search/solrsearch/search-pages'; +} + +/** + * Clones a single search page configuration + * @param $search_page + * The search page that needs to be cloned + */ +function solrsearch_search_clone_search_page_confirm($form, &$form_state, $search_page) { + $form['page_id'] = array( + '#type' => 'value', + '#value' => $search_page['page_id'], + ); + return confirm_form( + $form, + t('Are you sure you want to clone search page %name?', array('%name' => $search_page['label'])), + 'admin/config/search/solrsearch', + '', + t('Clone'), + t('Cancel') + ); +} + +/** + * Submits the confirmations of the cloning of a search page + */ +function solrsearch_search_clone_search_page_confirm_submit($form, &$form_state) { + if (solrsearch_search_page_clone($form_state['values']['page_id'])) { + drupal_set_message(t('The search page was cloned')); + } + $form_state['redirect'] = 'admin/config/search/solrsearch/search-pages'; +} + +/** + * Menu callback - the settings form. + */ +function solrsearch_search_get_fields($environment = NULL) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + $environment = solrsearch_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Try to fetch the schema fields. + try { + $solr = solrsearch_get_solr($env_id); + $fields = $solr->getFields(); + return $fields; + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + drupal_set_message(nl2br(check_plain($e->getMessage())), 'warning'); + drupal_set_message(t('Cannot get information about the fields in the index.'), 'warning'); + } +} + +/** + * Menu callback - Bias settings form. + */ +function solrsearch_bias_settings_page($environment = NULL) { + if (empty($environment)) { + $env_id = solrsearch_default_environment(); + $environment = solrsearch_environment_load($env_id); + } + $env_id = $environment['env_id']; + + // Initializes output with information about which environment's setting we are + // editing, as it is otherwise not transparent to the end user. + $output = array( + 'solrsearch_environment' => array( + '#theme' => 'solrsearch_settings_title', + '#env_id' => $env_id, + ), + ); + + // Adds content bias and type boost forms. + $fields = solrsearch_search_get_fields($environment); + $form = array(); + $form = drupal_get_form('solrsearch_search_bias_form', $env_id, $fields); + $output['bias_forms'] = $form; + return $output; +} + +function solrsearch_search_bias_form($form, &$form_state, $env_id, $fields) { + $form['#env_id'] = $env_id; + $form['bias_tabs'] = array( + '#type' => 'vertical_tabs', + ); + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + '#submit' => array('solrsearch_search_bias_form_submit'), + ); + $form['actions']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset to defaults'), + '#submit' => array('solrsearch_search_bias_form_reset'), + ); + $form += solrsearch_search_result_bias_form($env_id); + $form += solrsearch_search_type_boost_form($env_id); + $form += solrsearch_search_field_bias_form($fields, $env_id); + return $form; +} + +function solrsearch_search_bias_form_submit(&$form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + foreach ($form_state['values'] as $key => $value) { + if (is_array($value) && isset($form_state['values']['array_filter'])) { + $value = array_keys(array_filter($value)); + } + // There is no need to set default variable values. + if (!isset($form[$key]['#default_value']) || $form[$key]['#default_value'] != $value) { + switch ($key) { + case 'solrsearch_search_sticky_boost' : + case 'solrsearch_search_promote_boost' : + case 'solrsearch_search_date_boost' : + case 'solrsearch_search_comment_boost' : + case 'solrsearch_search_changed_boost' : + case 'solrsearch_search_type_boosts' : + case 'field_bias' : + solrsearch_environment_variable_set($form['#env_id'], $key, $value); + } + } + } + drupal_set_message(t('The configuration options have been saved.')); +} + +function solrsearch_search_bias_form_reset($form, &$form_state) { + // Exclude unnecessary elements. + form_state_values_clean($form_state); + + foreach ($form_state['values'] as $key => $value) { + solrsearch_environment_variable_del($form['#env_id'], $key); + } + drupal_set_message(t('The configuration options have been reset to their default values.')); +} + +/** + * Form builder function to set date, comment, etc biases. + */ +function solrsearch_search_result_bias_form($env_id) { + + $date_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_date_boost', '0:0'); + $comment_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_comment_boost', '0:0'); + $changed_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_changed_boost', '0:0'); + $sticky_boost = solrsearch_environment_variable_get($env_id, 'solrsearch_search_sticky_boost', '0'); + $promote_boost = solrsearch_environment_variable_get($env_id, 'solrsearch_search_promote_boost', '0'); + + $options = array( + '10:2000.0' => '10', + '8:1000.0' => '9', + '8:700.0' => '8', + '8:500.0' => '7', + '4:300.0' => '6', + '4:200.0' => '5', + '4:150.0' => '4', + '2:150.0' => '3', + '2:100.0' => '2', + '1:100.0' => '1', + '0:0' => t('Ignore'), + ); + + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Ignore'); + + $form = array(); + $form['result_bias'] = array( + '#type' => 'fieldset', + '#title' => t('Result biasing'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#description' => t('Give bias to certain properties when ordering the search results. Any value except Ignore will increase the score of the given type in search results. Choose Ignore to ignore any given property.'), + '#group' => 'bias_tabs', + ); + $form['result_bias']['solrsearch_search_sticky_boost'] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => t("Sticky at top of lists"), + '#default_value' => $sticky_boost, + '#description' => t("Select additional bias to give to nodes that are set to be 'Sticky at top of lists'."), + ); + $form['result_bias']['solrsearch_search_promote_boost'] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => t("Promoted to home page"), + '#default_value' => $promote_boost, + '#description' => t("Select additional bias to give to nodes that are set to be 'Promoted to home page'."), + ); + $form['result_bias']['solrsearch_search_date_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More recently created"), + '#default_value' => $date_settings, + '#description' => t('This setting will change the result scoring so that nodes created more recently may appear before those with higher keyword matching.'), + ); + $form['result_bias']['solrsearch_search_comment_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More comments"), + '#default_value' => $comment_settings, + '#description' => t('This setting will change the result scoring so that nodes with more comments may appear before those with higher keyword matching.'), + ); + $form['result_bias']['solrsearch_search_changed_boost'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t("More recent comments"), + '#default_value' => $changed_settings, + '#description' => t('This setting will change the result scoring so that nodes with the most recent comments (or most recent updates to the node itself) may appear before those with higher keyword matching.'), + ); + return $form; +} + +/** + * Form builder function to set query field weights. + */ +function solrsearch_search_field_bias_form($fields, $env_id) { + $form = array(); + // get the current weights + $defaults = array( + 'content' => '1.0', + 'ts_comments' => '0.5', + 'tos_content_extra' => '0.1', + 'label' => '5.0', + 'tos_name' => '3.0', + 'taxonomy_names' => '2.0', + 'tags_h1' => '5.0', + 'tags_h2_h3' => '3.0', + 'tags_h4_h5_h6' => '2.0', + 'tags_inline' => '1.0', + 'tags_a' => '0', + ); + $qf = solrsearch_environment_variable_get($env_id, 'field_bias', $defaults); + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Omit'); + if (!$qf) { + $qf = $defaults; + } + if ($fields) { + $form['field_bias'] = array( + '#type' => 'fieldset', + '#title' => t('Field biases'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#tree' => TRUE, + '#description' => t('Specify here which fields are more important when searching. Give a field a greater numeric value to make it more important. If you omit a field, it will not be searched.'), + '#group' => 'bias_tabs', + ); + foreach ($fields as $field_name => $field) { + // Only indexed feids are searchable. + if ($field->schema{0} == 'I') { + // By default we only show text fields. Use hook_form_alter to change. + // We use filter_xss to make sure links are allowed + $form['field_bias'][$field_name] = array( + '#access' => ($field->type == 'text' || $field->type == 'text_und'), + '#type' => 'select', + '#options' => $weights, + '#title' => filter_xss(solrsearch_field_name_map($field_name)), + '#default_value' => isset($qf[$field_name]) ? $qf[$field_name] : '0', + ); + } + } + + // Make sure all the default fields are included, even if they have + // no indexed content. + foreach ($defaults as $field_name => $weight) { + $form['field_bias'][$field_name] = array( + '#type' => 'select', + '#options' => $weights, + '#title' => check_plain(solrsearch_field_name_map($field_name)), + '#default_value' => isset($qf[$field_name]) ? $qf[$field_name] : $defaults[$field_name], + ); + } + + ksort($form['field_bias']); + } + return $form; +} + +/** + * Form builder function to set query type weights. + */ +function solrsearch_search_type_boost_form($env_id) { + + $form['type_boost'] = array( + '#type' => 'fieldset', + '#title' => t('Type biasing'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#group' => 'bias_tabs', + ); + $form['type_boost']['solrsearch_search_type_boosts'] = array( + '#type' => 'item', + '#description' => t("Specify here which node types should get a higher relevancy score in searches. Any value except Ignore will increase the score of the given type in search results."), + '#tree' => TRUE, + ); + + $weights = drupal_map_assoc(array('21.0', '13.0', '8.0', '5.0', '3.0', '2.0', '1.0', '0.8', '0.5', '0.3', '0.2', '0.1')); + $weights['0'] = t('Ignore'); + + // Get the current boost values. + $type_boosts = solrsearch_environment_variable_get($env_id, 'solrsearch_search_type_boosts', array()); + $names = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!empty($entity_info['solrsearch']['indexable'])) { + foreach ($entity_info['bundles'] as $key => $info) { + $names[$key] = $info['label']; + } + } + } + asort($names); + + foreach ($names as $type => $name) { + $form['type_boost']['solrsearch_search_type_boosts'][$type] = array( + '#type' => 'select', + '#title' => t('%type type content bias', array('%type' => $name)), + '#options' => $weights, + '#default_value' => isset($type_boosts[$type]) ? $type_boosts[$type] : 0, + ); + } + + return $form; +} + +/** + * MoreLikeThis administration and utility functions. + */ +function solrsearch_search_mlt_add_block_form() { + $form = solrsearch_search_mlt_block_form(); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => '5', + ); + return $form; +} + +function solrsearch_search_mlt_add_block_form_submit($form, &$form_state) { + solrsearch_search_mlt_save_block($form_state['values']); + $block_message = t('New More like this block created. Configure this block in the Block administration', array('!configure' => url('admin/structure/block'))); + drupal_set_message($block_message); + $form_state['redirect'] = 'admin/config/search/solrsearch/search-pages'; +} + +/** + * Merge supplied settings with the standard defaults.. + */ +function solrsearch_search_mlt_block_defaults($block = array()) { + return $block + array( + 'name' => '', + 'num_results' => '5', + 'mlt_fl' => array( + 'label' => 'label', + 'taxonomy_names' => 'taxonomy_names', + ), + 'mlt_env_id' => 'solr', + 'mlt_mintf' => '1', + 'mlt_mindf' => '1', + 'mlt_minwl' => '3', + 'mlt_maxwl' => '15', + 'mlt_maxqt' => '20', + 'mlt_type_filters' => array(), + 'mlt_custom_filters' => '', + ); +} + +/** + * Constructs a list of field names used on the settings form. + * + * @return array An array containing a the fields in the solr instance. + */ +function solrsearch_search_mlt_get_fields() { + $rows = array(); + + try { + $solr = solrsearch_get_solr(); + $fields = $solr->getFields(); + foreach ($fields as $field_name => $field) { + if ($field->schema{4} == 'V') { + $rows[$field_name] = solrsearch_field_name_map($field_name); + } + } + ksort($rows); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + + return $rows; +} + +/** + * A helper function to save MLT block data. + * + * If passed a block delta, the function will update block settings. If it is + * not passed a block delta, the function will create a new block. + * + * @param array $block_settings An array containing the settings required to form + * a moreLikeThis request. + * + * @param int $delta The id of the block you wish to update. + */ +function solrsearch_search_mlt_save_block($block_settings = array(), $delta = NULL) { + $blocks = variable_get('solrsearch_search_mlt_blocks', array()); + if (is_null($delta)) { + $count = 0; + ksort($blocks); + // Construct a new array key. + if (end($blocks)) { + list(, $count) = explode('-', key($blocks)); + } + $delta = sprintf('mlt-%03d', 1 + $count); + } + $defaults = solrsearch_search_mlt_block_defaults(); + // Remove stray form values. + $blocks[$delta] = array_intersect_key($block_settings, $defaults) + $defaults; + // Eliminate non-selected fields. + $blocks[$delta]['mlt_fl'] = array_filter($blocks[$delta]['mlt_fl']); + $blocks[$delta]['delta'] = $delta; + $blocks[$delta]['mlt_type_filters'] = array_filter($blocks[$delta]['mlt_type_filters']); + $blocks[$delta]['mlt_custom_filters'] = trim($blocks[$delta]['mlt_custom_filters']); + variable_set('solrsearch_search_mlt_blocks', $blocks); +} + +function solrsearch_search_mlt_delete_block_form($form, &$form_state, $block) { + if ($block) { + // Backwards compatibility for the block deltas + if (isset($block['delta'])) { + $delta = $block['delta']; + } + else { + $delta = arg(6); + } + // Add our delta to the delete form + $form['delta'] = array( + '#type' => 'value', + '#value' => $delta, + ); + $question = t('Are you sure you want to delete the "More Like this" block %name?', array('%name' => $block['name'])); + $path = 'admin/structure/block'; + $description = t('The block will be deleted. This action cannot be undone.'); + $yes = t('Delete'); + $no = t('Cancel'); + return confirm_form($form, filter_xss($question), $path, $description, $yes, $no); + } +} + +function solrsearch_search_mlt_delete_block_form_submit($form, &$form_state) { + $blocks = solrsearch_search_load_all_mlt_blocks(); + + unset($blocks[$form_state['values']['delta']]); + variable_set('solrsearch_search_mlt_blocks', $blocks); + drupal_set_message(t('The block has been deleted.')); + $form_state['redirect'] = 'admin/config/search/solrsearch/search-pages'; +} + +/** + * Form to edit moreLikeThis block settings. + * + * @param int $delta If editing, the id of the block to edit. + * + * @return array The form used for editing. + * @todo Add term boost settings. + * @todo Enable the user to specify a query, rather then forcing suggestions + * based on the node id. + */ +function solrsearch_search_mlt_block_form($block_id = NULL) { + if (!empty($block_id)) { + $block = solrsearch_search_mlt_block_load($block_id); + if (!$block) { + return array(); + } + } + else { + $block = solrsearch_search_mlt_block_defaults(); + } + + $form['delta'] = array( + '#type' => 'value', + '#default_value' => isset($block['delta']) ? $block['delta'] : '', + '#weight' => '-2', + ); + + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Block name'), + '#description' => t('The block name displayed to site users.'), + '#required' => TRUE, + '#default_value' => isset($block['name']) ? $block['name'] : '', + '#weight' => '-2', + ); + + $environments = solrsearch_load_all_environments(); + $options = array('' => t('')); + foreach ($environments as $id => $environment) { + $options[$id] = $environment['name']; + } + $form['mlt_env_id'] = array( + '#title' => t('Search environment'), + '#type' => 'select', + '#options' => $options, + '#default_value' => isset($block['mlt_env_id']) ? $block['mlt_env_id'] : solrsearch_default_environment(), + ); + + $form['num_results'] = array( + '#type' => 'select', + '#title' => t('Maximum number of related items to display'), + '#default_value' => isset($block['num_results']) ? $block['num_results'] : '', + '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)), + '#weight' => -1, + + ); + $form['mlt_fl'] = array( + '#type' => 'checkboxes', + '#title' => t('Fields for finding related content'), + '#description' => t('Choose the fields to be used in calculating similarity. The default combination of %taxonomy_names and %title will provide relevant results for typical sites.', array("%taxonomy_names" => solrsearch_field_name_map("taxonomy_names"), "%title" => solrsearch_field_name_map("label"))), + '#options' => solrsearch_search_mlt_get_fields(), + '#required' => TRUE, + '#default_value' => isset($block['mlt_fl']) ? $block['mlt_fl'] : '', + ); + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced configuration'), + '#weight' => '1', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $options = drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7)); + $form['advanced']['mlt_mintf'] = array( + '#type' => 'select', + '#title' => t('Minimum term frequency'), + '#description' => t('A word must appear this many times in any given document before the document is considered relevant for comparison.'), + '#default_value' => isset($block['mlt_mintf']) ? $block['mlt_mintf'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_mindf'] = array( + '#type' => 'select', + '#title' => t('Minimum document frequency'), + '#description' => t('A word must occur in at least this many documents before it will be used for similarity comparison.'), + '#default_value' => isset($block['mlt_mindf']) ? $block['mlt_mindf'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_minwl'] = array( + '#type' => 'select', + '#title' => t('Minimum word length'), + '#description' => 'You can use this to eliminate short words such as "the" and "it" from similarity comparisons. Words must be at least this number of characters or they will be ignored.', + '#default_value' => isset($block['mlt_minwl']) ? $block['mlt_minwl'] : '', + '#options' => $options, + ); + $form['advanced']['mlt_maxwl'] = array( + '#type' => 'select', + '#title' => t('Maximum word length'), + '#description' => t('You can use this to eliminate very long words from similarity comparisons. Words of more than this number of characters will be ignored.'), + '#default_value' => isset($block['mlt_maxwl']) ? $block['mlt_maxwl'] : '', + '#options' => drupal_map_assoc(array(8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)), + ); + $form['advanced']['mlt_maxqt'] = array( + '#type' => 'select', + '#title' => t('Maximum number of query terms'), + '#description' => t('The maximum number of query terms that will be included in any query. Lower numbers will result in fewer recommendations but will get results faster. If a content recommendation is not returning any recommendations, you can either check more "Comparison fields" checkboxes or increase the maximum number of query terms here.'), + '#options' => drupal_map_assoc(array(3, 5, 7, 10, 12, 15, 20, 25, 30, 35, 40)), + '#default_value' => isset($block['mlt_maxqt']) ? $block['mlt_maxqt'] : '', + ); + + $form['restrictions'] = array( + '#type' => 'fieldset', + '#title' => t('Filters'), + '#weight' => '1', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $type_options = array(); + foreach (node_type_get_types() as $key => $type) { + $type_options[$key] = $type->name; + } + + $form['restrictions']['mlt_type_filters'] = array( + '#type' => 'checkboxes', + '#title' => t('Content Types'), + '#default_value' => is_array($block['mlt_type_filters']) ? $block['mlt_type_filters'] : array(), + '#options' => $type_options, + '#description' => t('Select the content types that similarity suggestions should be restricted to. Multiple types are joined with an OR query, so selecting more types results in more recommendations. If none are selected, no filter will be applied.'), + '#weight' => '-2', + ); + + $form['restrictions']['mlt_custom_filters'] = array( + '#type' => 'textfield', + '#title' => t('Additional Query'), + '#description' => t("A query, in Lucene syntax, which will further filter the similarity suggestions. For example, 'label:strategy' will filter related content further to only those with strategy in the title. Here are some more examples:") . + '
    +
  • ss_language:fr
  • +
  • tid:(5 OR 7)
  • +
  • ds_created:[2009-05-01T23:59:59Z TO 2009-07-28T12:30:00Z]
  • +
  • -is_uid:0, -is_uid:1
  • +
', + '#required' => FALSE, + '#default_value' => isset($block['mlt_custom_filters']) ? $block['mlt_custom_filters'] : '', + '#weight' => '-1', + ); + + return $form; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,21 @@ +name = solrsearch_search +description = Frontend for solr II +dependencies[] = search +dependencies[] = solrsearch +package = SolrSearch +core = 7.x +configure = admin/config/search/solrsearch/search-pages + +files[] = solrsearch_search.install +files[] = solrsearch_search.module +files[] = solrsearch_search_blocks.inc +files[] = solrsearch_search.admin.inc +files[] = solrsearch_search.pages.inc + + +; Information added by drupal.org packaging script on 2013-03-15 +version = "7.x-1.1+34-dev" +core = "7.x" +project = "solrsearch" +datestamp = "1363307665" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,390 @@ + 'tid', + 'solrsearch_search_per_page' => 10, + 'solrsearch_search_browse' => 'results', + 'solrsearch_search_spellcheck' => FALSE, + 'solrsearch_search_search_box' => FALSE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'taxonomy_search', + 'label' => 'Taxonomy Search', + 'description' => 'Search all items with given term', + 'search_path' => 'taxonomy/term/%', + 'env_id' => '', + 'page_title' => '%value', + 'settings' => $settings, + ); + db_insert('solrsearch_search_page')->fields($fields)->execute(); +} + +/** + * Implements hook_enable(). + */ +function solrsearch_search_enable() { + // Make sure the default core search page is installed. + //dpm("ENABLE"); + $search_page = solrsearch_search_page_load('core_solr_search'); + //dpm($search_page); + if (empty($search_page)) { + // Add Default search page (core search) + // This is a duplication from update_7004 but it is intended + // so future changes are possible without breaking the update + $settings = array( + 'solrsearch_search_search_type' => 'custom', + 'solrsearch_search_per_page' => 10, + 'solrsearch_search_browse' => 'browse', + 'solrsearch_search_spellcheck' => TRUE, + 'solrsearch_search_not_removable' => TRUE, + 'solrsearch_search_search_box' => TRUE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'core_solr_search', + 'label' => 'Core Solr Search', + 'description' => 'Core SolrSearch', + 'search_path' => 'solrsearch/site', + 'env_id' => 'searchecho', + 'page_title' => 'echo', + 'settings' => $settings, + ); + db_insert('solrsearch_search_page')->fields($fields)->execute(); + } + + + #$active = variable_get('search_active_modules', array('node', 'user')); + #$active[] = 'solrsearch_search'; + #variable_set('search_active_modules', array_unique($active)); +} + +/** + * Implements hook_schema(). + */ +function solrsearch_search_schema() { + $schema = array(); + + $schema['solrsearch_search_page'] = array( + 'description' => 'Apache Solr Search search page settings.', + 'export' => array( + // Environment machine name. + 'key' => 'page_id', + // Description of key. + 'key name' => 'search page machine name', + // Variable name to use in exported code. + 'identifier' => 'searcher', + // Use the same hook as the API name below. + 'default hook' => 'solrsearch_search_default_searchers', + 'status' => 'solrsearch_search_page_status', + // CTools API implementation. + 'api' => array( + 'owner' => 'solrsearch_search', + 'api' => 'solrsearch_search_defaults', + 'minimum_version' => 3, + 'current_version' => 3, + ), + // Includes all search page specific configurations. + 'export callback' => 'solrsearch_search_page_settings_export', + ), + 'fields' => array( + 'page_id' => array( + 'description' => 'The machine readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'label' => array( + 'description' => 'The human readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'The description of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'search_path' => array( + 'description' => 'The path to the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'page_title' => array( + 'description' => 'The title of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'env_id' => array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'settings' => array( + 'description' => 'Serialized storage of general settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('page_id'), + 'indexes' => array( + 'env_id' => array('env_id'), + ), + ); + + return $schema; +} + +/** + * Implements hook_uninstall(). + */ +function solrsearch_search_uninstall() { + $stored = variable_get('solrsearch_index_last', array()); + unset($stored['solrsearch_search']); + variable_set('solrsearch_index_last', $stored); + + $active = variable_get('search_active_modules', array('node', 'user')); + $idx = array_search('solrsearch_search', $active); + if ($idx !== FALSE) { + unset($active[$idx]); + variable_set('search_active_modules', $active); + } + // Remove variables. + variable_del('solrsearch_search_spellcheck'); + variable_del('solrsearch_search_mlt_blocks'); + variable_del('solrsearch_search_default_search_page'); + // Remove blocks. + db_delete('block')->condition('module', 'solrsearch_search')->execute(); +} + +/** + * Various updates for Drupal 7. + */ +function solrsearch_search_update_7000() { + $taxo_links = variable_get('solrsearch_search_taxonomy_links', 0); + // TODO - enable the new contrib module? + variable_del('solrsearch_search_taxonomy_links'); + // TODO - possibly rename block deltas, etc. + $active = variable_get('search_active_modules', array('node', 'user')); + $active[] = 'solrsearch_search'; + variable_set('search_active_modules', array_unique($active)); + if (variable_get('solrsearch_search_make_default', 0)) { + variable_set('search_default_module', 'solrsearch_search'); + } + variable_del('solrsearch_search_make_default'); +} + +/** + * Add solrsearch_search_page table. + */ +function solrsearch_search_update_7001() { + // Moved to 7002 +} + +/** + * Add solrsearch_search_page table for real. + */ +function solrsearch_search_update_7002() { + + $schema['solrsearch_search_page'] = array( + 'description' => 'Apache Solr Search search page settings.', + 'export' => array( + 'key' => 'page_id', + 'identifier' => 'searcher', + 'default hook' => 'solrsearch_search_default_searchers', + 'status' => 'solrsearch_search_page_status', + 'api' => array( + 'owner' => 'solrsearch_search', + 'api' => 'solrsearch_search_defaults', + 'minimum_version' => 3, + 'current_version' => 3, + ), + 'export callback' => 'solrsearch_search_page_settings_export', + ), + 'fields' => array( + 'page_id' => array( + 'description' => 'The machine readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'label' => array( + 'description' => 'The human readable name of the search page.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'The description of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'search_path' => array( + 'description' => 'The path to the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'page_title' => array( + 'description' => 'The title of the search page.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'env_id' => array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'settings' => array( + 'description' => 'Serialized storage of general settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('page_id'), + 'indexes' => array( + 'env_id' => array('env_id'), + ), + ); + if (db_table_exists('solrsearch_search_page')) { + // Just in case you are chasing HEAD. + db_drop_table('solrsearch_search_page'); + } + db_create_table('solrsearch_search_page', $schema['solrsearch_search_page']); +} + + +/** + * Delete all Apache Solr Search blocks - they moved to Facet API. + */ +function solrsearch_search_update_7003() { + // Remove blocks. + db_delete('block')->condition('module', 'solrsearch_search')->execute(); +} + +/** + * Add a default search page for core + * Add a taxonomy page if the taxonomy module was ever active + */ +function solrsearch_search_update_7004() { + // Add Default search page (core search) + $settings = array( + 'solrsearch_search_search_type' => 'custom', + 'solrsearch_search_per_page' => variable_get('solrsearch_rows', 10), + 'solrsearch_search_browse' => variable_get('solrsearch_search_browse', 'browse'), + 'solrsearch_search_spellcheck' => variable_get('solrsearch_search_spellcheck', TRUE), + 'solrsearch_search_not_removable' => TRUE, + 'solrsearch_search_search_box' => TRUE, + ); + $settings = serialize($settings); + + $fields = array( + + 'page_id' => 'core_solr_search', + 'label' => 'Core Solr Search', + 'description' => 'Core SolrSearch', + 'search_path' => 'solrsearch/site', + 'env_id' => 'searchecho', + 'page_title' => 'echo', + 'settings' => $settings, + ); + db_insert('solrsearch_search_page')->fields($fields)->execute(); + // Remove variables. + variable_del('solrsearch_search_spellcheck'); + variable_del('solrsearch_search_browse'); + + // Add this taxonomy search page to the database + $settings = array( + 'solrsearch_search_search_type' => 'tid', + 'solrsearch_search_per_page' => 10, + 'solrsearch_search_browse' => 'results', + 'solrsearch_search_spellcheck' => FALSE, + 'solrsearch_search_search_box' => FALSE, + ); + $settings = serialize($settings); + + $fields = array( + 'page_id' => 'taxonomy_search', + 'label' => 'Taxonomy Search', + 'description' => 'Search all items with given term', + 'search_path' => 'taxonomy/term/%', + 'env_id' => '', + 'page_title' => '%value', + 'settings' => $settings, + ); + db_insert('solrsearch_search_page')->fields($fields)->execute(); + + // Check if the taxonomy module was ever present + $status = db_query("SELECT 1 FROM {system} WHERE name = 'solrsearch_taxonomy'")->fetchField(); + if ($status) { + $message = t('If you had the solrsearch_taxonomy module enabled please go to the !link and enable the Taxonomy Term page', array('!link' => l('Apache Solr custom pages', 'admin/config/search/solrsearch/search-pages'))); + drupal_set_message($message, 'warning'); + } +} + +/** + * Make the env_id length on the solrsearch_search_page table 64 characters + * to match the length of the env_id on all other tables + */ +function solrsearch_search_update_7005(&$sandbox) { + db_drop_index('solrsearch_search_page', 'env_id'); + db_change_field('solrsearch_search_page', 'env_id', 'env_id', + array( + 'description' => 'The machine name of the search environment.', + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + array( + 'indexes' => array( + 'env_id' => array('env_id'), + ) + ) + ); +} + +/** + * Remove all solrsearch_search env variables for show_facets if it is zero + */ +function solrsearch_search_update_7006() { + module_load_include('module', 'solrsearch'); + $environments = solrsearch_load_all_environments(); + foreach ($environments as $environment) { + $show_facets = solrsearch_environment_variable_get($environment['env_id'], 'solrsearch_search_show_facets', 0); + if ($show_facets === 0) { + solrsearch_environment_variable_del($environment['env_id'], 'solrsearch_search_show_facets'); + } + } +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search.module Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1824 @@ +fetchField(); + + //dpm($query); + //dpm($count); + if ($count == 0) { + return NULL; + } + + // Load the default search page, we only support facets to link to this + // search page due to complexity and slow downs + //dpm("SEARCH PAGE I"); + + $search_page_id = solrsearch_search_default_search_page(); + //dpm("SP".$search_page_id); + $search_page = solrsearch_search_page_load($search_page_id); + // Do not continue if our search page is not valid + if (empty($search_page)) { + return NULL; + } + //dpm("SEARCH PAGE II"); + //dpm($search_page); + $show_facets = solrsearch_environment_variable_get($search_page['env_id'], 'solrsearch_search_show_facets', 0); + //dpm($show_facets); + if ($show_facets) { + + // Converts current path to lowercase for case insensitive matching. + $paths = array(); + $paths[] = drupal_strtolower(drupal_get_path_alias(current_path())); + $paths[] = drupal_strtolower(current_path()); + + $facet_pages = solrsearch_environment_variable_get($search_page['env_id'], 'solrsearch_search_facet_pages', ''); + //dpm("FACET_PAGES"); + //dpm($facet_pages); + //dpm($paths); + //dpm("FACET_PAGES_PATJS"); + // Iterates over each environment to check if an empty query should be run. + if (!empty($facet_pages)) { + // Compares path with settings, runs query if there is a match. + $patterns = drupal_strtolower($facet_pages); + foreach ($paths as $path) { + //dpm($path."===".$patterns); + if (drupal_match_path($path, $patterns)) { + //dpm("MATCH"); + try { + if (!empty($search_page['search_path'])) { + + $solr = solrsearch_get_solr($search_page['env_id']); + // Initializes params for empty query. + //dpm("solr"); + //dpm($solr); + $params = array( + 'spellcheck' => 'false', + 'fq' => array(), + 'rows' => 1, + ); + $context['page_id'] = $search_page_id; + $context['search_type'] = 'solrsearch_search_show_facets'; + solrsearch_search_run_empty('solrsearch', $params, $search_page['search_path'], $solr, $context); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + } + } + } +} + +/** + * Implements hook_menu(). + */ +function solrsearch_search_menu() { + $items['admin/config/search/solrsearch/search-pages'] = array( + 'title' => 'Pages/Blocks', + 'description' => 'Configure search pages', + 'page callback' => 'solrsearch_search_page_list_all', + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'solrsearch_search.admin.inc', + ); + $items['admin/config/search/solrsearch/search-pages/add'] = array( + 'title' => 'Add search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_search_page_settings_form'), + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 1, + 'file' => 'solrsearch_search.admin.inc', + ); + $items['admin/config/search/solrsearch/search-pages/%solrsearch_search_page/edit'] = array( + 'title' => 'Edit search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_search_page_settings_form', 5), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch_search.admin.inc', + ); + $items['admin/config/search/solrsearch/search-pages/%solrsearch_search_page/delete'] = array( + 'title' => 'Delete search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_search_delete_search_page_confirm', 5), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch_search.admin.inc', + ); + $items['admin/config/search/solrsearch/search-pages/%solrsearch_search_page/clone'] = array( + 'title' => 'Clone search page', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_search_clone_search_page_confirm', 5), + 'access arguments' => array('administer search'), + 'file' => 'solrsearch_search.admin.inc', + ); + $items['admin/config/search/solrsearch/search-pages/addblock'] = array( + 'title' => 'Add search block "More Like This"', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('solrsearch_search_mlt_add_block_form'), + 'access arguments' => array('administer search'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => 2, + 'file' => 'solrsearch_search.admin.inc', + ); + + + return $items; +} + +function UNUSED_solrsearch_search_menu_alter(&$items) { + // Gets default search information. + $default_info = search_get_default_module_info(); + $search_types = solrsearch_search_load_all_search_types(); + $search_pages = solrsearch_search_load_all_search_pages(); + + // Iterates over search pages, builds menu items. + foreach ($search_pages as $search_page) { + // Validate the environemnt ID in case of import or missed deletion. + $environment = solrsearch_environment_load($search_page['env_id']); + if (!$environment) { + continue; + } + + // Parses search path into it's various parts, builds menu items dependent + // on whether %keys is in the path. + $parts = explode('/', $search_page['search_path']); + $keys_pos = count($parts); + // Tests whether we are simulating a core search tab. + $core_solr_search = ($parts[0] == 'search'); + $position = array_search('%', $parts); + $page_title = isset($search_page['page_title']) ? $search_page['page_title'] : 'Search Results'; + + + // Replace possible tokens [term:tid], [node:nid], [user:uid] with their + // menu-specific variant + $items[$search_page['search_path']] = array( + 'title' => $page_title, + 'page callback' => 'solrsearch_search_custom_page', + 'page arguments' => array($search_page['page_id'], '', $position), + 'access arguments' => array('search content'), + 'type' => ($core_solr_search) ? MENU_LOCAL_TASK : MENU_SUGGESTED_ITEM, + 'file' => 'solrsearch_search.pages.inc', + 'file path' => drupal_get_path('module', 'solrsearch_search'), + ); + + // Not using menu tail because of inflexibility with clean urls + $items[$search_page['search_path'] . '/%'] = array( + 'title' => $page_title, + 'page callback' => 'solrsearch_search_custom_page', + 'page arguments' => array($search_page['page_id'], $keys_pos, $position), + 'access arguments' => array('search content'), + 'type' => !($core_solr_search) ? MENU_CALLBACK : MENU_LOCAL_TASK, + 'file' => 'solrsearch_search.pages.inc', + 'file path' => drupal_get_path('module', 'solrsearch_search'), + ); + + // If title has a certain callback for the selected type we use it + $search_type_id = !empty($search_page['settings']['solrsearch_search_search_type']) ? $search_page['settings']['solrsearch_search_search_type'] : FALSE; + $search_type = !empty($search_types[$search_type_id]) ? $search_types[$search_type_id] : FALSE; + + if ($search_type && !empty($position)) { + $title_callback = $search_type['title callback']; + $items[$search_page['search_path']]['title callback'] = $title_callback; + $items[$search_page['search_path']]['title arguments'] = array($search_page['page_id'], $position); + $items[$search_page['search_path'] . '/%']['title callback'] = $title_callback; + $items[$search_page['search_path'] . '/%']['title arguments'] = array($search_page['page_id'], $position); + } + // If we have additional searches in the search/* path + if ($core_solr_search) { + $items[$search_page['search_path'] . '/%']['tab_root'] = 'search/' . $default_info['path'] . '/%'; + $items[$search_page['search_path'] . '/%']['tab_parent'] = 'search/' . $default_info['path']; + } + } +} + +/** + * Function that loads all the search types + * + * @return array $search_types + */ +function solrsearch_search_load_all_search_types() { + $search_types = &drupal_static(__FUNCTION__); + + if (isset($search_types)) { + return $search_types; + } + // Use cache_get to avoid DB when using memcache, etc. + $cache = cache_get('solrsearch_search:search_types', 'cache_solrsearch'); + if (isset($cache->data)) { + $search_types = $cache->data; + } + else { + $search_types = array( + 'bundle' => array( + 'name' => solrsearch_field_name_map('bundle'), + 'default menu' => 'search/type/%', + 'title callback' => 'solrsearch_search_get_value_title', + ), + ); + drupal_alter('solrsearch_search_types', $search_types); + cache_set('solrsearch_search:search_types', $search_types, 'cache_solrsearch'); + } + return $search_types; +} + + + +/** + * Used as a callback function to generate a title for a node/page depending + * on the input in the configuration screen + * @param integer $search_page_id + * @param integer $value + * @return String + */ +/* +function solrsearch_search_get_value_title($search_page_id = NULL, $value = NULL) { + $page_title = 'Search results for %value'; + if (isset($value) && isset($search_page_id)) { + $search_page = solrsearch_search_page_load($search_page_id); + $page_title = str_replace('%value', '!value', $search_page['page_title']); + $title = $value; + } + return t($page_title, array('!value' => $title)); +} +*/ + +/** + * Get or set the default search page id for the current page. + */ +function solrsearch_search_default_search_page($page_id = NULL) { + $default_page_id = &drupal_static(__FUNCTION__, NULL); + + if (isset($page_id)) { + $default_page_id = $page_id; + } + if (empty($default_page_id)) { + $default_page_id = variable_get('solrsearch_search_default_search_page', 'core_solr_search'); + } + return $default_page_id; +} + +/** + * Implements hook_solrsearch_default_environment() + * + * Make sure the core search page is using the default environment. + */ +function solrsearch_search_solrsearch_default_environment($env_id, $old_env_id) { + $page = solrsearch_search_page_load('core_solr_search'); + if ($page && $page['env_id'] != $env_id) { + $page['env_id'] = $env_id; + solrsearch_search_page_save($page); + } +} + +/** + * Load a search page + * @param string $page_id + * @return array + */ +function solrsearch_search_page_load($page_id) { + //dpm(" solrsearch_search_page_load"); + + $search_pages = solrsearch_search_load_all_search_pages(); + //dpm($search_pages); + if (!empty($search_pages[$page_id])) { + return $search_pages[$page_id]; + } + return FALSE; +} + +function solrsearch_search_page_save($search_page) { + if (!empty($search_page)) { + db_merge('solrsearch_search_page') + ->key(array('page_id' => $search_page['page_id'])) + ->fields(array( + 'label' => $search_page['label'], + 'page_id' => $search_page['page_id'], + 'description' => $search_page['description'], + 'env_id' => $search_page['env_id'], + 'search_path' => $search_page['search_path'], + 'page_title' => $search_page['page_title'], + 'settings' => serialize($search_page['settings']), + )) + ->execute(); + } +} + + /** + * Function that clones a search page + * + * @param $page_id + * The page identifier it needs to clone. + * + */ +function solrsearch_search_page_clone($page_id) { + $search_page = solrsearch_search_page_load($page_id); + // Get all search_pages + $search_pages = solrsearch_search_load_all_search_pages(); + // Create an unique ID + $new_search_page_id = solrsearch_create_unique_id($search_pages, $search_page['page_id']); + // Set this id to the new search page + $search_page['page_id'] = $new_search_page_id; + $search_page['label'] = $search_page['label'] . ' [cloned]'; + // All cloned search pages should be removable + if (isset($search_page['settings']['solrsearch_search_not_removable'])) { + unset($search_page['settings']['solrsearch_search_not_removable']); + } + // Save our new search page in the database + solrsearch_search_page_save($search_page); +} + +/** + * Implements hook_block_info(). + */ +function solrsearch_search_block_info() { + // Get all of the moreLikeThis blocks that the user has created + $blocks = solrsearch_search_load_all_mlt_blocks(); + foreach ($blocks as $delta => $settings) { + $blocks[$delta] += array('info' => t('Solr Search recommendations: !name', array('!name' => $settings['name'])) , 'cache' => DRUPAL_CACHE_PER_PAGE); + } + // Add the sort block. + $blocks['sort'] = array( + 'info' => t('Solr Search Core: Sorting'), + 'cache' => DRUPAL_CACHE_CUSTOM, + ); + + $blocks['solrsearch'] = array( + 'info' => t('Solr Search Core: Search'), + 'cache' => DRUPAL_CACHE_CUSTOM, + ); + + + $blocks['solrsearch_author'] = array( + 'info' => t('Solr Search Core: Search - author'), + 'cache' => DRUPAL_CACHE_CUSTOM, + ); + + $blocks['solrsearch_title'] = array( + 'info' => t('Solr Search Core: Search - title'), + 'cache' => DRUPAL_CACHE_CUSTOM, + ); + + + + return $blocks; +} + +/** + * Implements hook_block_view(). + */ +function solrsearch_search_block_view($delta = '') { + + if ($delta == 'sort') { + $environments = solrsearch_load_all_environments(); + foreach ($environments as $env_id => $environment) { + if (solrsearch_has_searched($env_id) && !solrsearch_suppress_blocks($env_id) && $delta == 'sort') { + $response = NULL; + $query = solrsearch_current_query($env_id); + $solrsort = NULL; + if ($query) { + // Get the query and response. Without these no blocks make sense. + $response = solrsearch_static_response_cache($query->getSearcher()); + } + + // If there are less than two results, do not return the sort block + if (empty($response) || ($response->response->numFound < 2)) { + return NULL; + } + + // Check if we have to return a cached version of this block + if ($query) { + // Get the URI without any query parameter. + $uri = parse_url(request_uri()); + // Get the current sort as an array. + $solrsort = $query->getSolrsort(); + $cache_id = $uri['path'] . ':' . implode(':', $solrsort); + // Do we have something in cache ? + if ($cache = cache_get($cache_id, 'cache_block')) { + $block = $cache->data; + return $block; + } + } + + $sorts = $query->getAvailableSorts(); + $sort_links = array(); + $path = $query->getPath(); + $new_query = clone $query; + $toggle = array('asc' => 'desc', 'desc' => 'asc'); + foreach ($sorts as $name => $sort) { + $active = $solrsort['#name'] == $name; + if ($name == 'score') { + $direction = ''; + $new_direction = 'desc'; // We only sort by descending score. + } + elseif ($active) { + $direction = $toggle[$solrsort['#direction']]; + $new_direction = $toggle[$solrsort['#direction']]; + } + else { + $direction = ''; + $new_direction = $sort['default']; + } + $new_query->setSolrsort($name, $new_direction); + $sort_links[$name] = array( + 'text' => $sort['title'], + 'path' => $path, + 'options' => array('query' => $new_query->getSolrsortUrlQuery()), + 'active' => $active, + 'direction' => $direction, + ); + } + foreach ($sort_links as $name => $link) { + $themed_links[$name] = theme('solrsearch_sort_link', $link); + } + $block = array( + 'subject' => t('Sort by'), + 'content' => theme('solrsearch_sort_list', array('items' => $themed_links)) + ); + // Cache the block + cache_set($cache_id, $block, 'cache_block'); + return $block; + } + } + } + elseif ($delta == 'solrsearch') { + + return solrsearch_search_block(); + } + + elseif($delta == 'solrsearch_author'){ + + return solrsearch_search_author_block(); + } + + elseif($delta == 'solrsearch_title'){ + + return solrsearch_search_title_block(); + } + + + + elseif (($node = menu_get_object()) && (!arg(2) || arg(2) == 'view')) { + $suggestions = array(); + // Determine whether the user can view the current node. Probably not necessary. + $block = solrsearch_search_mlt_block_load($delta); + if ($block && node_access('view', $node)) { + // Get our specific environment for the MLT block + $env_id = (!empty($block['mlt_env_id'])) ? $block['mlt_env_id'] : ''; + try { + $solr = solrsearch_get_solr($env_id); + $context['search_type'] = 'solrsearch_search_mlt'; + $context['block_id'] = $delta; + $docs = solrsearch_search_mlt_suggestions($block, solrsearch_document_id($node->nid), $solr, $context); + if (!empty($docs)) { + $suggestions['subject'] = check_plain($block['name']); + $suggestions['content'] = array( + '#theme' => 'solrsearch_search_mlt_recommendation_block', + '#docs' => $docs, + '#delta' => $delta + ); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } + } + return $suggestions; + } +} + +/** + * Implements hook_form_[form_id]_alter(). + */ +function solrsearch_search_form_block_admin_display_form_alter(&$form) { + foreach ($form['blocks'] as $key => $block) { + if ((strpos($key, "solrsearch_search_mlt-") === 0) && $block['module']['#value'] == 'solrsearch_search') { + $form['blocks'][$key]['delete'] = array( + '#type' => 'link', + '#title' => 'delete', + '#href' => 'admin/config/search/solrsearch/search-pages/block/' . $block['delta']['#value'] . '/delete', + ); + } + } +} + +/** + * Implements hook_block_configure(). + */ +function solrsearch_search_block_configure($delta = '') { + if ($delta != 'sort') { + require_once(drupal_get_path('module', 'solrsearch') . '/solrsearch_search.admin.inc'); + return solrsearch_search_mlt_block_form($delta); + } +} + +/** + * Implements hook_block_save(). + */ +function solrsearch_search_block_save($delta = '', $edit = array()) { + if ($delta != 'sort') { + require_once(drupal_get_path('module', 'solrsearch') . '/solrsearch_search.admin.inc'); + solrsearch_search_mlt_save_block($edit, $delta); + } +} + + +/** + * Return all the saved search pages + * @return array $search_pages + * Array of all search pages + */ +function solrsearch_search_load_all_search_pages() { + $search_pages = &drupal_static(__FUNCTION__, array()); + //dpm("solrsearch_search_load_all_search_pages"); + if (!empty($search_pages)) { + return $search_pages; + } + + // If ctools module is enabled, add search pages from code, e.g. from a + // feature module. + if (module_exists('ctools')) { + ctools_include('export'); + $defaults = ctools_export_load_object('solrsearch_search_page', 'all'); + foreach ($defaults as $page_id => $default) { + $search_pages[$page_id] = (array) $default; + } + } + + // Get all search_pages and their id + $search_pages_db = db_query('SELECT * FROM {solrsearch_search_page}')->fetchAllAssoc('page_id', PDO::FETCH_ASSOC); + //$search_pages_db = db_query('SELECT * FROM {apachesolr_search_page}')->fetchAllAssoc('page_id', PDO::FETCH_ASSOC); + //dpm($search_pages); + //dpm("QUERY"); + $search_pages = $search_pages + $search_pages_db; + + // Ensure that the core search page uses the default environment. In some + // instances, for example when unit testing, this search page isn't defined. + if (isset($search_pages['core_solr_search'])) { + $search_pages['core_solr_search']['env_id'] = solrsearch_default_environment(); + } + + // convert settings to an array + foreach ($search_pages as $id => $search_page) { + if (is_string($search_pages[$id]['settings'])) { + $search_pages[$id]['settings'] = unserialize($search_pages[$id]['settings']); + // Prevent false outcomes for the following search page + $settings = 0; + } + } + //dpm($search_pages); + //dpm("QUERY II"); + return $search_pages; +} + +function solrsearch_search_load_all_mlt_blocks() { + $search_blocks = variable_get('solrsearch_search_mlt_blocks', array()); + return $search_blocks; +} + +function solrsearch_search_mlt_block_load($block_id) { + $search_blocks = variable_get('solrsearch_search_mlt_blocks', array()); + return isset($search_blocks[$block_id]) ? $search_blocks[$block_id] : FALSE; +} + +/** + * Performs a moreLikeThis query using the settings and retrieves documents. + * + * @param $settings + * An array of settings. + * @param $id + * The Solr ID of the document for which you want related content. + * For a node that is solrsearch_document_id($node->nid) + * @param $solr + * The solr environment you want to query against + * + * @return An array of response documents, or NULL + */ +function solrsearch_search_mlt_suggestions($settings, $id, $solr = NULL, $context = array()) { + + try { + $fields = array( + 'mlt_mintf' => 'mlt.mintf', + 'mlt_mindf' => 'mlt.mindf', + 'mlt_minwl' => 'mlt.minwl', + 'mlt_maxwl' => 'mlt.maxwl', + 'mlt_maxqt' => 'mlt.maxqt', + 'mlt_boost' => 'mlt.boost', + 'mlt_qf' => 'mlt.qf', + ); + $params = array( + 'q' => 'id:' . $id, + 'qt' => 'mlt', + 'fl' => array('entity_id', 'entity_type', 'label', 'path', 'url'), + 'mlt.fl' => $settings['mlt_fl'], + 'start' => 0, + 'rows' => $settings['num_results'], + ); + // We can optionally specify a Solr object. + $query = solrsearch_drupal_query('solrsearch_mlt', $params, '', '', $solr, $context); + + foreach ($fields as $form_key => $name) { + if (!empty($settings[$form_key])) { + $query->addParam($name, $settings[$form_key]); + } + } + + $type_filters = array(); + if (is_array($settings['mlt_type_filters']) && !empty($settings['mlt_type_filters'])) { + $query->addFilter('bundle', '(' . implode(' OR ', $settings['mlt_type_filters']) . ') '); + } + + if ($custom_filters = $settings['mlt_custom_filters']) { + // @todo - fix the settings form to take a comma-delimited set of filters. + $query->addFilter('', $custom_filters); + } + + // This hook allows modules to modify the query object. + drupal_alter('solrsearch_query', $query); + if ($query->abort_search) { + return NULL; + } + + $response = $query->search(); + + if (isset($response->response->docs)) { + return (array) $response->response->docs; + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +function theme_solrsearch_search_mlt_recommendation_block($vars) { + $docs = $vars['docs']; + $links = array(); + foreach ($docs as $result) { + // Suitable for single-site mode. Label is already safe. + $links[] = l($result->label, $result->path, array('html' => TRUE)); + } + $links = array( + '#theme' => 'item_list', + '#items' => $links, + ); + return render($links); +} + +/** + * Implements hook_search_info(). + */ +function UNUSED_solrsearch_search_search_info() { + // Load our core search page + // This core search page is assumed to always be there. It cannot be deleted. + $search_page = solrsearch_search_page_load('core_solr_search'); + + // This can happen during install, or if the DB was manually changed. + if (empty($search_page)) { + $search_page = array(); + $search_page['page_title'] = 'echo'; + $search_page['search_path'] = 'solrsearch/site'; + } + + return array( + 'title' => $search_page['page_title'], + 'path' => str_replace('solrsearch/', '', $search_page['search_path']), + 'conditions_callback' => variable_get('solrsearch_search_conditions_callback', 'solrsearch_search_conditions'), + ); +} + +/** + * Implements hook_search_reset(). + */ +function UNUSED_solrsearch_search_search_reset() { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $env_id = solrsearch_default_environment(); + solrsearch_index_mark_for_reindex($env_id); +} + +/** + * Implements hook_search_status(). + */ +function UNUSED_solrsearch_search_search_status() { + module_load_include('inc', 'solrsearch', 'solrsearch.index'); + $env_id = solrsearch_default_environment(); + return solrsearch_index_status($env_id); +} + +/** + * Implements hook_search_execute(). + * @param $keys + * The keys that are available after the path that is defined in + * hook_search_info + * @param $conditions + * Conditions that are coming from solrsearch_search_conditions + */ +function solrsearch_search_search_execute($keys = NULL, $conditions = NULL) { + + $search_page = solrsearch_search_page_load('core_solr_search'); + $results = solrsearch_search_search_results($keys, $conditions, $search_page); + return $results; +} + +/** + * Implementation of a search_view() conditions callback. + */ +function solrsearch_search_conditions() { + //get default conditions from the core_solr_search + $search_page = solrsearch_search_page_load('core_solr_search'); + $conditions = solrsearch_search_conditions_default($search_page); + return $conditions; +} + +/** + * Implements hook_search_page(). + * @param $results + * The results that came from apache solr + */ +function solrsearch_search_search_page($results) { + $search_page = solrsearch_search_page_load('core_solr_search'); + $build = solrsearch_search_search_page_custom($results, $search_page); + return $build; +} + +/** + * Mimics solrsearch_search_search_page() but is used for custom search pages + * We prefer to keep them seperate so we are not dependent from core search + * when someone tries to disable the core search + * @param $results + * The results that came from apache solr + * @param $build + * the build array from where this function was called. Good to append output + * to the build array + * @param $search_page + * the search page that is requesting an output + */ +function solrsearch_search_search_page_custom($results, $search_page, $build = array()) { + if (!empty($search_page['settings']['solrsearch_search_spellcheck'])) { + // Retrieve suggestion + $suggestions = solrsearch_search_get_search_suggestions($search_page['env_id']); + if ($search_page && !empty($suggestions)) { + $build['suggestions'] = array( + '#theme' => 'solrsearch_search_suggestions', + '#links' => array(l($suggestions[0], $search_page['search_path'] . '/' . $suggestions[0])), + ); + } + } + // Retrieve expected results from searching + if (!empty($results['solrsearch_search_browse'])) { + // Show facet browsing blocks. + $build['search_results'] = solrsearch_search_page_browse($results['solrsearch_search_browse'], $search_page['env_id']); + } + elseif ($results) { + + $build['search_results'] = array( + '#theme' => 'solrsearch_results', + '#results' => $results, + '#module' => 'solrsearch_search', + '#search_page' => $search_page, + ); + } + else { + // Give the user some custom help text. + $build['search_results'] = array('#markup' => theme('solrsearch_search_noresults')); + } + + // Allows modules to alter the render array before returning. + drupal_alter('solrsearch_search_page', $build, $search_page); + + return $build; +} + +/** + * Executes search depending on the conditions given. + * See solrsearch_search.pages.inc for another use of this function + */ +function solrsearch_search_search_results($keys = NULL, $conditions = NULL, $search_page = NULL) { + + $params = array(); + $results = array(); + // Process the search form. Note that if there is $_POST data, + // search_form_submit() will cause a redirect to search/[module path]/[keys], + // which will get us back to this page callback. In other words, the search + // form submits with POST but redirects to GET. This way we can keep + // the search query URL clean as a whistle. + if (empty($_POST['form_id']) + || ($_POST['form_id'] != 'solrsearch_search_custom_page_search_form') + && ($_POST['form_id'] != 'search_form') + && ($_POST['form_id'] != 'search_block_form') ) { + + // Check input variables + if (empty($search_page)) { + $search_page = solrsearch_search_page_load('core_solr_search'); + // Verify if it actually loaded + if (empty($search_page)) { + // Something must have been really messed up. + solrsearch_failure(t('Solr search'), $keys); + return array(); + } + } + if (empty($conditions)) { + $conditions = solrsearch_search_conditions_default($search_page); + } + + // Sort options from the conditions array. + // @see solrsearch_search_conditions_default() + // See This condition callback to find out how. + $solrsort = isset($conditions['solrsearch_search_sort']) ? $conditions['solrsearch_search_sort'] : ''; + // What to do when we have an initial empty search + $empty_search_behavior = isset($search_page['settings']['solrsearch_search_browse']) ? $search_page['settings']['solrsearch_search_browse'] : ''; + + try { + + $solr = solrsearch_get_solr($search_page['env_id']); + // Default parameters + $params['fq'] = isset($conditions['fq']) ? $conditions['fq'] : array(); + $params['rows'] = $search_page['settings']['solrsearch_search_per_page']; + + if (empty($search_page['settings']['solrsearch_search_spellcheck'])) { + // Spellcheck needs to have a string as false/true + $params['spellcheck'] = 'false'; + } + else { + $params['spellcheck'] = 'true'; + } + + // Empty text Behavior + if (!$keys && !isset($conditions['f']) && ($empty_search_behavior == 'browse' || $empty_search_behavior == 'blocks')) { + // Pass empty search behavior as string on to solrsearch_search_search_page() + // Hardcoded solrsearch name since we rely on this for the facets + + $context['page_id'] = $search_page['page_id']; + $context['search_type'] = 'solrsearch_search_browse'; + solrsearch_search_run_empty('solrsearch', $params, $search_page['search_path'], $solr, $context); + $results['solrsearch_search_browse'] = $empty_search_behavior; + + if ($empty_search_behavior == 'browse') { + // Hide sidebar blocks for content-area browsing instead. + solrsearch_suppress_blocks($search_page['env_id'], TRUE); + } + } + // Full text behavior. Triggers with a text search or a facet + elseif (($keys || isset($conditions['f'])) || ($empty_search_behavior == 'results')) { + + $params['q'] = $keys; + // Hardcoded solrsearch name since we rely on this for the facets + $context['page_id'] = $search_page['page_id']; + $context['search_type'] = 'solrsearch_search_results'; + $results = solrsearch_search_run('solrsearch', $params, $solrsort, $search_page['search_path'], pager_find_page(), $solr, $context); + } + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + solrsearch_failure(t('Solr search'), $keys); + } + } + return $results; +} + +function solrsearch_search_conditions_default($search_page) { + $conditions = array(); + $search_type = isset($search_page['settings']['solrsearch_search_search_type']) ? $search_page['settings']['solrsearch_search_search_type'] : ''; + $allow_user_input = isset($search_page['settings']['solrsearch_search_allow_user_input']) ? $search_page['settings']['solrsearch_search_allow_user_input'] : FALSE; + $path_replacer = isset($search_page['settings']['solrsearch_search_path_replacer']) ? $search_page['settings']['solrsearch_search_path_replacer'] : ''; + $set_custom_filter = isset($search_page['settings']['solrsearch_search_custom_enable']) ? $search_page['settings']['solrsearch_search_custom_enable'] : ''; + $search_page_fq = !empty($search_page['settings']['fq']) ? $search_page['settings']['fq'] : ''; + + + + + + $conditions['fq'] = array(); + // We only allow this to happen if the search page explicitely allows it + if ($allow_user_input) { + // Get the filterQueries from the url + if (!empty($_GET['fq']) && is_array($_GET['fq'])) { + // Reset the array so that we have one level lower to go through + $conditions['fq'] = $_GET['fq']; + } + foreach($conditions['fq'] as $condition_id => $condition) { + // If the user input does not pass our validation we do not allow + // it to query solr + $test_query = solrsearch_drupal_subquery('Test'); + if (!$test_query->validFilterValue($condition)) { + unset($conditions['fq'][$condition_id]); + } + } + } + + // Custom filters added in search pages + if (!empty($search_page_fq) && !empty($set_custom_filter)) { + if (!empty($path_replacer)) { + // If the manual filter has a % in it, replace it with $value + $conditions['fq'][] = str_replace('%', $path_replacer, $search_page_fq); + } + else { + // Put the complete filter in the filter query + $conditions['fq'][] = $search_page_fq; + } + } + + // Search type filters (such as taxonomy) + if (!empty($path_replacer) && !empty($search_type) && $search_type != 'custom') { + $conditions['fq'][] = $search_type . ':' . $path_replacer; + } + + // We may also have filters added by facet API module. The 'f' + // is determined by variable FacetapiUrlProcessor::$filterKey. Hard + // coded here to avoid extra class loading. + if (!empty($_GET['f']) && is_array($_GET['f'])) { + if (module_exists('facetapi')) { + $conditions['f'] = $_GET['f']; + } + } + // Add the sort from the page to our conditions + $sort = isset($_GET['solrsort']) ? $_GET['solrsort'] : ''; + $conditions['solrsearch_search_sort'] = $sort; + + + $conditions['fq'][] = "doc-type:indexMeta"; + return $conditions; +} + +/** + * Handle browse results for empty searches. + */ +function solrsearch_search_page_browse($empty_search_behavior, $env_id) { + $build = array(); + //dpm("solrsearch_search_page_browse"); + //dpm($empty_search_behavior); + + // Switch in case we come up with new flags. + switch ($empty_search_behavior) { + case 'browse': + if (module_exists('facetapi') && $query = solrsearch_current_query($env_id)) { + module_load_include('inc', 'facetapi', 'facetapi.block'); + // Get facet render elements. + $searcher = $query->getSearcher(); + $elements = facetapi_build_realm($searcher, 'block'); + $build = array(); + //dpm($searcher); + //dpm($elements); + foreach (element_children($elements) as $key) { + $delta = "facetapi_{$key}"; + // @todo: order/filter these pseudo-blocks according to block.module weight, visibility (see 7.x-1beta4) + $block = new stdClass(); + $block->visibility = TRUE; + $block->enabled = TRUE; + $block->module = 'facetapi'; + $block->subject = theme('facetapi_title', array('title' => $elements[$key]['#title'])); + $build[$delta] = $elements[$key]; + $block->region = NULL; + $block->delta = 'solrsearch-' . $key; + // @todo: the final themed block's div id attribute does not coincide with "real" block's id (see facetapi_get_delta_map()) + $build[$delta]['#block'] = $block; + $build[$delta]['#theme_wrappers'][] = 'block'; + $build['#sorted'] = TRUE; + } + $build['#theme_wrappers'][] = 'solrsearch_search_browse_blocks'; + } + break; + } + return $build; +} + +/** + * Shows a groups of blocks for starting a search from a filter. + */ +function theme_solrsearch_search_browse_blocks($vars) { + $result = ''; + if ($vars['content']['#children']) { + $result .= "
\n

" . t('Browse available categories') . "

\n"; + $result .= '

' . t('Pick a category to launch a search.') . "

\n"; + $result .= $vars['content']['#children'] . "\n
\n"; + } + + return $result; +} + +/** + * Execute a search with zero results rows so as to populate facets. + */ +function solrsearch_search_run_empty($name, array $params = array(), $base_path = '', $solr = NULL, $context = array()) { + $query = solrsearch_drupal_query($name, $params, '', $base_path, $solr, $context); + $query->addParam('rows', '0'); + $solr_id = $query->solr('getId'); + list($final_query, $response) = solrsearch_do_query($query); + solrsearch_has_searched($solr_id, TRUE); +} + +/** + * Execute a search results based on keyword, filter, and sort strings. + * + * @param $name + * @param $params + * Array - 'q' is the keywords to search. + * @param $solrsort + * @param $base_path + * For constructing filter and sort links. Leave empty unless the links need to point somewhere + * other than the base path of the current request. + * @param integer $page + * For pagination. + * @param DrupalApacheSolrServiceInterface $solr + * The solr server resource to execute the search on. + * + * @return stdClass $response + * + * @throws Exception + */ +function solrsearch_search_run($name, array $params = array(), $solrsort = '', $base_path = '', $page = 0, DrupalApacheSolrServiceInterface $solr = NULL, $context = array()) { + // Merge the default params into the params sent in. + + $params += solrsearch_search_basic_params(); + // This is the object that knows about the query coming from the user. + $query = solrsearch_drupal_query($name, $params, $solrsort, $base_path, $solr, $context); + + if ($query->getParam('q')) { + solrsearch_search_add_spellcheck_params($query); + } + + // Add the paging parameters + $query->page = $page; + + solrsearch_search_add_boost_params($query); + if ($query->getParam('q')) { + solrsearch_search_highlighting_params($query); + if (!$query->getParam('hl.fl')) { + $qf = array(); + foreach ($query->getParam('qf') as $field) { + // Truncate off any boost so we get the simple field name. + $parts = explode('^', $field, 2); + $qf[$parts[0]] = TRUE; + } + foreach (array('content', 'ts_comments') as $field) { + if (isset($qf[$field])) { + $query->addParam('hl.fl', $field); + } + } + } + } + else { + // No highlighting, use the teaser as a snippet. + $query->addParam('fl', 'teaser'); + } + + + list($final_query, $response) = solrsearch_do_query($query); + + $env_id = $query->solr('getId'); + solrsearch_has_searched($env_id, TRUE); + $process_response_callback = solrsearch_environment_variable_get($env_id, 'process_response_callback', 'solrsearch_search_process_response'); + if (function_exists($process_response_callback)) { + return call_user_func($process_response_callback, $response, $final_query); + } + else { + return solrsearch_search_process_response($response, $final_query); + } +} + +function solrsearch_search_basic_params(DrupalSolrQueryInterface $query = NULL) { + $params = array( + 'fl' => array( + 'id', + 'entity_type', + 'author_c', + 'author', + 'title', + 'title_s', + 'keyword', + 'year', + 'IM_date', + 'IM_signature', + 'archive-path', + 'doc-type', + 'mpiwg-dri', + 'access-type', + ), + 'mm' => 1, + 'rows' => 10, + 'pf' => 'title^2.0 author^1.0', + 'ps' => 15, + 'hl' => 'true', + 'hl.fl' => 'title', + 'hl.snippets' => 3, + 'hl.mergeContigious' => 'true', + 'f.content.hl.alternateField' => 'teaser', + 'f.content.hl.maxAlternateFieldLength' => 256, + ); + if ($query) { + $query->addParams($params); + } + return $params; +} + +/** + * Add highlighting settings to the search params. + * + * These settings are set in solrconfig.xml. + * See the defaults there. + * If you wish to override them, you can via settings.php or drush + */ +function solrsearch_search_highlighting_params(DrupalSolrQueryInterface $query = NULL) { + $params['hl'] = variable_get('solrsearch_hl_active', NULL); + $params['hl.fragsize']= variable_get('solrsearch_hl_textsnippetlength', NULL); + $params['hl.simple.pre'] = variable_get('solrsearch_hl_pretag', NULL); + $params['hl.simple.post'] = variable_get('solrsearch_hl_posttag', NULL); + $params['hl.snippets'] = variable_get('solrsearch_hl_numsnippets', NULL); + // This should be an array of possible field names. + $params['hl.fl'] = variable_get('solrsearch_hl_fieldtohighlight', NULL); + $params = array_filter($params); + if ($query) { + $query->addParams($params); + } + return $params; +} + +function solrsearch_search_add_spellcheck_params(DrupalSolrQueryInterface $query) { + $params = array(); + + // Add new parameter to the search request + $params['spellcheck.q'] = $query->getParam('q'); + $params['spellcheck'] = 'true'; + $query->addParams($params); +} + +function solrsearch_search_add_boost_params(DrupalSolrQueryInterface $query) { + $env_id = $query->solr('getId'); + $params = array(); + + $defaults = array( + 'content' => '1.0', + 'ts_comments' => '0.5', + 'tos_content_extra' => '0.1', + 'label' => '5.0', + 'tos_name' => '3.0', + 'taxonomy_names' => '2.0', + 'tags_h1' => '5.0', + 'tags_h2_h3' => '3.0', + 'tags_h4_h5_h6' => '2.0', + 'tags_inline' => '1.0', + 'tags_a' => '0', + ); + $qf = solrsearch_environment_variable_get($env_id, 'field_bias', $defaults); + $fields = $query->solr('getFields'); + if ($qf && $fields) { + foreach ($fields as $field_name => $field) { + if (!empty($qf[$field_name])) { + $prefix = substr($field_name, 0, 3); + if ($field_name == 'content' || $prefix == 'IM_' || $prefix == 'TT_') { + // Normed fields tend to have a lower score. Multiplying by 40 is + // a rough attempt to bring the score in line with fields that are + // not normed. + $qf[$field_name] *= 40.0; + } + $params['qf'][$field_name] = $field_name . '^' . $qf[$field_name]; + } + } + } + + $date_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_date_boost', '0:0'); + $comment_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_comment_boost', '0:0'); + $changed_settings = solrsearch_environment_variable_get($env_id, 'solrsearch_search_changed_boost', '0:0'); + $sticky_boost = solrsearch_environment_variable_get($env_id, 'solrsearch_search_sticky_boost', '0'); + $promote_boost = solrsearch_environment_variable_get($env_id, 'solrsearch_search_promote_boost', '0'); + // For the boost functions for the created timestamp, etc we use the + // standard date-biasing function, as suggested (but steeper) at + // http://wiki.apache.org/solr/SolrRelevancyFAQ#How_can_I_boost_the_score_of_newer_documents + // ms() returns the time difference in ms between now and the date + // The function is thus: $ab/(ms(NOW,date)*$steepness + $ab). + list($date_steepness, $date_boost) = explode(':', $date_settings); + if ($date_boost) { + $ab = 4 / $date_steepness; + $params['bf'][] = "recip(ms(NOW,ds_created),3.16e-11,$ab,$ab)^$date_boost"; + } + // Boost on comment count. + list($comment_steepness, $comment_boost) = explode(':', $comment_settings); + if ($comment_boost) { + $params['bf'][] = "recip(div(1,max(is_comment_count,1)),$comment_steepness,10,10)^$comment_boost"; + } + // Boost for a more recent comment or node edit. + list($changed_steepness, $changed_boost) = explode(':', $changed_settings); + if ($changed_boost) { + $ab = 4 / $changed_steepness; + $params['bf'][] = "recip(ms(NOW,ds_created),3.16e-11,$ab,$ab)^$changed_boost"; + } + // Boost for nodes with sticky bit set. + if ($sticky_boost) { + $params['bq'][] = "bs_sticky:true^$sticky_boost"; + } + // Boost for nodes with promoted bit set. + if ($promote_boost) { + $params['bq'][] = "bs_promote:true^$promote_boost"; + } + // Modify the weight of results according to the node types. + $type_boosts = solrsearch_environment_variable_get($env_id, 'solrsearch_search_type_boosts', array()); + if (!empty($type_boosts)) { + foreach ($type_boosts as $type => $boost) { + // Only add a param if the boost is != 0 (i.e. > "Normal"). + if ($boost) { + $params['bq'][] = "bundle:$type^$boost"; + } + } + } + $query->addParams($params); +} + +function solrsearch_search_process_response($response, DrupalSolrQueryInterface $query) { + $results = array(); + // We default to getting snippets from the body content and comments. + $hl_fl = $query->getParam('hl.fl'); + if (!$hl_fl) { + //TODO: make highlighitn configurabel + $hl_fl = array('title','author'); + } + $total = $response->response->numFound; + + + pager_default_initialize($total, $query->getParam('rows')); + if ($total > 0) { + $fl = $query->getParam('fl'); + // 'id' and 'entity_type' are the only required fields in the schema, and + // 'score' is generated by solr. + + + foreach ($response->response->docs as $doc) { + $extra = array(); + // Allow modules to alter each document and its extra information. + drupal_alter('solrsearch_search_result', $doc, $extra, $query); + + // Start with an empty snippets array. + $snippets = array(); + + //TODO mappe das irgendwo allgemein? + $doc->id=$doc->{'archive-path'}; + + // Find the nicest available snippet. + foreach ($hl_fl as $hl_param) { + if (isset($response->highlighting->{$doc->id}->$hl_param)) { + // Merge arrays preserving keys. + foreach ($response->highlighting->{$doc->id}->$hl_param as $value) { + $snippets[$hl_param][] = $value; + } + } + } + // If there's no snippet at this point, add the teaser. + if (!$snippets) { + if (isset($doc->teaser)) { + $snippets[] = truncate_utf8($doc->teaser, 256, TRUE); + } + } + + $hook = 'solrsearch_search_snippets__' . $doc->{'doc-type'}; + /*$bundle = !empty($doc->bundle) ? $doc->bundle : NULL; + if ($bundle) { + $hook .= '__' . $bundle; + }*/ + $snippet = theme($hook, array('doc' => $doc, 'snippets' => $snippets)); + + if (!isset($doc->content)) { + $doc->content = $snippet; + } + + /* + // Normalize common dates so that we can use Drupal's normal date and + // time handling. + if (isset($doc->ds_created)) { + $doc->created = strtotime($doc->ds_created); + } + else { + $doc->created = NULL; + } + + if (isset($doc->ds_changed)) { + $doc->changed = strtotime($doc->ds_changed); + } + else { + $doc->changed = NULL; + } + + if (isset($doc->tos_name)) { + $doc->name = $doc->tos_name; + } + else { + $doc->name = NULL; + } + */ + // Set all expected fields from fl to NULL if they are missing so + // as to prevent Notice: Undefined property. + $fl = array_merge($fl, array('path', 'label', 'score')); + foreach ($fl as $field) { + if (!isset($doc->{$field})) { + $doc->{$field} = NULL; + } + } + + $fields = (array) $doc; + + // a path is not a requirement of entity (see entity_uri() ), so we check if we + // can show it and fallback to the main page of the site if we don't + // have it. + if (!isset($doc->url)) { + $path = ''; + } + else { + $path = $doc->url; + } + + $result = array( + + // template_preprocess_search_result() runs check_plain() on the title + // again. Decode to correct the display. + 'title' => htmlspecialchars_decode($doc->title[0], ENT_QUOTES), + 'author' => htmlspecialchars_decode($doc->author[0], ENT_QUOTES), + // These values are not required by the search module but are provided + // to give entity callbacks and themers more flexibility. + 'score' => $doc->score, + 'snippets' => $snippets, + 'snippet' => $snippet, + 'fields' => $fields, + 'doc-type' => $doc->{'doc-type'}, + 'mpiwg-dri' => $doc->{'mpiwg-dri'}, + 'access-type'=> $doc->{'access-type'}, + 'year' => $doc->{'year'}, + ); + + if (isset($doc->{'IM_date'})){ + $result['date']=$doc->{'IM_date'}; + } + if (isset($doc->{'IM_signature'})){ + $result['signature']=$doc->{'IM_signature'}; + } + + + // Call entity-type-specific callbacks for extra handling. + /*$function = solrsearch_entity_get_callback($doc->{'doc-type'}, 'result callback', ''); + if (is_callable($function)) { + $function($doc, $result, $extra); + } + */ + $result['extra'] = $extra; + + $results[] = $result; + } + } + // Hook to allow modifications of the retrieved results + foreach (module_implements('solrsearch_process_results') as $module) { + $function = $module . '_solrsearch_process_results'; + $function($results, $query); + } + return $results; +} + +/** + * Used as a callback function to generate a title for a node/page depending + * on the input in the configuration screen + * @param integer $search_page_id + * @param integer $value + * @return String + */ +function searchsolr_search_get_value_title($search_page_id = NULL, $value = NULL) { + $page_title = 'Search results for %value'; + if (isset($value) && isset($search_page_id)) { + $search_page = apachesolr_search_page_load($search_page_id); + $page_title = str_replace('%value', '!value', $search_page['page_title']); + $title = $value; + } + return t($page_title, array('!value' => $title)); +} + +/** + * Retrieve all of the suggestions that were given after a certain search + * @return array() + */ +function solrsearch_search_get_search_suggestions($env_id) { + $suggestions_output = array(); + if (solrsearch_has_searched($env_id)) { + $query = solrsearch_current_query($env_id); + $keyword = $query->getParam('q'); + $searcher = $query->getSearcher(); + $response = solrsearch_static_response_cache($searcher); + // Get spellchecker suggestions into an array. + if (!empty($response->spellcheck->suggestions)) { + $suggestions = get_object_vars($response->spellcheck->suggestions); + if ($suggestions) { + $replacements = array(); + // Get the original query and retrieve all words with suggestions. + foreach ($suggestions as $word => $value) { + $replacements[$word] = $value->suggestion[0]; + } + // Replace the keyword with the suggested keyword. + $suggested_keyword = strtr($keyword, $replacements); + // Show only if suggestion is different than current query. + if ($keyword != $suggested_keyword) { + $suggestions_output[] = $suggested_keyword; + } + } + } + } + return $suggestions_output; +} + +/** + * Implements hook_solrsearch_entity_info_alter(). + */ +function solrsearch_search_solrsearch_entity_info_alter(&$entity_info) { + // First set defaults so that we don't need to worry about NULL keys. + foreach (array_keys($entity_info) as $type) { + $entity_info[$type]['result callback'] = ''; + } + // Now set those values that we know. Other modules can do so + // for their own entities if they want. + $entity_info['node']['result callback'] = 'solrsearch_search_node_result'; +} + +/** + * Callback function for node search results. + * + * @param stdClass $doc + * The result document from Apache Solr. + * @param array $result + * The result array for this record to which to add. + */ +function solrsearch_search_node_result($doc, &$result, &$extra) { + $doc->uid = $doc->is_uid; + $result += array( + 'type' => node_type_get_name($doc->bundle), + 'user' => theme('username', array('account' => $doc)), + 'date' => isset($doc->changed) ? $doc->changed : 0, + 'node' => $doc, + 'uid' => $doc->is_uid, + ); + + if (isset($doc->is_comment_count)) { + $extra['comments'] = format_plural($doc->is_comment_count, '1 comment', '@count comments'); + } +} + +/** + * Returns whether a search page exists. + */ +function solrsearch_search_page_exists($search_page_id) { + return db_query('SELECT 1 FROM {solrsearch_search_page} WHERE page_id = :page_id', array(':page_id' => $search_page_id))->fetchField(); +} + +/** + * Template preprocess for solrsearch search results. + * + * We need to add additional entity/bundle-based templates + */ +function solrsearch_search_preprocess_search_result(&$variables) { + // If this search result is coming from our module, we want to improve the + // template potential to make life easier for themers. + + + if ($variables['module'] == 'solrsearch_search') { + $result = $variables['result']; + + //info in display should display the author. + $variables['info']=$result['author']; + if (!empty($result['entity_type'])) { + $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'] . '__' . $result['entity_type']; + if (!empty($result['bundle'])) { + $variables['theme_hook_suggestions'][] = 'search_result__' . $variables['module'] . '__' . $result['entity_type'] . '__' . $result['bundle']; + } + } + } +} + +function solrsearch_search_preprocess_search_results(&$variables) { + // Initialize variables + $env_id = NULL; + + // If this is a solr search, expose more data to themes to play with. + if ($variables['module'] == 'solrsearch_search') { + // Fetch our current query + if (!empty($variables['search_page']['env_id'])) { + $env_id = $variables['search_page']['env_id']; + } + $query = solrsearch_current_query($env_id); + + if ($query) { + $variables['query'] = $query; + $variables['response'] = solrsearch_static_response_cache($query->getSearcher()); + } + if (empty($variables['response'])) { + $variables['description'] = ''; + return NULL; + } + $total = $variables['response']->response->numFound; + $params = $variables['query']->getParams(); + + $variables['description'] = t('Showing items @start through @end of @total.', array( + '@start' => $params['start'] + 1, + '@end' => $params['start'] + $params['rows'] - 1, + '@total' => $total, + )); + // Redefine the pager if it was missing + pager_default_initialize($total, $params['rows']); + $variables['pager'] = theme('pager', array('tags' => NULL)); + + // Add template hints for environments + if (!empty($env_id)) { + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $env_id; + // Add template hints for search pages + if (!empty($variables['search_page']['page_id'])) { + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $variables['search_page']['page_id']; + // Add template hints for both + $variables['theme_hook_suggestions'][] = 'search_results__' . $variables['module'] . '__' . $env_id . '__' . $variables['search_page']['page_id']; + } + } + } +} + +/** + * Implements hook_solrsearch_environment_delete(). + */ +function solrsearch_search_solrsearch_environment_delete($server) { + db_update('solrsearch_search_page') + ->fields(array( + 'env_id' => '', + )) + ->condition('env_id', $server['env_id']) + ->execute(); + solrsearch_environment_variable_del($server['env_id'], 'solrsearch_search_show_facets'); + solrsearch_environment_variable_del($server['env_id'], 'solrsearch_search_facet_pages'); + menu_rebuild(); +} + +/*function solrsearch_search_form_search_block_form_alter(&$form, $form_state) { + if (variable_get('search_default_module') == 'solrsearch_search') { + $form['#submit'][] = 'solrsearch_search_form_search_submit'; + } +} +*/ + +/** + * Default theme function for spelling suggestions. + */ +function theme_solrsearch_search_suggestions($variables) { + $output = '
'; + $output .= '
' . t('Did you mean') . '
'; + foreach ((array) $variables['links'] as $link) { + $output .= '
' . $link . '
'; + } + $output .= '
'; + return $output; +} + +/** + * Added form submit function to retain filters. + * + * @see solrsearch_search_form_search_form_alter() + */ +function solrsearch_search_form_search_submit($form, &$form_state) { + $fv = $form_state['values']; + // Replace keys with their rawurlencoded value + if (isset($fv['search_block_form'])) { + $raw_keys = str_replace("/","%2f",$fv['search_block_form']); + $form_state['redirect'] = str_replace($fv['search_block_form'], $raw_keys, $form_state['redirect']); + } +} + + +/** + * submit function for the delete_index form. + * + */ +function solrsearch_search_build_spellcheck($form, &$form_state) { + try { + $solr = solrsearch_get_solr(); + $params['spellcheck'] = 'true'; + $params['spellcheck.build'] = 'true'; + $response = $solr->search('solr', 0, 0, $params); + } + catch (Exception $e) { + watchdog('Apache Solr', nl2br(check_plain($e->getMessage())), NULL, WATCHDOG_ERROR); + } +} + +/** + * Implements hook_form_[form_id]_alter(). + * + * Adds settings to show facet blocks on non-search pages. + */ +function solrsearch_search_form_facetapi_realm_settings_form_alter(&$form, &$form_state) { + + if ('solrsearch' == $form['#facetapi']['adapter']->getId() && 'block' == $form['#facetapi']['realm']['name']) { + // Gets the environment ID from the searcher, stores in #facetapi property. + $env_id = ltrim(strstr($form['#facetapi']['adapter']->getSearcher(), '@'), '@'); + + $show_facets = solrsearch_environment_variable_get($env_id, 'solrsearch_search_show_facets', 0); + $facet_pages = solrsearch_environment_variable_get($env_id, 'solrsearch_search_facet_pages', ''); + + $form['#facetapi']['env_id'] = $env_id; + + $form['solrsearch_search_show_facets'] = array( + '#type' => 'checkbox', + '#title' => t('Show facets on non-search pages.'), + '#default_value' => $show_facets, + '#weight' => '-10', + ); + + $form['solrsearch_search_facet_pages'] = array( + '#title' => t('Non-search paths'), + '#type' => 'textarea', + '#default_value' => $facet_pages, + '#weight' => '-10', + '#dependency' => array( + 'edit-solrsearch-search-show-facets' => array(1), + ), + ); + + $form['#submit'][] = 'solrsearch_search_facetapi_realm_settings_form_submit'; + } +} + +/** + * Form submission handler for facetapi_realm_settings_form(). + */ +function solrsearch_search_facetapi_realm_settings_form_submit(&$form, &$form_state) { + $env_id = $form['#facetapi']['env_id']; + + // Adds the settings to the array keyed by environment ID, saves variables. + $show_facets = $form_state['values']['solrsearch_search_show_facets']; + $facet_pages = $form_state['values']['solrsearch_search_facet_pages']; + if ($show_facets) { + solrsearch_environment_variable_set($env_id, 'solrsearch_search_show_facets', $show_facets); + } + else { + // Due to performance reasons, we delete it from the vars so that our init + // process can react on environments that hae it set and not unset. + // See solrsearch_search_init(). + solrsearch_environment_variable_del($env_id, 'solrsearch_search_show_facets'); + } + solrsearch_environment_variable_set($env_id, 'solrsearch_search_facet_pages', $facet_pages); +} + +/** + * Implements hook_theme(). + */ +function solrsearch_search_theme() { + return array( + /** + * Shows the facets in blocks in the search result area + */ + 'solrsearch_search_browse_blocks' => array( + 'render element' => 'content', + ), + /** + * Shows the search snippet + */ + 'solrsearch_search_snippets' => array( + 'variables' => array('doc' => NULL, 'snippets' => array()), + ), + /** + * Shows a message when the search does not return any result + */ + 'solrsearch_search_noresults' => array( + 'variables' => array(), + ), + /** + * Shows a list of suggestions + */ + 'solrsearch_search_suggestions' => array( + 'variables' => array('links' => NULL), + ), + /** + * Shows a list of results (docs) in content recommendation block + */ + 'solrsearch_search_mlt_recommendation_block' => array( + 'variables' => array('docs' => NULL, 'delta' => NULL), + ), + 'solrsearch_search_block_form' => array( + 'render element' => 'form', + 'template' => 'solrsearch-block-form', + ), + + 'solrsearch_search_author_block_form' => array( + 'render element' => 'form', + 'template' => 'solrsearch-author-block-form', + ), + + 'solrsearch_search_title_block_form' => array( + 'render element' => 'form', + 'template' => 'solrsearch-title-block-form', + ), + ); +} + + + +/** + * Implements hook_theme_registry_alter(). + */ +function solrsearch_search_theme_registry_alter(&$theme_registry) { + + if (isset($theme_registry['search_results'])) { + $theme_registry['search_results']['variables']['search_page'] = NULL; + } +} + +/** + * Theme the highlighted snippet text for a search entry. + * + * @param array $vars + * + */ +function theme_solrsearch_search_snippets($vars) { + $result = ''; + if (is_array($vars['snippets'])) { + $snippets = $vars['snippets']; + if (isset($snippets['content'])) { + $result .= implode(' ... ', $snippets['content']); + unset($snippets['content']); + } + if (isset($snippets['teaser'])) { + $result .= (strlen($result) > 0) ? ' ... ' : ''; + $result .= implode(' ... ', $snippets['teaser']); + unset($snippets['teaser']); + } + if (count($snippets)) { + $result .= (strlen($result) > 0) ? ' ... ' : ''; + foreach ($snippets as $snippet) { + $result .= implode(' ... ', $snippet); + } + } + } + return $result . ' ...'; +} + +/** + * Brief message to display when no results match the query. + * + * @see search_help() + */ +function theme_solrsearch_search_noresults() { + return t('
    +
  • Check if your spelling is correct, or try removing filters.
  • +
  • Remove quotes around phrases to match each word individually: "blue drop" will match less than blue drop.
  • +
  • You can require or exclude terms using + and -: big +blue drop will require a match on blue while big blue -drop will exclude results that contain drop.
  • +
'); +} + + + + +/** + * Implements hook_forms(). + */ +function solrsearch_search_forms() { + $forms['solrsearch_search_block_form']= array( + 'callback' => 'solrsearch_search_box', + 'callback arguments' => array('solrsearch_search_block_form'), + ); + + $forms['solrsearch_search_author_block_form']= array( + 'callback' => 'solrsearch_search_author_box', + 'callback arguments' => array('solrsearch_search_author_block_form'), + ); + + $forms['solrsearch_search_title_block_form']= array( + 'callback' => 'solrsearch_search_title_box', + 'callback arguments' => array('solrsearch_search_title_block_form'), + ); + return $forms; +} + + + + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search.pages.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search.pages.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,130 @@ + 'container', + '#attributes' => array('class' => array('container-inline')), + ); + $form['basic']['keys'] = array( + '#type' => 'textfield', + '#title' => t('Enter terms'), + '#default_value' => $keys, + '#size' => 20, + '#maxlength' => 255, + ); + $form['basic']['submit'] = array( + '#type' => 'submit', + '#value' => t('Search'), + ); + + $form['basic']['get'] = array( + '#type' => 'hidden', + '#default_value' => json_encode(array_diff_key($_GET, array('q' => 1, 'page' => 1, 'solrsort' => 1, 'retain-filters' => 1))), + ); + + $fq = NULL; + + if (solrsearch_has_searched($search_page['env_id'])) { + $query = solrsearch_current_query($search_page['env_id']); + // We use the presence of filter query params as a flag for the retain filters checkbox. + $fq = $query->getParam('fq'); + } + + if ($fq || isset($form_state['input']['retain-filters'])) { + $form['basic']['retain-filters'] = array( + '#type' => 'checkbox', + '#title' => t('Retain current filters'), + '#default_value' => (int) !empty($_GET['retain-filters']), + ); + } + + return $form; +} + +/** + * Processes solrsearch_search_custom_page_search_form submissions. + */ +function solrsearch_search_custom_page_search_form_submit(&$form, &$form_state) { + $search_page = $form['#search_page']; + $redirect = $search_page['search_path']; + + // Also encode slashes so we don't get akward situations when obtaining the + // search key. We can't use drupal_encode_path because for "aestetic" reasons + // they don't encode slashes... + $redirect_value = rawurlencode($form_state['values']['keys']); + + if (strlen($form_state['values']['keys'])) { + $redirect .= '/' . $redirect_value; + } + + $get = array(); + if (isset($form_state['values']['get'])) { + $get = json_decode($form_state['values']['get'], TRUE); + } + if (!empty($form_state['values']['retain-filters'])) { + // Add our saved values + $get['retain-filters'] = '1'; + } + else { + // Remove all filters + if (!empty($search_page['settings']['solrsearch_search_allow_user_input'])) { + unset($get['fq']); + } + if (module_exists('facetapi')) { + unset($get['f']); + } + } + + // Add the query values into the redirect. + $form_state['redirect'] = array($redirect, array('query' => $get)); +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search_author_block.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search_author_block.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,93 @@ + 'textfield', + '#title' => t('Search'), + '#title_display' => 'invisible', + '#size' => 15, + '#default_value' => '', + '#attributes' => array('title' => t('Enter the terms you wish to search for.')), + ); + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Search')); + $form['#submit'][] = 'solrsearch_search_author_box_form_submit'; + + return $form; +} + +/** + * Process a block search form submission. + */ +function solrsearch_search_author_box_form_submit($form, &$form_state) { + // The search form relies on control of the redirect destination for its + // functionality, so we override any static destination set in the request, + // for example by drupal_access_denied() or drupal_not_found() + // (see http://drupal.org/node/292565). + if (isset($_GET['destination'])) { + unset($_GET['destination']); + } + + // Check to see if the form was submitted empty. + // If it is empty, display an error message. + // (This method is used instead of setting #required to TRUE for this field + // because that results in a confusing error message. It would say a plain + // "field is required" because the search keywords field has no title. + // The error message would also complain about a missing #title field.) + if ($form_state['values']['solrsearch_search_author_block_form'] == '') { + form_set_error('keys', t('Please enter some keywords.')); + } + + $form_id = $form['form_id']['#value']; + $info = search_get_default_module_info(); + if ($info) { + $form_state['redirect'] = 'solrsearch/' . $info['path'] . '/IM_author:' . trim($form_state['values'][$form_id]); + } + else { + form_set_error(NULL, t('Search is currently disabled.'), 'error'); + } +} + +/** + * Process variables for search-block-form.tpl.php. + * + * The $variables array contains the following arguments: + * - $form + * + * @see search-block-form.tpl.php + */ +function template_preprocess_solrsearch_search_author_block_form(&$variables) { + $variables['search'] = array(); + $hidden = array(); + // Provide variables named after form keys so themers can print each element independently. + foreach (element_children($variables['form']) as $key) { + $type = $variables['form'][$key]['#type']; + if ($type == 'hidden' || $type == 'token') { + $hidden[] = drupal_render($variables['form'][$key]); + } + else { + $variables['search'][$key] = drupal_render($variables['form'][$key]); + } + } + // Hidden form elements have no value to themers. No need for separation. + $variables['search']['hidden'] = implode($hidden); + // Collect all form elements to make it easier to print the whole form. + $variables['solrsearch_search_form'] = implode($variables['search']); +} + + + +function solrsearch_search_author_block(){ + + $block['content'] = drupal_get_form('solrsearch_search_author_block_form'); + return $block; + + +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search_blocks.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search_blocks.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,94 @@ + 'textfield', + '#title' => t('Search'), + '#title_display' => 'invisible', + '#size' => 15, + '#default_value' => '', + '#attributes' => array('title' => t('Enter the terms you wish to search for.')), + ); + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Search')); + $form['#submit'][] = 'solrsearch_search_box_form_submit'; + + return $form; +} + +/** + * Process a block search form submission. + */ +function solrsearch_search_box_form_submit($form, &$form_state) { + // The search form relies on control of the redirect destination for its + // functionality, so we override any static destination set in the request, + // for example by drupal_access_denied() or drupal_not_found() + // (see http://drupal.org/node/292565). + if (isset($_GET['destination'])) { + unset($_GET['destination']); + } + + // Check to see if the form was submitted empty. + // If it is empty, display an error message. + // (This method is used instead of setting #required to TRUE for this field + // because that results in a confusing error message. It would say a plain + // "field is required" because the search keywords field has no title. + // The error message would also complain about a missing #title field.) + if ($form_state['values']['solrsearch_search_block_form'] == '') { + form_set_error('keys', t('Please enter some keywords.')); + } + + $form_id = $form['form_id']['#value']; + $info = search_get_default_module_info(); + if ($info) { + $form_state['redirect'] = 'solrsearch/' . $info['path'] . '/' . trim($form_state['values'][$form_id]); + } + else { + form_set_error(NULL, t('Search is currently disabled.'), 'error'); + } +} + + + + +function solrsearch_search_block(){ + + $block['content'] = drupal_get_form('solrsearch_search_block_form'); + return $block; + + +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_search_title_block.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_search_title_block.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,93 @@ + 'textfield', + '#title' => t('Search'), + '#title_display' => 'invisible', + '#size' => 15, + '#default_value' => '', + '#attributes' => array('title' => t('Enter the terms you wish to search for.')), + ); + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Search')); + $form['#submit'][] = 'solrsearch_search_title_box_form_submit'; + + return $form; +} + +/** + * Process a block search form submission. + */ +function solrsearch_search_title_box_form_submit($form, &$form_state) { + // The search form relies on control of the redirect destination for its + // functionality, so we override any static destination set in the request, + // for example by drupal_access_denied() or drupal_not_found() + // (see http://drupal.org/node/292565). + if (isset($_GET['destination'])) { + unset($_GET['destination']); + } + + // Check to see if the form was submitted empty. + // If it is empty, display an error message. + // (This method is used instead of setting #required to TRUE for this field + // because that results in a confusing error message. It would say a plain + // "field is required" because the search keywords field has no title. + // The error message would also complain about a missing #title field.) + if ($form_state['values']['solrsearch_search_title_block_form'] == '') { + form_set_error('keys', t('Please enter some keywords.')); + } + + $form_id = $form['form_id']['#value']; + $info = search_get_default_module_info(); + if ($info) { + $form_state['redirect'] = 'solrsearch/' . $info['path'] . '/IM_title:' . trim($form_state['values'][$form_id]); + } + else { + form_set_error(NULL, t('Search is currently disabled.'), 'error'); + } +} + +/** + * Process variables for search-block-form.tpl.php. + * + * The $variables array contains the following arguments: + * - $form + * + * @see search-block-form.tpl.php + */ +function template_preprocess_solrsearch_search_title_block_form(&$variables) { + $variables['search'] = array(); + $hidden = array(); + // Provide variables named after form keys so themers can print each element independently. + foreach (element_children($variables['form']) as $key) { + $type = $variables['form'][$key]['#type']; + if ($type == 'hidden' || $type == 'token') { + $hidden[] = drupal_render($variables['form'][$key]); + } + else { + $variables['search'][$key] = drupal_render($variables['form'][$key]); + } + } + // Hidden form elements have no value to themers. No need for separation. + $variables['search']['hidden'] = implode($hidden); + // Collect all form elements to make it easier to print the whole form. + $variables['solrsearch_search_form'] = implode($variables['search']); +} + + + +function solrsearch_search_title_block(){ + + $block['content'] = drupal_get_form('solrsearch_search_title_block_form'); + return $block; + + +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch/solrsearch_terms.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch/solrsearch_terms.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,106 @@ +data); + $authorsAll=$data['terms'][$field]; + return $authorsAll; +} + +function solrsearch_load_data($field,$letter,$accessType="free"){ + #todo cache this! + $env = solrsearch_environment_load('echosearch'); + $searchStr = "/select?q=". $field. ":". $letter . "*%20and%20doc-type:indexMeta%20&json.nl=map&facet.sort=index&facet.mincount=1&wt=json&facet=true&facet.field=" . $field . "&facet.limit=100000&start=0&rows=9"; + + if ($accessType=="free") + { + $searchStr .="&fq=access-type:free"; + } + + $url = $env['url']; + $result = drupal_http_request($url . $searchStr); + $data = drupal_json_decode($result->data); + #return $data; + + $resultsAll=$data['facet_counts']['facet_fields'][$field]; + + #result list ist zu lang, da hier alle werke gefunden werden, bei denene ein Autor mit dem Buchstaben beginnt. + $res=array(); + foreach ($resultsAll as $author => $val){ + if (drupal_substr($author, 0,1) == $letter){ + $res[$author]=$val; + } + + } + + return $res; +} + +# + +function solrsearch_term_select_field(){ + $query = $_GET; + if (isset($query['browseField'])){ + drupal_goto("solrsearch-terms/" .$query['browseField'] ); + } + +} +function solrsearch_term_list($field="",$letter="A",$numperpage=20){ + + if ($field==""){ + return ''; + } + + + + #field z.b. author_c + + if (!user_access("view restricted content")){ + $accessType="free"; + } else { + $accessType=""; + } + + $authorsAll=solrsearch_load_data($field,$letter,$accessType); + $cnt=count($authorsAll); + + $page = pager_find_page(); + $offset = $numperpage * $page; + $authors = array_slice($authorsAll, $offset,$slice_lenght=$numperpage); + + pager_default_initialize($cnt, 10); + + $rs = theme('solrsearch_term_selection_form',array('field' => $field)); + $rs .=theme('pager', array('tags' => array('<<','<','..','>','>>'),'quantity' => 3)); + + if ($field=='author_c'){ + $rs .= theme('solrsearch_term_list_author',array('authors' => $authors,'cnt' => $cnt,'letter' => $letter)); + } else { + $rs .= theme('solrsearch_term_list_title',array('titles' => $authors,'cnt' => $cnt,'letter' => $letter)); + } + + + $rs .=theme('pager', array('tags' => array('<<','<','..','>','>>'),'quantity' => 3)); + + return $rs; + } + + + function solrsearch_alphapager($field) { + $attributes = array( 'class' => 'alpha-page' ); + $output = ""; + + foreach(range('A', 'Z') as $letter) { + $output .= " +
  • " . l($letter, "solrsearch-terms/" . $field . "/" . $letter, $attributes). " |
  • "; + } + + + + return $output; + } \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/LICENSE.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/README.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,102 @@ +-- veraenderunge bisher +DW + +An das Textinputfeld für die solr suche wird jeweils zusätzlich eine klasse: + "solrsearch-autocomplete.field.FIELDNAME" angeängt, FIELDName ist hierbei das Feld das für die autovervollständigung durchsucht werden soll. + + + daher hat die function: + solrsearch_autocomplete_do_alter + + jetzt einen zusätzlichen parameter für den feldnamen + + function solrsearch_autocomplete_do_alter(&$element,$field_name) { + + + solrsearch_autocomplete.js + + is dementsprechend erweitert, dass die zusatzliche klasse ausgewertet wird + jquery fragt dann die autocompletion function von solrsearch (angehaengt bei /solrsearch_autocomplete") mit den paramtern "query" und "fieldName". + + entsprechend wird dann solr abgefragt. + +TODO: +--bisher ist fuer jedes such feld in der solrsuche +die form-Veränderung hard codiert (korrespondierte dazu ist bisher ist auch noch fuer jedes suchfeld eine eigene such box in solrsearch angelegt.) + +das sollte so geändert werden, dass fuer jedes suchfeld ein parameter existiert. + + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_solrsearch_search_block_form_alter(&$form, $form_state) { + + $element = &$form['solrsearch_search_block_form']; + solrsearch_autocomplete_do_alter($element,"title"); +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_solrsearch_search_author_block_form_alter(&$form, $form_state) { + + $element = &$form['solrsearch_search_author_block_form']; + solrsearch_autocomplete_do_alter($element,"author"); +} + + +--die widgets funktioneren nicht + +---- original apachesolr_autocomplete README -- begin ---- + + +Apache Solr Autocomplete module for Drupal. + +-- SUMMARY -- + +Add-on module to Apache Solr Search Integration that adds simple autocomplete +functionality. It enforces node access, meaning that all suggestions are only +from nodes that the user actually has access to. + +For a full description of the module, visit the project page: + http://drupal.org/project/apachesolr_autocomplete + +To submit bug reports and feature suggestions, or to track changes: + http://drupal.org/project/issues/apachesolr_autocomplete + + +-- REQUIREMENTS -- + +Apache Solr Search Integration module. +See http://drupal.org/project/apachesolr + + +-- INSTALLATION -- + +* Install as usual, see http://drupal.org/node/70151 for further information. + + +-- CONFIGURATION -- + +For configuration, go to: + + Administration >> Settings >> Apache Solr + +and look for the "Advanced Options" fieldset. The setting is: + + "Autocomplete widget to use:" + +where you can choose between a custom Javascript widget (included with the +module) or fall back to the core Drupal autocomplete widget. The default is to +use the custom widget. + +-- TROUBLESHOOTING -- + +If you are having trouble with the autocomplete suggestions not working correctly, +try changing the configuration to use the core Drupal autocomplete widget. + +If you encounter other problems, please post to the project issue queue: + http://drupal.org/project/issues/apachesolr_autocomplete + +-- \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/changelog.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/changelog.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,27 @@ +1.1 +--- +* Added matchContains: "word" option, match only the start of words instead of everywhere +* Fixed mustMatch to trigger result event when no match was found +* Fixed the issue where an autocomplete was applied after the field had focus +* Extended multiple complete to enable editing words not at the end of the field (doesn't work in Opera) + +1.0.2 +----- +* Fixed missing semicolon + +1.0.1 +----- +* Fixed element creation (
      to
        and
      • to
      • ) +* Fixed ac_even class (was ac_event) +* Fixed bgiframe usage: now its really optional +* Removed the blur-on-return workaround, added a less obtrusive one only for Opera +* Fixed hold cursor keys: Opera needs keypress, everyone else keydown to scroll through result list when holding cursor key +* Updated package to jQuery 1.2.5, removing dimensions +* Fixed multiple-mustMatch: Remove only the last term when no match is found +* Fixed multiple without mustMatch: Don't select the last active when no match is found (on tab/return) +* Fixed multiple cursor position: Put cursor at end of input after selecting a value + +1.0 +--- + +* First release. diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/jquery.autocomplete.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/jquery.autocomplete.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,48 @@ +.ac_results { + padding: 0px; + border: 1px solid black; + background-color: white; + overflow: hidden; + z-index: 99999; +} + +.ac_results ul { + width: 100%; + list-style-position: outside; + list-style: none; + padding: 0; + margin: 0; +} + +.ac_results li { + margin: 0px; + padding: 2px 5px; + cursor: default; + display: block; + /* + if width will be 100% horizontal scrollbar will apear + when scroll mode will be used + */ + /*width: 100%;*/ + font: menu; + font-size: 12px; + /* + it is very important, if line-height not setted or setted + in relative units scroll will be broken in firefox + */ + line-height: 16px; + overflow: hidden; +} + +.ac_loading { + background: white url('indicator.gif') right center no-repeat; +} + +.ac_odd { + background-color: #eee; +} + +.ac_over { + background-color: #0A246A; + color: white; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/jquery.autocomplete.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/jquery.autocomplete.js Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,808 @@ +/* + * jQuery Autocomplete plugin 1.1 + * + * Copyright (c) 2009 Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +;(function($) { + +$.fn.extend({ + autocomplete: function(urlOrData, options) { + + var isUrl = typeof urlOrData == "string"; + options = $.extend({}, $.Autocompleter.defaults, { + url: isUrl ? urlOrData : null, + data: isUrl ? null : urlOrData, + delay: isUrl ? $.Autocompleter.defaults.delay : 10, + max: options && !options.scroll ? 10 : 150 + }, options); + + // if highlight is set to false, replace it with a do-nothing function + options.highlight = options.highlight || function(value) { return value; }; + + // if the formatMatch option is not specified, then use formatItem for backwards compatibility + options.formatMatch = options.formatMatch || options.formatItem; + + return this.each(function() { + new $.Autocompleter(this, options); + }); + }, + result: function(handler) { + return this.bind("result", handler); + }, + search: function(handler) { + return this.trigger("search", [handler]); + }, + flushCache: function() { + return this.trigger("flushCache"); + }, + setOptions: function(options){ + return this.trigger("setOptions", [options]); + }, + unautocomplete: function() { + return this.trigger("unautocomplete"); + } +}); + +$.Autocompleter = function(input, options) { + + var KEY = { + UP: 38, + DOWN: 40, + DEL: 46, + TAB: 9, + RETURN: 13, + ESC: 27, + COMMA: 188, + PAGEUP: 33, + PAGEDOWN: 34, + BACKSPACE: 8 + }; + + // Create $ object for input element + var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); + + var timeout; + var previousValue = ""; + var cache = $.Autocompleter.Cache(options); + var hasFocus = 0; + var lastKeyPressCode; + var config = { + mouseDownOnSelect: false + }; + var select = $.Autocompleter.Select(options, input, selectCurrent, config); + + var blockSubmit; + + // prevent form submit in opera when selecting with return key + $.browser.opera && $(input.form).bind("submit.autocomplete", function() { + if (blockSubmit) { + blockSubmit = false; + return false; + } + }); + + // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all + $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) { + // a keypress means the input has focus + // avoids issue where input had focus before the autocomplete was applied + hasFocus = 1; + // track last key pressed + lastKeyPressCode = event.keyCode; + switch(event.keyCode) { + + case KEY.UP: + event.preventDefault(); + if ( select.visible() ) { + select.prev(); + } else { + onChange(0, true); + } + break; + + case KEY.DOWN: + event.preventDefault(); + if ( select.visible() ) { + select.next(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEUP: + event.preventDefault(); + if ( select.visible() ) { + select.pageUp(); + } else { + onChange(0, true); + } + break; + + case KEY.PAGEDOWN: + event.preventDefault(); + if ( select.visible() ) { + select.pageDown(); + } else { + onChange(0, true); + } + break; + + // matches also semicolon + case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: + case KEY.TAB: + case KEY.RETURN: + if( selectCurrent() ) { + // stop default to prevent a form submit, Opera needs special handling + event.preventDefault(); + blockSubmit = true; + return false; + } + break; + + case KEY.ESC: + select.hide(); + break; + + default: + clearTimeout(timeout); + timeout = setTimeout(onChange, options.delay); + break; + } + }).focus(function(){ + // track whether the field has focus, we shouldn't process any + // results if the field no longer has focus + hasFocus++; + }).blur(function() { + hasFocus = 0; + if (!config.mouseDownOnSelect) { + hideResults(); + } + }).click(function() { + // show select when clicking in a focused field + if ( hasFocus++ > 1 && !select.visible() ) { + onChange(0, true); + } + }).bind("search", function() { + // TODO why not just specifying both arguments? + var fn = (arguments.length > 1) ? arguments[1] : null; + function findValueCallback(q, data) { + var result; + if( data && data.length ) { + for (var i=0; i < data.length; i++) { + if( data[i].result.toLowerCase() == q.toLowerCase() ) { + result = data[i]; + break; + } + } + } + if( typeof fn == "function" ) fn(result); + else $input.trigger("result", result && [result.data, result.value]); + } + $.each(trimWords($input.val()), function(i, value) { + request(value, findValueCallback, findValueCallback); + }); + }).bind("flushCache", function() { + cache.flush(); + }).bind("setOptions", function() { + $.extend(options, arguments[1]); + // if we've updated the data, repopulate + if ( "data" in arguments[1] ) + cache.populate(); + }).bind("unautocomplete", function() { + select.unbind(); + $input.unbind(); + $(input.form).unbind(".autocomplete"); + }); + + + function selectCurrent() { + var selected = select.selected(); + if( !selected ) + return false; + + var v = selected.result; + previousValue = v; + + if ( options.multiple ) { + var words = trimWords($input.val()); + if ( words.length > 1 ) { + var seperator = options.multipleSeparator.length; + var cursorAt = $(input).selection().start; + var wordAt, progress = 0; + $.each(words, function(i, word) { + progress += word.length; + if (cursorAt <= progress) { + wordAt = i; + return false; + } + progress += seperator; + }); + words[wordAt] = v; + // TODO this should set the cursor to the right position, but it gets overriden somewhere + //$.Autocompleter.Selection(input, progress + seperator, progress + seperator); + v = words.join( options.multipleSeparator ); + } + v += options.multipleSeparator; + } + + $input.val(v); + hideResultsNow(); + $input.trigger("result", [selected.data, selected.value]); + return true; + } + + function onChange(crap, skipPrevCheck) { + if( lastKeyPressCode == KEY.DEL ) { + select.hide(); + return; + } + + var currentValue = $input.val(); + + if ( !skipPrevCheck && currentValue == previousValue ) + return; + + previousValue = currentValue; + + currentValue = lastWord(currentValue); + if ( currentValue.length >= options.minChars) { + $input.addClass(options.loadingClass); + if (!options.matchCase) + currentValue = currentValue.toLowerCase(); + request(currentValue, receiveData, hideResultsNow); + } else { + stopLoading(); + select.hide(); + } + }; + + function trimWords(value) { + if (!value) + return [""]; + if (!options.multiple) + return [$.trim(value)]; + return $.map(value.split(options.multipleSeparator), function(word) { + return $.trim(value).length ? $.trim(word) : null; + }); + } + + function lastWord(value) { + if ( !options.multiple ) + return value; + var words = trimWords(value); + if (words.length == 1) + return words[0]; + var cursorAt = $(input).selection().start; + if (cursorAt == value.length) { + words = trimWords(value) + } else { + words = trimWords(value.replace(value.substring(cursorAt), "")); + } + return words[words.length - 1]; + } + + // fills in the input box w/the first match (assumed to be the best match) + // q: the term entered + // sValue: the first matching result + function autoFill(q, sValue){ + // autofill in the complete box w/the first match as long as the user hasn't entered in more data + // if the last user key pressed was backspace, don't autofill + if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) { + // fill in the value (keep the case the user has typed) + $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); + // select the portion of the value not typed by the user (so the next character will erase) + $(input).selection(previousValue.length, previousValue.length + sValue.length); + } + }; + + function hideResults() { + clearTimeout(timeout); + timeout = setTimeout(hideResultsNow, 200); + }; + + function hideResultsNow() { + var wasVisible = select.visible(); + select.hide(); + clearTimeout(timeout); + stopLoading(); + if (options.mustMatch) { + // call search and run callback + $input.search( + function (result){ + // if no value found, clear the input box + if( !result ) { + if (options.multiple) { + var words = trimWords($input.val()).slice(0, -1); + $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") ); + } + else { + $input.val( "" ); + $input.trigger("result", null); + } + } + } + ); + } + }; + + function receiveData(q, data) { + if ( data && data.length && hasFocus ) { + stopLoading(); + select.display(data, q); + autoFill(q, data[0].value); + select.show(); + } else { + hideResultsNow(); + } + }; + + function request(term, success, failure) { + if (!options.matchCase) + term = term.toLowerCase(); + var data = cache.load(term); + // recieve the cached data + if (data && data.length) { + success(term, data); + // if an AJAX url has been supplied, try loading the data now + } else if( (typeof options.url == "string") && (options.url.length > 0) ){ + + var extraParams = { + timestamp: +new Date() + }; + $.each(options.extraParams, function(key, param) { + extraParams[key] = typeof param == "function" ? param() : param; + }); + + $.ajax({ + // try to leverage ajaxQueue plugin to abort previous requests + mode: "abort", + // limit abortion to this input + port: "autocomplete" + input.name, + dataType: options.dataType, + url: options.url, + data: $.extend({ + query: lastWord(term), + limit: options.max + }, extraParams), + success: function(data) { + var parsed = options.parse && options.parse(data) || parse(data); + cache.add(term, parsed); + success(term, parsed); + } + }); + } else { + // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match + select.emptyList(); + failure(term); + } + }; + + function parse(data) { + var parsed = []; + var rows = data.split("\n"); + for (var i=0; i < rows.length; i++) { + var row = $.trim(rows[i]); + if (row) { + row = row.split("|"); + parsed[parsed.length] = { + data: row, + value: row[0], + result: options.formatResult && options.formatResult(row, row[0]) || row[0] + }; + } + } + return parsed; + }; + + function stopLoading() { + $input.removeClass(options.loadingClass); + }; + +}; + +$.Autocompleter.defaults = { + inputClass: "ac_input", + resultsClass: "ac_results", + loadingClass: "ac_loading", + minChars: 1, + delay: 400, + matchCase: false, + matchSubset: true, + matchContains: false, + cacheLength: 10, + max: 100, + mustMatch: false, + extraParams: {}, + selectFirst: true, + formatItem: function(row) { return row[0]; }, + formatMatch: null, + autoFill: false, + width: 0, + multiple: false, + multipleSeparator: ", ", + highlight: function(value, term) { + return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); + }, + scroll: true, + scrollHeight: 180 +}; + +$.Autocompleter.Cache = function(options) { + + var data = {}; + var length = 0; + + function matchSubset(s, sub) { + if (!options.matchCase) + s = s.toLowerCase(); + var i = s.indexOf(sub); + if (options.matchContains == "word"){ + i = s.toLowerCase().search("\\b" + sub.toLowerCase()); + } + if (i == -1) return false; + return i == 0 || options.matchContains; + }; + + function add(q, value) { + if (length > options.cacheLength){ + flush(); + } + if (!data[q]){ + length++; + } + data[q] = value; + } + + function populate(){ + if( !options.data ) return false; + // track the matches + var stMatchSets = {}, + nullData = 0; + + // no url was specified, we need to adjust the cache length to make sure it fits the local data store + if( !options.url ) options.cacheLength = 1; + + // track all options for minChars = 0 + stMatchSets[""] = []; + + // loop through the array and create a lookup structure + for ( var i = 0, ol = options.data.length; i < ol; i++ ) { + var rawValue = options.data[i]; + // if rawValue is a string, make an array otherwise just reference the array + rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; + + var value = options.formatMatch(rawValue, i+1, options.data.length); + if ( value === false ) + continue; + + var firstChar = value.charAt(0).toLowerCase(); + // if no lookup array for this character exists, look it up now + if( !stMatchSets[firstChar] ) + stMatchSets[firstChar] = []; + + // if the match is a string + var row = { + value: value, + data: rawValue, + result: options.formatResult && options.formatResult(rawValue) || value + }; + + // push the current match into the set list + stMatchSets[firstChar].push(row); + + // keep track of minChars zero items + if ( nullData++ < options.max ) { + stMatchSets[""].push(row); + } + }; + + // add the data items to the cache + $.each(stMatchSets, function(i, value) { + // increase the cache size + options.cacheLength++; + // add to the cache + add(i, value); + }); + } + + // populate any existing data + setTimeout(populate, 25); + + function flush(){ + data = {}; + length = 0; + } + + return { + flush: flush, + add: add, + populate: populate, + load: function(q) { + if (!options.cacheLength || !length) + return null; + /* + * if dealing w/local data and matchContains than we must make sure + * to loop through all the data collections looking for matches + */ + if( !options.url && options.matchContains ){ + // track all matches + var csub = []; + // loop through all the data grids for matches + for( var k in data ){ + // don't search through the stMatchSets[""] (minChars: 0) cache + // this prevents duplicates + if( k.length > 0 ){ + var c = data[k]; + $.each(c, function(i, x) { + // if we've got a match, add it to the array + if (matchSubset(x.value, q)) { + csub.push(x); + } + }); + } + } + return csub; + } else + // if the exact item exists, use it + if (data[q]){ + return data[q]; + } else + if (options.matchSubset) { + for (var i = q.length - 1; i >= options.minChars; i--) { + var c = data[q.substr(0, i)]; + if (c) { + var csub = []; + $.each(c, function(i, x) { + if (matchSubset(x.value, q)) { + csub[csub.length] = x; + } + }); + return csub; + } + } + } + return null; + } + }; +}; + +$.Autocompleter.Select = function (options, input, select, config) { + var CLASSES = { + ACTIVE: "ac_over" + }; + + var listItems, + active = -1, + data, + term = "", + needsInit = true, + element, + list; + + // Create results + function init() { + if (!needsInit) + return; + element = $("
        ") + .hide() + .addClass(options.resultsClass) + .css("position", "absolute") + .appendTo(document.body); + + list = $("
          ").appendTo(element).mouseover( function(event) { + if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { + active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); + $(target(event)).addClass(CLASSES.ACTIVE); + } + }).click(function(event) { + $(target(event)).addClass(CLASSES.ACTIVE); + select(); + // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus + input.focus(); + return false; + }).mousedown(function() { + config.mouseDownOnSelect = true; + }).mouseup(function() { + config.mouseDownOnSelect = false; + }); + + if( options.width > 0 ) + element.css("width", options.width); + + needsInit = false; + } + + function target(event) { + var element = event.target; + while(element && element.tagName != "LI") + element = element.parentNode; + // more fun with IE, sometimes event.target is empty, just ignore it then + if(!element) + return []; + return element; + } + + function moveSelect(step) { + listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE); + movePosition(step); + var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); + if(options.scroll) { + var offset = 0; + listItems.slice(0, active).each(function() { + offset += this.offsetHeight; + }); + if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { + list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); + } else if(offset < list.scrollTop()) { + list.scrollTop(offset); + } + } + }; + + function movePosition(step) { + active += step; + if (active < 0) { + active = listItems.size() - 1; + } else if (active >= listItems.size()) { + active = 0; + } + } + + function limitNumberOfItems(available) { + return options.max && options.max < available + ? options.max + : available; + } + + function fillList() { + list.empty(); + var max = limitNumberOfItems(data.length); + for (var i=0; i < max; i++) { + if (!data[i]) + continue; + var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); + if ( formatted === false ) + continue; + var li = $("
        • ").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0]; + $.data(li, "ac_data", data[i]); + } + listItems = list.find("li"); + if ( options.selectFirst ) { + listItems.slice(0, 1).addClass(CLASSES.ACTIVE); + active = 0; + } + // apply bgiframe if available + if ( $.fn.bgiframe ) + list.bgiframe(); + } + + return { + display: function(d, q) { + init(); + data = d; + term = q; + fillList(); + }, + next: function() { + moveSelect(1); + }, + prev: function() { + moveSelect(-1); + }, + pageUp: function() { + if (active != 0 && active - 8 < 0) { + moveSelect( -active ); + } else { + moveSelect(-8); + } + }, + pageDown: function() { + if (active != listItems.size() - 1 && active + 8 > listItems.size()) { + moveSelect( listItems.size() - 1 - active ); + } else { + moveSelect(8); + } + }, + hide: function() { + element && element.hide(); + listItems && listItems.removeClass(CLASSES.ACTIVE); + active = -1; + }, + visible : function() { + return element && element.is(":visible"); + }, + current: function() { + return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); + }, + show: function() { + var offset = $(input).offset(); + element.css({ + width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), + top: offset.top + input.offsetHeight, + left: offset.left + }).show(); + if(options.scroll) { + list.scrollTop(0); + list.css({ + maxHeight: options.scrollHeight, + overflow: 'auto' + }); + + if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { + var listHeight = 0; + listItems.each(function() { + listHeight += this.offsetHeight; + }); + var scrollbarsVisible = listHeight > options.scrollHeight; + list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); + if (!scrollbarsVisible) { + // IE doesn't recalculate width when scrollbar disappears + listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); + } + } + + } + }, + selected: function() { + var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); + return selected && selected.length && $.data(selected[0], "ac_data"); + }, + emptyList: function (){ + list && list.empty(); + }, + unbind: function() { + element && element.remove(); + } + }; +}; + +$.fn.selection = function(start, end) { + if (start !== undefined) { + return this.each(function() { + if( this.createTextRange ){ + var selRange = this.createTextRange(); + if (end === undefined || start == end) { + selRange.move("character", start); + selRange.select(); + } else { + selRange.collapse(true); + selRange.moveStart("character", start); + selRange.moveEnd("character", end); + selRange.select(); + } + } else if( this.setSelectionRange ){ + this.setSelectionRange(start, end); + } else if( this.selectionStart ){ + this.selectionStart = start; + this.selectionEnd = end; + } + }); + } + var field = this[0]; + if ( field.createTextRange ) { + var range = document.selection.createRange(), + orig = field.value, + teststring = "<->", + textLength = range.text.length; + range.text = teststring; + var caretAt = field.value.indexOf(teststring); + field.value = orig; + this.selection(caretAt, caretAt + textLength); + return { + start: caretAt, + end: caretAt + textLength + } + } else if( field.selectionStart !== undefined ){ + return { + start: field.selectionStart, + end: field.selectionEnd + } + } +}; + +})(jQuery); diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/todo --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/jquery-autocomplete/todo Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,166 @@ +TODO + +- test formatItem implementation that returns (clickable) anchors +- bug: handle del key; eg. type a letter, remove it using del, type same letter again: nothing happens +- handle up/down keys in textarea (prevent default while select is open?) +- docs: max:0 works, too, "removing" it(??) +- fix ac_loading/options.loadingClass +- support/enable request urls like foo/bar/10 instead of foo/q=10 +- urlencode request term before passing to $.ajax/data; evaluate why $.ajax doesn't handle that itself, if at all; try with umlauts, russian/danish/chinese characeters (see validate) +- test what happens when an element gets focused programmatically (maybe even before then autcomplete is applied) +- check if blur on selecting can be removed +- fix keyhandling to ignore metakeys, eg. shift; especially important for chinese characters that need more then one key +- enhance mustMatch: provide event/callback when a value gets deleted +- handle tab key different then enter, eg. don't blur field or prevent default, just let it move on; in any case, no need to blur the field when selecting a value via tab, unlike return +- prevent redundant requests on + - superstring returned no result, no need to query again for substring, eg. pete returned nothing, peter won't either + - previous query mustn't be requested again, eg. pete returns 10 lines, peter nothing, backspace to pete should get the 10 lines from cache (may need TimeToLive setting for cache to invalidate it) +- incorporate improvements and suggestions by Hector: http://beta.winserver.com/public/test/MultiSuggestTest.wct +- json support: An optional JSON format, that assumes a certain JSON format as default and just looks for a dataType "json" to be activated; [records], where each record is { id:String, label:String, moreOptionalValues... } +- accept callback as first argument to let users implement their own dynamic data (no caching) - consider async API +- allow users to keep their incomplete value when pressing tab, just mimic the default-browser-autocomplete: tab doesn't select any proposed value -> tab closes the select and works normal otherwise +- small bug in your autocomplete, When setting autoFill:true I would expect formatResult to be called on autofill, it seems not to be the case. +- add a callback to allow decoding the response +- allow modification of not-last value in multiple-fields +@option Number size Limit the number of items to show at once. Default: +@option Function parse - TEST AND DOCUMENT ME +- add option to display selectbox on focus + +$input.bind("show", function() { + if ( !select.visible() ) { + onChange(0, true); + } +}); + +- reference: http://capxous.com/ + - add "try ..." hints to demo + - check out demos +- reference: http://createwebapp.com/demo/ + +- add option to hide selectbox when no match is found - see comment by Ian on plugin page (14. Juli 2007 04:31) +- add example for reinitializing an autocomplete using unbind() + +- Add option to pass through additional arguments to $.ajax, like type to use POST instead of GET + + - I found out that the problem with UTF-8 not being correctly sent can be solved on the server side by applying (PHP) rawurldecode() function, which decodes the Unicode characters sent by GET method and therefore URL-encoded. +-> add that hint to docs and examples + +But I am trying this with these three values: “foo bar”, “foo foo”, and “foo far”, and if I enter “b” (or “ba”) nothing matches, if I enter “f” all three do match, and if I enter “fa” the last one matches. +The problem seems to be that the cache is implemented with a first-character hashtable, so only after matching the first character, the latter ones are searched for. + +xml example: + + + + + + FreeNode: irc.freenode.net:6667]]> + + + + + irc.oftc.net:6667]]> + + + + + irc.undernet.org:6667]]> + + + + + + + +Hi all, + +I use Autocomplete 1.0 Alpha mostly for form inputs bound to foreign +key columns. For instance I have a user_position table with two +columns: user_id and position_id. On new appointment form I have two +autocomplete text inputs with the following code: + + + + +As you can see the inputs do not have a name attribute, and when the +form is submitted their values are not sent, which is all right since +they will contain strings like: + + 'John Doe' + 'Sales Manager' + +whereas our backend expects something like: + + 23 + 14 + +which are the user_id for John Doe and position_id for Sales Manager. +To send these values I have two hidden inputs in the form like this: + + + + +Also I have the following code in the $().ready function: + + $("#user_id").result(function(event, data, formatted) { + $("input[@name=user_id]").val(data[1]); + }); + $("#position_id").result(function(event, data, formatted) { + $("input[@name=position_id]").val(data[1]); + }); + +As could be seen these functions stuff user_id and position_id values +(in our example 23 and 14) into the hidden inputs, and when the form +is submitted these values are sent: + + user_id = 23 + position_id = 14 + +The backend script then takes care of adding a record to our +user_position table containing those values. + +I wonder how could the plugin code be modified to simplify the setup +by taking care of adding hidden inputs and updating the value of +hidden inputs as default behavior. I have successfully attempted a +simpler solution - writing a wrapper to perform these additional tasks +and invoke autocomplete as well. I hope my intention is clear enough, +if not, this is exactly the expected outcome: + +Before: + + + + +After: + + + + + +Last word, I know this looks like a tall order, and I do not hope +someone will make a complete working mod for me, but rather would very +much appreciate helpful advise and directions. + +Many thanks in advance +Majid + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,21 @@ +/** + * @file + * Adds some styles to the standard Drupal autocomplete widget. + */ +.apachesolr_autocomplete { +} +.apachesolr_autocomplete.message { + font-size:80%; + color:#888; +} +.apachesolr_autocomplete.count { + float:right; +} +div.apachesolr_autocomplete.suggestion { + display:inline; + float:left; +} +div.ac_results li { + list-style:none; + background-image: none !important; +} diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,13 @@ +name = Search Solr autocomplete +description = Enables autocomplete on Search Solr search boxes +dependencies[] = solrsearch +dependencies[] = solrsearch_search +package = SolrSearch +core = "7.x" + +; Information added by drupal.org packaging script on 2012-08-29 +version = "7.x-1.3" +core = "7.x" +project = "solrsearch_autocomplete" +datestamp = "1346272315" + diff -r 000000000000 -r 015d06b10d37 sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/custom/solrsearch_autocomplete/solrsearch_autocomplete.install Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,17 @@ + array('path' => url('solrsearch_autocomplete'))), 'setting'); + } +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_search_form_alter(&$form, $form_state) { + + if ($form['module']['#value'] == 'solrsearch_search' || $form['module']['#value'] == 'solrsearch_multisitesearch') { + $element = &$form['basic']['keys']; + solrsearch_autocomplete_do_alter($element); + } +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_solrsearch_search_block_form_alter(&$form, $form_state) { + + $element = &$form['solrsearch_search_block_form']; + solrsearch_autocomplete_do_alter($element,"title_s,title,author"); +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_solrsearch_search_author_block_form_alter(&$form, $form_state) { + + $element = &$form['solrsearch_search_author_block_form']; + + solrsearch_autocomplete_do_alter($element,"author"); +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + */ +function solrsearch_autocomplete_form_solrsearch_search_title_block_form_alter(&$form, $form_state) { + + $element = &$form['solrsearch_search_title_block_form']; + + solrsearch_autocomplete_do_alter($element,"title_s,title"); +} +/** + * Helper function to do the actual altering of search forms. + * + * @param $element + * The element to alter. Should be passed by reference so that original form + * element will be altered. + * E.g.: solrsearch_autocomplete_do_alter(&$form['xyz']) + */ +function solrsearch_autocomplete_do_alter(&$element,$field_name) { + + if (solrsearch_autocomplete_variable_get_widget() == 'custom') { + // Create elements if they do not exist. + if (!isset($element['#attributes'])) { + $element['#attributes'] = array(); + } + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = array(); + } + array_push($element['#attributes']['class'], 'solrsearch-autocomplete', 'unprocessed','solrsearch-autocomplete.field.'.$field_name); + + } + else { + $element['#autocomplete_path'] = 'solrsearch_autocomplete'; + } +} + +/** + * Implementation of hook_menu(). + */ +function solrsearch_autocomplete_menu() { + $items = array(); + + $items['solrsearch_autocomplete'] = array( + 'page callback' => 'solrsearch_autocomplete_callback', + 'access callback' => 'user_access', + 'access arguments' => array('search content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Callback for url solrsearch_autocomplete/autocomplete. + * @param $keys + * The user-entered query. + */ +function solrsearch_autocomplete_callback($keys = '', $field_name='') { + + + if (solrsearch_autocomplete_variable_get_widget() == 'custom') { + // Keys for custom widget come from $_GET. + $keys = $_GET['query']; + $field_names =$_GET['fieldName']; + } + + /* + $res = split("\:",$keys); + + if (count($res) == 2){ + $keys=$res[1]; + $field_name=$res[0]; + } else { + $keys=$res[0]; + $field_name=""; + } + */ + + $suggestions = array(); + + foreach (explode(",",$field_names) as $field_name) { + + $suggestions = array_merge($suggestions, solrsearch_autocomplete_suggest_word_completion($keys, 5,$field_name)); + if (solrsearch_autocomplete_variable_get_suggest_keywords() || solrsearch_autocomplete_variable_get_suggest_spellcheck()) { + $suggestions = array_merge($suggestions, solrsearch_autocomplete_suggest_additional_term($keys, 5,$field_name)); + + } + } + $result = array(); + if (solrsearch_autocomplete_variable_get_widget() == 'custom') { + // Place suggestions into new array for returning as JSON. + foreach ($suggestions as $key => $display) { + $result[] = array( + "key" => substr($key,1), + "display" => $display + ); + } + } + else { + foreach ($suggestions as $key => $display) { + $result[substr($key,1)] =$display; + } + } + drupal_json_output($result); + exit(); +} + +/** + * Implementation of hook_theme(). + */ +function solrsearch_autocomplete_theme() { + return array( + 'solrsearch_autocomplete_highlight' => array( + 'file' => 'solrsearch_autocomplete.module', + 'arguments' => array( + 'keys' => NULL, + 'suggestion' => NULL, + 'count' => NULL, + ), + ), + 'solrsearch_autocomplete_spellcheck' => array( + 'file' => 'solrsearch_autocomplete.module', + 'arguments' => array( + 'suggestion' => NULL, + ), + ), + ); +} + +/** + * Themes each returned suggestion. + */ +function theme_solrsearch_autocomplete_highlight($variables) { + static $first = true; + $html = ''; + $html .= '
          '; + $html .= '' . drupal_substr($variables['suggestion'], 0, strlen($variables['keys'])) . '' . drupal_substr($variables['suggestion'], strlen($variables['keys'])); + $html .= '
          '; + if ($variables['count'] && $variables['show_counts']) { + if ($first) { + $html .= "
          "; + $html .= t('!count results', array('!count' => $variables['count'])); + $html .= "

          "; + $first = false; + } else { + $html .= "
          " . $variables['count'] . "

          "; + } + } + return $html; +} + +/** + * Themes the spellchecker's suggestion. + */ +function theme_solrsearch_autocomplete_spellcheck($variables) { + return '' . t('Did you mean') .': ' . $variables['suggestion']; +} + +/** + * Return the basic set of parameters for the Solr query. + * + * @param $suggestions_to_return + * Number of facets to return. + * @return array + */ +function solrsearch_autocomplete_basic_params($suggestions_to_return,$facet_name) { + return array( + 'facet' => 'true', + 'facet.field' => array($facet_name), + // We ask for $suggestions_to_return * 5 facets, because we want + // not-too-frequent terms (will be filtered below). 5 is just my best guess. + 'facet.limit' => $suggestions_to_return * 5, + 'facet.mincount' => 1, + 'start' => 0, + 'rows' => 0, + ); +} + +/** + * Helper function that suggests ways to complete partial words. + * + * For example, if $keys = "learn", this might return suggestions like: + * learn, learning, learner, learnability. + * The suggested terms are returned in order of frequency (most frequent first). + * + */ +function solrsearch_autocomplete_suggest_word_completion($keys, $suggestions_to_return = 5, $facet_name="author") { + /** + * Split $keys into two: + * $first_part will contain all complete words (delimited by spaces). Can be empty. + * $last_part is the (assumed incomplete) last word. If this is empty, don't suggest. + * Example: + * $keys = "learning dis" : $first_part = "learning", $last_part = "dis" + */ + preg_match('/^(:?(.* |))([^ ]+)$/', $keys, $matches); + $first_part = @$matches[2]; + // Make sure $last_part contains meaningful characters + $last_part = preg_replace('/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']+/u', '', @$matches[3]); + if ($last_part == '') { + return array(); + } + // Ask Solr to return facets that begin with $last_part; these will be the suggestions. + $params = solrsearch_autocomplete_basic_params($suggestions_to_return,$facet_name); + $params['facet.prefix'] = $last_part; + // Get array of themed suggestions. + $result = solrsearch_autocomplete_suggest($first_part, $params, 'solrsearch_autocomplete_highlight', $keys, $suggestions_to_return); + if ($result && $result['suggestions']) { + return $result['suggestions']; + } else { + return array(); + } +} + +/** + * Helper function that suggests additional terms to search for. + * + * For example, if $keys = "learn", this might return suggestions like: + * learn student, learn school, learn mathematics. + * The suggested terms are returned in order of frequency (most frequent first). + */ +function solrsearch_autocomplete_suggest_additional_term($keys, $suggestions_to_return = 5, $facet_name="author") { + $keys = trim($keys); + $keys = check_plain($keys); + if ($keys == '') { + return array(); + } + // Return no suggestions when $keys consists of only word delimiters + if (drupal_strlen(preg_replace('/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']+/u', '', $keys)) < 1) { + return array(); + } + + // Ask Solr to return facets from the 'spell' field to use as suggestions. + $params = solrsearch_autocomplete_basic_params($suggestions_to_return,$facet_name); + + // Initialize arrays + $suggestions = array(); + $replacements = array(); + + // Get array of themed suggestions. + $result = solrsearch_autocomplete_suggest($keys, $params, 'solrsearch_autocomplete_highlight', $keys, $suggestions_to_return); + if ($result && solrsearch_autocomplete_variable_get_suggest_keywords()) { + if (isset($result['suggestions']) && sizeof($result['suggestions'])) { + $suggestions = array_merge($suggestions, $result['suggestions']); + } + } + + // Suggest using the spellchecker + if (solrsearch_autocomplete_variable_get_suggest_spellcheck()) { + if (isset($result['response']->spellcheck) && isset($result['response']->spellcheck->suggestions)) { + $spellcheck_suggestions = get_object_vars($result['response']->spellcheck->suggestions); + foreach($spellcheck_suggestions as $word => $value) { + $replacements[$word] = $value->suggestion[0]; + } + if (count($replacements)) { + $new_keywords = strtr($keys, $replacements); + if ($new_keywords != $keys) { + // Place spellchecker suggestion before others + $suggestions = array_merge(array('*' . $new_keywords => theme('solrsearch_autocomplete_spellcheck', array('suggestion' => $new_keywords))), $suggestions); + } + } + } + } + + return $suggestions; +} + + +function solrsearch_autocomplete_suggest($keys, $params, $theme_callback, $orig_keys, $suggestions_to_return = 5) { + $matches = array(); + $suggestions = array(); + $keys = trim($keys); + $show_counts = solrsearch_autocomplete_variable_get_counts(); + + // We need the keys array to make sure we don't suggest words that are already + // in the search terms. + $keys_array = explode(' ', $keys); + $keys_array = array_filter($keys_array); + + // Query Solr for $keys so that suggestions will always return results. + $query = solrsearch_drupal_query($keys); + + // This hook allows modules to modify the query and params objects. + drupal_alter('solrsearch_query', $query); + if (!$query) { + return array(); + } + solrsearch_search_add_spellcheck_params($query); + foreach ($params as $param => $paramValue) { + $query->addParam($param, $paramValue); + } + solrsearch_search_add_boost_params($query); + + // Query Solr + $response = $query->search($keys); + // Loop through requested fields and get suggestions. + foreach ($params['facet.field'] as $field) { + foreach ($response->facet_counts->facet_fields->{$field} as $terms => $count) { + + + + $terms = preg_replace('/[_-]+/', ' ', $terms); + $term=$terms; + if ($term) { + + if (isset($matches[$term])) { + $matches[$term] += $count; + } + else { + $matches[$term] = $count; + } + } + + } + } + + if (sizeof($matches) > 0) { + // Eliminate suggestions that are stopwords or are already in the query. + $matches_clone = $matches; + $stopwords = solrsearch_autocomplete_get_stopwords(); + foreach ($matches_clone as $term => $count) { + if ((strlen($term) > 3) && !in_array($term, $stopwords) && !array_search($term, $keys_array)) { + // Longer strings get higher ratings. + #$matches_clone[$term] += strlen($term); + } + else { + unset($matches_clone[$term]); + unset($matches[$term]); + } + } + + // Don't suggest terms that are too frequent (in >90% of results). + $max_occurence = $response->response->numFound * 0.90; + foreach ($matches_clone as $match => $count) { + if ($count > $max_occurence) { + unset($matches_clone[$match]); + } + } + + // The $count in this array is actually a score. We want the highest ones first. + arsort($matches_clone); + + // Shorten the array to the right ones. + $matches_clone = array_slice($matches_clone, 0, $suggestions_to_return, TRUE); + + // Add current search as suggestion if results > 0 + if ($response->response->numFound > 0 && $keys != '') { + // Add * to array element key to force into a string, else PHP will + // renumber keys that look like numbers on the returned array. + $suggestions['*' . $keys] = theme('solrsearch_autocomplete_highlight', array('keys' => $keys, 'suggestion' => $keys, 'count' => $response->response->numFound, 'show_counts' => $show_counts)); + } + + // Build suggestions using returned facets + foreach ($matches_clone as $match => $count) { + if ($keys != $match) { + $suggestion = trim($keys . ' ' . $match); + // On cases where there are more than 3 keywords, omit displaying + // the count because of the mm settings in solrconfig.xml + if (substr_count($suggestion, ' ') >= 2) { + $count = 0; + } + if ($suggestion != '') { + // Add * to array element key to force into a string, else PHP will + // renumber keys that look like numbers on the returned array. + $suggestions['*' . $suggestion] = theme('solrsearch_autocomplete_highlight', array('keys' => $orig_keys, 'suggestion' => $suggestion, 'count' => $count, 'show_counts' => $show_counts)); + } + } + } + } + + return array( + 'suggestions' => $suggestions, + 'response' => &$response + ); +} + +/** + * Gets the current stopwords list configured in Solr. + */ +function solrsearch_autocomplete_get_stopwords() { + static $words = array(), $flag = false; + if ($flag) { + return $words; + } + $stopwords_url = "/admin/file/?file=stopwords.txt"; + $host = variable_get('solrsearch_host', 'localhost'); + $port = variable_get('solrsearch_port', 8983); + $path = variable_get('solrsearch_path', '/solr'); + $url = "http://{$host}:{$port}{$path}{$stopwords_url}"; + $result = drupal_http_request($url); + if ($result->code != 200) { + return array(); + } + $words = array(); + foreach (explode("\n", $result->data) as $line) { + if (drupal_substr($line, 0, 1) == "#") { + continue; + } + if ($word = trim($line)) { + $words[] = $word; + } + } + $flag = true; + return $words; +} + +/** + * Wrapper around variable_get() for variable solrsearch_autocomplete_widget. + */ +function solrsearch_autocomplete_variable_get_widget() { + return variable_get('solrsearch_autocomplete_widget', 'custom'); +} + +/** + * Wrapper around variable_get() for variable solrsearch_autocomplete_suggest_keywords. + */ +function solrsearch_autocomplete_variable_get_suggest_keywords() { + return variable_get('solrsearch_autocomplete_suggest_keywords', 1); +} + +/** + * Wrapper around variable_get() for variable solrsearch_autocomplete_suggest_spellcheck. + */ +function solrsearch_autocomplete_variable_get_suggest_spellcheck() { + return variable_get('solrsearch_autocomplete_suggest_spellcheck', 1); +} + +/** + * Wrapper around variable_get() for variable solrsearch_autocomplete_counts. + */ +function solrsearch_autocomplete_variable_get_counts() { + return variable_get('solrsearch_autocomplete_counts', TRUE); +} + +/** + * Alter the solrsearch.module "advanced settings" form. + */ +function solrsearch_autocomplete_form_solrsearch_settings_alter(&$form, $form_state) { + $form['advanced']['solrsearch_autocomplete_widget'] = array( + '#type' => 'radios', + '#title' => t('Autocomplete widget to use'), + '#description' => t('The custom widget provides instant search upon selection, whereas the Drupal widget needs the user to hit Enter or click on the Search button. If you are having problems, try switching to the default Drupal autocomplete widget.'), + '#options' => array('custom' => t('Custom autocomplete widget'), 'drupal' => t('Drupal core autocomplete widget')), + '#default_value' => solrsearch_autocomplete_variable_get_widget(), + ); + $form['advanced']['solrsearch_autocomplete_suggest_keywords'] = array( + '#type' => 'checkbox', + '#title' => t('Enable additional keyword suggestions on the autocomplete widget'), + '#description' => t('Suggest words to add to the currently typed-in words. E.g.: typing "blue" might suggest "blue bike" or "blue shirt".'), + '#default_value' => solrsearch_autocomplete_variable_get_suggest_keywords(), + ); + $form['advanced']['solrsearch_autocomplete_suggest_spellcheck'] = array( + '#type' => 'checkbox', + '#title' => t('Enable spellchecker suggestions on the autocomplete widget'), + '#description' => t('Suggest corrections to the currently typed-in words. E.g.: typing "rec" or "redd" might suggest "red".'), + '#default_value' => solrsearch_autocomplete_variable_get_suggest_spellcheck(), + ); + $form['advanced']['solrsearch_autocomplete_counts'] = array( + '#type' => 'checkbox', + '#title' => t('Enable counts in autocomplete widget suggestions'), + '#description' => t('WARNING: Counts shown alongside suggestions might be lower than the actual result count due to stemming and minimum match (mm) settings in solrconfig.xml.'), + '#default_value' => solrsearch_autocomplete_variable_get_counts(), + ); +} diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/README.txt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,25 @@ + +ABOUT STARK +----------- + +The Stark theme is provided for demonstration purposes; it uses Drupal's default +HTML markup and CSS styles. It can be used as a troubleshooting tool to +determine whether module-related CSS and JavaScript are interfering with a more +complex theme, and can be used by designers interested in studying Drupal's +default markup without the interference of changes commonly made by more complex +themes. + +To avoid obscuring CSS added to the page by Drupal or a contrib module, the +Stark theme itself has no styling, except just enough CSS to arrange the page in +a traditional "Header, sidebars, content, and footer" layout. See the layout.css +file for more information. + + +ABOUT DRUPAL THEMING +-------------------- + +To learn how to build your own custom theme and override Drupal's default code, +see the Theming Guide: http://drupal.org/theme-guide + +See the sites/all/themes/README.txt for more information on where to place your +custom themes to ensure easy maintenance and upgrades. diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/block.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/block.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,56 @@ +subject: Block title. + * - $content: Block content. + * - $block->module: Module that generated the block. + * - $block->delta: An ID for the block, unique within each module. + * - $block->region: The block region embedding the current block. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - block: The current template type, i.e., "theming hook". + * - block-[module]: The module generating the block. For example, the user + * module is responsible for handling the default user navigation block. In + * that case the class would be 'block-user'. + * - $title_prefix (array): An array containing additional output populated by + * modules, intended to be displayed in front of the main title tag that + * appears in the template. + * - $title_suffix (array): An array containing additional output populated by + * modules, intended to be displayed after the main title tag that appears in + * the template. + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. + * - $zebra: Same output as $block_zebra but independent of any block region. + * - $block_id: Counter dependent on each block region. + * - $id: Same output as $block_id but independent of any block region. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * - $block_html_id: A valid HTML ID and guaranteed unique. + * + * @see template_preprocess() + * @see template_preprocess_block() + * @see template_process() + * + * @ingroup themeable + */ +?> +
          > + +subject): ?> + >subject ?> + + +
          > + +
          +
          diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/base.png Binary file sites/all/themes/mpiwgDev/color/base.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/color.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/color/color.inc Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,103 @@ + t('Web Main (Default)'), + +// Put the logo path into JavaScript for the live preview. +drupal_add_js(array('color' => array('logo' => theme_get_setting('logo', 'mpiwgDev'))), 'setting'); + +$info = array( + // Available colors and color labels used in theme. + 'fields' => array( + 'headings' => t('Ueberschriften'), + 'base' => t('Hintergrund'), + 'top' => t('Hintergrud Content'), + 'sidebarborders' => t('Trennstriche'), + 'titleslogan' => t('Title and slogan'), + 'text' => t('Text color'), + 'link' => t('Link color'), + 'searchbox' => t('Search box'), + + ), + // Pre-defined color schemes. + 'schemes' => array( + 'default' => array( + 'title' => t('MPIWG (default)'), + 'colors' => array( + 'base' => '#ebecf3', + 'top' => '#f8f8f9', + 'sidebarborders' => '#9da0c2', + 'titleslogan' => '#3c4286', + 'text' => '#000000', + 'link' => '#000b86', + 'headings' => '#c10000', + 'searchbox'=> '#ebecf3' + ), + ), + 'institute_add' => array( + 'title' => t('Additional institute sites'), + 'colors' => array( + 'base' => '#dce8e8', + 'top' => '#f8f8f9', + 'sidebarborders' => '#9da0c2', + 'titleslogan' => '#3c4286', + 'text' => '#000000', + 'link' => '#3c4286', + 'headings' => '#00b2a3', + 'searchbox'=> '#ebecf3' + ), + ), + 'institute_add2' => array( + 'title' => t('Additional institute sites II'), + 'colors' => array( + 'base' => '#f4f9e5', + 'top' => '#f8f8f9', + 'sidebarborders' => '#9da0c2', + 'titleslogan' => '#3c4286', + 'text' => '#000000', + 'link' => '#3c4286', + 'headings' => '#79a000', + 'searchbox'=> '#ebecf3' + ), + ), + ), + + // CSS files (excluding @import) to rewrite with new color scheme. + 'css' => array( + 'layout.css', + ), + + // Files to copy. + 'copy' => array( + 'logo.png', + ), + + // Gradient definitions. + 'gradients' => array( + array( + // (x, y, width, height). + 'dimension' => array(0, 0, 0, 0), + // Direction of gradient ('vertical' or 'horizontal'). + 'direction' => 'vertical', + // Keys of colors to use for the gradient. + 'colors' => array('top', 'bottom'), + ), + ), + + // Color areas to fill (x, y, width, height). + 'fill' => array(), + + // Coordinates of all the theme slices (x, y, width, height) + // with their filename as used in the stylesheet. + 'slices' => array(), + + // Reference color used for blending. Matches the base.png's colors. + 'blend_target' => '#ffffff', + + // Preview files. + 'preview_css' => 'color/preview.css', + 'preview_js' => 'color/preview.js', + 'preview_html' => 'color/preview.html', + + // Base file for image generation. + 'base_image' => 'color/base.png', +); diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/preview.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/color/preview.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,200 @@ + +/* ---------- Color form ----------- */ +#color_scheme_form #palette .form-item { + width: 25em; +} +#color_scheme_form #palette .form-item label { + width: 15em; +} + +/* ---------- Preview Styles ----------- */ + +html.js #preview { + clear: both; + float: none !important; +} +#preview { + background-color: #fff; + font-family: Georgia, "Times New Roman", Times, serif; + font-size: 14px; + line-height: 1.5; + overflow: hidden; + word-wrap: break-word; + margin-bottom: 10px; +} +#preview-header { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + position: relative; +} +#preview-logo { + float: left; + padding: 15px 15px 15px 10px; +} +#preview-site-name { + color: #686868; + font-weight: normal; + font-size: 1.821em; + line-height: 1; + margin-bottom: 30px; + margin-left: 15px; + padding-top: 34px; +} +#preview-main-menu { + clear: both; + padding: 0 15px 3px; +} +#preview-main-menu-links a { + color: #d9d9d9; + padding: 0.6em 1em 0.4em; +} +#preview-main-menu-links { + font-size: 0.929em; + margin: 0; + padding: 0; +} +#preview-main-menu-links a { + color: #333; + background: #ccc; + background: rgba(255, 255, 255, 0.7); + text-shadow: 0 1px #eee; + -khtml-border-radius-topleft: 8px; + -moz-border-radius-topleft: 8px; + -webkit-border-top-left-radius: 8px; + border-top-left-radius: 8px; + -khtml-border-radius-topright: 8px; + -moz-border-radius-topright: 8px; + -webkit-border-top-right-radius: 8px; + border-top-right-radius: 8px; +} +#preview-main-menu-links a:hover, +#preview-main-menu-links a:focus { + background: #fff; + background: rgba(255, 255, 255, 0.95); +} +#preview-main-menu-links a:active { + background: #b3b3b3; + background: rgba(255, 255, 255, 1); +} +#preview-main-menu-links li a.active { + border-bottom: none; +} +#preview-main-menu-links li { + display: inline; + list-style-type: none; + padding: 0.6em 0 0.4em; +} +#preview-sidebar, +#preview-content { + display: inline; + float: left; + position: relative; +} +#preview-sidebar { + margin-left: 15px; + width: 210px; +} +#preview-content { + margin-left: 30px; + width: 26.5em; +} +#preview-sidebar .preview-block { + border: 1px solid; + margin: 20px 0; + padding: 15px 20px; +} +#preview-sidebar h2 { + border-bottom: 1px solid #d6d6d6; + font-size: 1.071em; + font-weight: normal; + line-height: 1.2; + margin: 0 0 0.5em; + padding-bottom: 5px; + text-shadow: 0 1px 0 #fff; +} +#preview .preview-block .preview-content { + margin-top: 1em; +} +#preview .preview-block-menu .preview-content, +#preview .preview-block-menu .preview-content ul { + margin-top: 0; +} +#preview-main { + margin-bottom: 40px; + margin-top: 20px; +} +#preview-page-title { + font-size: 2em; + font-weight: normal; + line-height: 1; + margin: 1em 0 0.5em; +} +#preview-footer-wrapper { + color: #c0c0c0; + color: rgba(255, 255, 255, 0.65); + display: block !important; + font-size: 0.857em; + padding: 20px 20px 25px; +} +#preview-footer-wrapper a { + color: #fcfcfc; + color: rgba(255, 255, 255, 0.8); +} +#preview-footer-wrapper a:hover, +#preview-footer-wrapper a:focus { + color: #fefefe; + color: rgba(255, 255, 255, 0.95); + text-decoration: underline; +} +#preview-footer-wrapper .preview-footer-column { + display: inline; + float: left; + padding: 0 10px; + position: relative; + width: 220px; +} +#preview-footer-wrapper .preview-block { + border: 1px solid #444; + border-color: rgba(255, 255, 255, 0.1); + margin: 20px 0; + padding: 10px; +} +#preview-footer-columns .preview-block-menu { + border: none; + margin: 0; + padding: 0; +} +#preview-footer-columns h2 { + border-bottom: 1px solid #555; + border-color: rgba(255, 255, 255, 0.15); + font-size: 1em; + margin-bottom: 0; + padding-bottom: 3px; + text-transform: uppercase; +} +#preview-footer-columns .preview-content { + margin-top: 0; +} +#preview-footer-columns .preview-content ul { + margin-left: 0; + padding-left: 0; +} +#preview-footer-columns .preview-content li { + list-style: none; + list-style-image: none; + margin: 0; + padding: 0; +} +#preview-footer-columns .preview-content li a { + border-bottom: 1px solid #555; + border-color: rgba(255, 255, 255, 0.15); + display: block; + line-height: 1.2; + padding: 0.8em 2px 0.8em 20px; + text-indent: -15px; +} +#preview-footer-columns .preview-content li a:hover, +#preview-footer-columns .preview-content li a:focus { + background-color: #1f1f21; + background-color: rgba(255, 255, 255, 0.05); + text-decoration: none; +} diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/preview.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/color/preview.html Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,65 @@ +
          + +
          + +
          Bartik
          + +
          + +
          +
          +
          +

          Etiam est risus

          +
          + Maecenas id porttitor Ut enim ad minim veniam, quis nostrudfelis. + Laboris nisi ut aliquip ex ea. +
          +
          +
          +
          +

          Lorem ipsum dolor

          +
          +
          + Sit amet, consectetur adipisicing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis + nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Maecenas id porttitor Ut enim ad minim veniam, quis nostr udfelis. +
          +
          +
          +
          + + + +
          diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/preview.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/color/preview.js Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,39 @@ + +(function ($) { + Drupal.color = { + logoChanged: false, + callback: function(context, settings, form, farb, height, width) { + // Change the logo to be the real one. + if (!this.logoChanged) { + $('#preview #preview-logo img').attr('src', Drupal.settings.color.logo); + this.logoChanged = true; + } + // Remove the logo if the setting is toggled off. + if (Drupal.settings.color.logo == null) { + $('div').remove('#preview-logo'); + } + + // Solid background. + $('#preview', form).css('backgroundColor', $('#palette input[name="palette[bg]"]', form).val()); + + // Text preview. + $('#preview #preview-main h2, #preview .preview-content', form).css('color', $('#palette input[name="palette[text]"]', form).val()); + $('#preview #preview-content a', form).css('color', $('#palette input[name="palette[link]"]', form).val()); + + // Sidebar block. + $('#preview #preview-sidebar #preview-block', form).css('background-color', $('#palette input[name="palette[sidebar]"]', form).val()); + $('#preview #preview-sidebar #preview-block', form).css('border-color', $('#palette input[name="palette[sidebarborders]"]', form).val()); + + // Footer wrapper background. + $('#preview #preview-footer-wrapper', form).css('background-color', $('#palette input[name="palette[footer]"]', form).val()); + + // CSS3 Gradients. + var gradient_start = $('#palette input[name="palette[top]"]', form).val(); + var gradient_end = $('#palette input[name="palette[bottom]"]', form).val(); + + $('#preview #preview-header', form).attr('style', "background-color: " + gradient_start + "; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(" + gradient_start + "), to(" + gradient_end + ")); background-image: -moz-linear-gradient(-90deg, " + gradient_start + ", " + gradient_end + ");"); + + $('#preview #preview-site-name', form).css('color', $('#palette input[name="palette[titleslogan]"]', form).val()); + } + }; +})(jQuery); diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/color/preview.png Binary file sites/all/themes/mpiwgDev/color/preview.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/icons/blue/Download.png Binary file sites/all/themes/mpiwgDev/icons/blue/Download.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/icons/blue/Email.png Binary file sites/all/themes/mpiwgDev/icons/blue/Email.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/icons/blue/IconsSatellitenseiten.psd Binary file sites/all/themes/mpiwgDev/icons/blue/IconsSatellitenseiten.psd has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/icons/blue/LinkExtern.png Binary file sites/all/themes/mpiwgDev/icons/blue/LinkExtern.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/icons/blue/LinkIntern.png Binary file sites/all/themes/mpiwgDev/icons/blue/LinkIntern.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/layout.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/layout.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1343 @@ + +/** + * @file + * Stark layout method + * + * To avoid obscuring CSS added to the page by Drupal or a contrib module, the + * Stark theme itself has no styling, except just enough CSS to arrange the page + * in a traditional "Header, sidebars, content, and footer" layout. + * + * This layout method works reasonably well, but shouldn't be used on a + * production site because it can break. For example, if an over-large image + * (one that is wider than 20% of the viewport) is in the left sidebar, the + * image will overlap with the #content to the right. The exception to this + * is IE6 which will just hide the navigation block completely in these + * instances due to a positioning bug. + */ + + +/* MPIWG addtional syles for drupal */ + +/* + * return to mpiwg + */ + + +/* suchseite */ +div.form-item-exact{ + display: block; +} + + + /* entspricht .line*/ + +li.node-readmore a { + background: url("./icons/blue/LinkIntern.png") center left no-repeat; + padding-left: 10px; +} +.pane-title{ + border-bottom: 3px solid #9DA0C2; + margin-bottom: 5px; + padding-bottom: 5px; +} + + + + +td.key{ + color :rgb(59, 65, 134); + + } + + div.main{ + width: 75%; + } + + div.title h1, + div.title h2 + { + color: #3c4286; + } + +.mpiwg_internal_view{ + background-color: red; + } + .webSearch_hl { + font-weight: bold; + } + + + +#mpiwg_link a { + background-color: #ebecf3; + color: #3b4186; + padding: 3px 10px 3px 10px; + font-style: italic; + +} + +#header div.title img.logo_left { + width: 180px; + height: 100%; + vertical-align: top; + padding: 0px 5px 20px 0px; +} + +div.logo_left { +float: left; +} + +#header div.title { + width: 750px; + + +} + +div.subnav .sn_off { + padding-left: 0px; + background-color: #f8f8f9; + font-weight: normal; +} + +div.subnav li.sn_off a{ + padding-left: 0px; + color: #c10000; + font-weight: normal; +} + +ul li.leaf{ + list-style-image: none; +} + +ul li.expanded{ + list-style-image: none; +} + +ul li.collapsed{ + list-style-image: none; +} + +ul.menu li{ + margin-left : 0px; +} + +ul.inline li { + display: inline; + padding: 0; +} + +/* MPIWG website style sheet + * + * (c) 2013 MPIWG Berlin + * Author: Robert Casties + */ +body { + margin: 0; + font-family: Verdana, Arial, sans-serif; + background-color: #ebecf3; + font-size: 12px; +} + +/* + * global styles + */ +img { + border: none; +} + +a { + color: #000b86; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* link-classes (with icons) */ +a.internal, +a.more { + background: url(../images/internal.png) center left no-repeat; + padding-left: 10px; +} + +a.download { + background: url(../images/download.png) center left no-repeat; + padding-left: 15px; +} + +a.external { + background: url(../images/external.png) center left no-repeat; + padding-left: 15px; +} + +a.down { + background: url(../images/down.png) center left no-repeat; + padding-left: 15px; +} + +a.jumptop { + background: url(../images/jump_top.png) center left no-repeat; + padding-left: 15px; +} + +a.email { + background: url(../images/email.png) center left no-repeat; + padding-left: 23px; +} + +/* old blue mail link */ +a.maillink { + color: #000b86 !important; +} + +h1 { + color: #c10000; + font-size: 20px; + font-weight: normal; + margin: 0.5em 0; +} + +h1:first-child { + /* does this work correctly? */ + margin-top: 0; +} + +h2 { + color: #c10000; + font-size: 16px; + font-weight: normal; + margin: 0.5em 0; +} + +h3 { + color: #c10000; + font-size: 14px; + font-weight: normal; + margin: 0; +} + +h3 + h3 { + /* h3 directly after h3 has a margin */ + margin-top: 0.5em; +} + +h1 a { + /* a header with a link looks like a header */ + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +p { + margin: 0.5em 0; + line-height: 1.5; +} + +ul.plain { + list-style-type: none; + padding-left: 0.5em; +} +ul.plain > li { + margin-top: 0.5em; +} + +ul.inline { + display: inline; + padding: 0; + margin: 0; +} + +ul.inline li { + display: inline; +} + +/* + * table-like rows and columns + */ + +div.rows { + display: table; +} + +div.row { + display: table-row; +} + +div.row>div.col { + display: table-cell; + vertical-align: top; +} + +div.row>div.col:first-child { + /* first block has no left margin */ + padding-left: 0 !important; +} + +/* + * page wrapper and header + */ +#wrapper { + width: 900px; + margin: 0 auto; + padding: 10px 40px 10px 20px; + background-color: #f8f8f9; + box-shadow: 0 0 5px 3px #f8f8f9; +} + +#header { + display: block; + position: relative; + height: 95px; + margin: 0 0 10px 0; +} + +#header div.title { + position: absolute; + left: 0; + top: 25px; +} + +#header div.title img { + width: 661px; + height: 40px; +} + +#header div.logo { + position: absolute; + right: 0; +} + +#header div.logo img { + + height: 88px; +} + +/* + * Main Navigation + */ +#mainnav { + display: block; + position: relative; + font-family: Verdana, Arial, sans-serif; + font-size: 12px; + border-top: 8px solid #9da0c2; + padding-top: 5px; + margin-bottom: 15px; + /* make iPhone-Safari not botch text size */ + -webkit-text-size-adjust: 100%; +} + +#mainnav ul { + padding: 0; + margin: 0; +} + +#mainnav li.sec { + display: inline; + border-top: 8px solid #9da0c2; + margin: 0 35px 0 0; + padding-top: 5px; +} + +#mainnav li.sec:hover { + border-top: 8px solid #c10000; +} + +#mainnav a { + color: #696968; + outline: none; +} + +#mainnav a:hover { + color: #000000; + text-decoration: none; +} + +#mainnav li.sec.on { + border-top: 8px solid #c10000; +} + +#mainnav li.sec.on a { + color: #3c4286; + outline: none; + font-weight: bold; +} + +#mainnav li.sec.lang { + position: absolute; + right: 0; + top: -8px; + margin-right: 0; +} + +/* + * breadcrumbs + */ +#breadcrumbs { + font-size: 11px; + color: #6a4d3c; + margin-bottom: 5px; +} + +#breadcrumbs a { + color: #6a4d3c; +} + +#breadcrumbs .selected { + font-weight: bold; +} + +/* + * main section wrapper + */ +#mainrow { + display: table-row; +} + +/* + * subnavigation + */ +div.leftbox { + display: table-cell; + vertical-align: top; + width: 170px; + border-top: 3px solid #9da0c2; +} + +div.subnav { + font-size: 11px; +} + +div.subnav ul { + margin: 0; + padding: 0; +} + +div.subnav li { + list-style-type: none; + border-bottom: 1px solid #9da0c2; + padding: 3px 6px 6px 3px; +} + +div.subnav .sn_on { + padding-left: 6px; + background-color: #c10000; +} + +div.subnav .sn_on a { + color: #f8f8f9; + font-weight: bold; +} + +div.subnav h2.subnavhead { + background-color: white; + color: #c10000; + border-bottom: 1px solid #9da0c2; + font-size: 11px; + font-weight: bold; + margin: 0; + padding: 5px 3px; +} + +div.subnav li.dept { + padding-left: 18px; + list-style-position: inside; + list-style-image: url(../images/internal.png); + text-indent: -14px; +} + +div.subnav li.dept .type { + font-style: italic; + color: #6a4d3c; +} + +div.subnav li.dept.sn_on .type { + color: #f9f4e1; +} + +div.subnavbox { + /* navigation below e.g. research projects */ + font-size: 11px; + margin-top: 30px; +} + +div.subnavbox h2 { + font-size: 11px; + color: #b69f7b; + padding-bottom: 3px; + border-bottom: 1px solid #9da0c2; + margin-top: 20px; +} + +/* + * main section + */ +div.main { + display: table-cell; + vertical-align: top; + background-color: white; + border-top: 3px solid #9da0c2; + padding: 20px; +} + +div.main div.row>div.col { + /* col default padding */ + padding-left: 20px; +} + +div.main div.center { + /* make sure center does not collapse */ + min-width: 460px; +} + + +/* + * frontpage + */ +div.main.home { + background-color: transparent; + border-top: 0; + padding: 0; +} + +div.main.home b { + /* bold is blue */ + color: #3b4186; +} + +div.main.home div.row.triple>div.col { + /* frontpage col width */ + width: 286px; + padding-left: 21px; +} + +div.box h2, +h2.line { + /* more-links in title are right */ + position: relative; +} + +div.box h2 a:link, +h2.line a.more:link { + /* more-links in title are right */ + position: absolute; + right: 0; + top: 3px; + /* more-links in title are small */ + font-size: 12px; +} + +/* + * front page feature teaser + */ +div.main.home div.teaser { + background-color: #f4e0c7; +} + +div.main.home div.teaser div.box { + /* teaser box needs no margins */ + margin: 0; +} + +div.teaser_image { + position: relative; + width: 593px; + height: 351px; +} + +div.teaser_image div.caption { + /* caption inside image */ + position: absolute; + bottom: 0; + padding: 3px; + text-align: right; + color: white; + font-size: 10px; + opacity: 0.8; + background-color: gray; + /* background-color: rgba(80,80,80,0.6); */ +} + +div.teaser_text { + padding-right: 21px; +} +/* feature number */ +div.teaser_text div.feature_number { + position: absolute; + right: 14px; + top: 5px; + font-family: Georgia, Times, serif; + font-size: 26px; + color: #887163; +} +/* feature title */ +div.teaser_text h1 { + font-size: 16px; + font-weight: normal; + margin-top: 0; +} +/* current research topic */ +div.teaser_text h3 { + color: #887163; + margin-bottom: 3px; + margin-top: 15px; +} +/* feature date */ +div.teaser_text div.date { + color: #3b4186; + padding-top: 20px; + margin-bottom: 20px; +} + +/* teaser slider */ +div.teaser div.box div.slider { + top: 150px; + width: 18px; + height: 24px; + padding-top: 11px; +} + +div.teaser div.box div.slider.prev { + background-image: url(../images/slider_prev_bg.png); +} + +div.teaser div.box div.slider.next { + background-image: url(../images/slider_next_bg.png); +} + +/* + * toolboxes + */ +div.tool.box { + font-family: Georgia, Times, serif; + font-style: italic; + font-size: 16px; + color: #887163; + background-color: #efeeec; + padding: 20px; + box-shadow: 0 0 1px 1px #e0e0e0; +} + +div.tool.box a { + color: #3b4186; +} + +div.tool.box div.searchbox { + /* only to make it shrink-wrap */ + display: table-cell; +} + +div.tool.box div.searchbox input.text { + height: 20px; + width: 370px; + background-color: #9fa0a7; + color: white; + font-size: 14px; + font-style: italic; + border: 0; + padding: 3px; +} + +div.tool.box div.searchbox input.submit { + /* submit uses border-box. don't ask */ + height: 26px; + width: 26px; + border: 0; + padding: 2px 3px 4px 3px; + /* TODO: this should be an image */ + background-color: #ee7f2d; + color: white; + font-size: 14px; +} + +div.tool.box div.searchbox div.extended { + font-family: Verdana, Arial, sans-serif; + font-size: 12px; + text-align: right; + padding: 5px 30px 5px 3px; +} + +div.tool.box h3 { + font-family: Georgia, Times, serif; + font-style: italic; + font-size: 16px; + color: #887163; + margin: 0.5em 0; +} + +div.tool.box h3:first-child { + /* top header has no margin */ + margin-top: 0; +} + +div.tool.box ul { + margin: 0; + padding: 0; +} + +div.tool.box div.options { + margin-top: 10px; +} + +div.tool.box div.options li { + list-style-type: none; + margin: 0.5em 0; +} + +div.tool.box div.atoz { + color: #887163; + font-family: Verdana, Arial, sans-serif; + font-size: 14px; + font-style: normal; + margin-top: 10px; +} + +div.tool.box input.checkbox, +div.tool.box input.radio { + /* checkbox uses border-box. don't ask */ + width: 20px; + height: 20px; + margin: 0; + /* TODO: this should be an image */ + background-color: white; +} + +/* project page toolbox with thumbs */ +div.tool.box div.box.thumbs { + width: 420px; +} + +div.box.thumbs div.thumb { + display: inline; +} +div.box.thumbs div.thumb img { + width: 55px; + height: 34px; +} + + +/* + * service box on frontpage + */ +div.box h2.service { + /* title has no line */ + border: 0; +} + +div.tool.box.service { + margin-top: 0; + padding: 0; +} + +div.tool.box.service div.row > div.col { + padding: 0; +} +div.tool.box.service div.search { + width: 200px; + height: 20px; + padding: 26px 0 26px 16px; + border-bottom: 1px solid #9fa0a7; +} +div.tool.box.service div.search.both { + height: 44px; + padding: 14px 0 14px 16px; +} +div.tool.box.service div.searchicon { + width: 38px; + height: 40px; + text-align: center; + padding: 16px; + border-bottom: 1px solid #9fa0a7; +} +div.tool.box.service div.icon { + width: 71px; + height: 70px; + text-align: center; + line-height: 2.5; + padding: 1px 0; + border-left: 1px solid #9fa0a7; +} +div.tool.box.service div.icon a { + color: black; + font-size: 12px; + font-family: Verdana, Arial, sans-serif; + font-style: normal; +} +div.tool.box.service div.icon.first { + border-left: 0; + width: 70px; +} +div.tool.box.service div.searchbox { + display: block; +} +div.tool.box.service div.searchbox input.text { + width: 170px; + height: 17px; + font-size: 12px; + padding: 1px 3px; +} +div.tool.box.service div.searchbox input.submit { + height: 19px; + width: 19px; + padding: 0; + margin: 0; + vertical-align: bottom; +} +div.tool.box.service select.quickfinder { + width: 200px; + height: 19px; + font-size: 12px; + background-color: white; + margin-bottom: 5px; + border: 0; +} + +/* + * other boxes + */ +div.box { + margin: 20px 0; + position: relative; +} + +div.box h2, +h2.line { + /* h2 is title with line */ + padding-bottom: 5px; + border-bottom: 3px solid #9da0c2; + margin-bottom: 5px; +} + +div.box.line, +div.line { + border-bottom: 1px solid #9da0c2; +} + +div.box h3 { + /* h3 is subtitle/type */ + font-size: 12px; + color: #6a4d3c; +} + +div.box h3 + h2 { + /* directly over h2 */ + margin-top: 0; +} + +/* + * small box with thumbnail + */ +div.mini { + padding: 10px 0; + /* line-height: 1; */ +} + +div.row.quintuple div.mini { + max-width: 122px; + font-size: 11px; +} + +div.mini div.thumb { + background-color: #f8f8f8; + text-align: center; + margin-top: 0.5em; +} +div.mini div.thumb:first-child { + margin-top: 0; +} +div.row.quintuple div.mini div.thumb img { + width: 120px; + height: 75px; +} + +div.row.triple div.mini { + width: 140px; +} + +div.row.triple div.mini div.thumb img { + width: 140px; + height: 87px; +} + +div.row.triple div.mini.source div.thumb img { + /* thumb size for sources given by digilib */ + width: auto; + height: auto; +} + +div.row.double div.mini div.thumb img { + height: 180px; +} + +div.mini div.type { + color: #9f917a; + margin-top: 0.5em; +} + +div.mini div.title { + margin-top: 0.5em; +} + +div.mini div.author { + color: #3b4186; + margin-top: 0.5em; +} + +div.mini div.link { + margin-top: 0.5em; +} + +div.mini h2 { + /* e.g. title for mini-books */ + font-size: 14px; +} + +div.mini.website div.description { + /* initially hidden */ + display: none; +} + +div.box.foldable div.fold { + /* initially hidden */ + display: none; + position: absolute; + bottom: 0; + right: 0; + /* TODO: this should be an image */ + background-color: #ee7f2d; + color: white; + font-size: 14px; +} + +/* + * slider buttons + */ +div.box div.slider { + position: absolute; + top: 110px; + width: 16px; + height: 18px; +} + +div.box div.slider.prev { + left: 0; +} + +div.box div.slider.next { + right: 0; + text-align: right; +} + +/* + * paragraph with line + */ +div.item { + padding-top: 5px; + padding-bottom: 5px; + border-bottom: 1px solid #9da0c2; + line-height: 1.5; +} + +/* + * pubman references + */ +.reference .Italic { + font-style: italic; +} + + +/* + * table with items (articles) + */ +table.items { + /* no space between cells */ + border-collapse: collapse; +} + +table.items td { + vertical-align: top; + text-align: left; + line-height: 1.5; + padding: 1em 1em 0.5em 0; +} +table.items td:last-child { + padding-right: 0; +} +table.items.shorter td { + padding: 0.5em 0.5em 0.5em 0; +} + +table.items th { + font-family: Georgia, Times, serif; + font-size: 16px; + font-weight: normal; + font-style: italic; + color: #887163; + text-align: left; + border-bottom: 3px solid #9da0c2; +} + +table.items h2 { + border-bottom: 3px solid #9da0c2; + margin-bottom: 0; +} +table.items h2 a { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +table.items img.thumb { + width: 55px; + height: 34px; +} + +table.items td.line, +table.items tr.line td { + border-bottom: 1px solid #9da0c2; +} + +table.items td.topline, +table.items tr.topline td { + border-top: 1px solid #9da0c2; +} + +table.items tr.last_item td { + /* last_item has no padding-top */ + padding: 0 0 0.5em 0; +} + +table.items h3.fold_head { + font-size: 12px; + background-color: #f6f2eb; + padding: 3px; +} + +table.items h3.fold_head img { + margin: 0 5px 0 3px; +} + +table.items div.fold_body { + /* e.g. preprint abstracts */ + padding: 0.5em 0 0 20px; +} + +ul.items { + list-style-type: none; + padding: 0; +} + +ul.items li { + padding: 0 0 0.5em 20px; + border-bottom: 3px solid #9da0c2; + margin-bottom: 0.5em; +} + +ul.items .fold_head { + text-indent: -20px; +} +ul.items .fold_head img.fold_open, +ul.items .fold_head img.fold_closed { + padding-right: 5px; +} + +ul.items .type { + /* font-size: 11px; */ + color: #696968; +} + +ul.items h2 { + font-size: 12px; +} + +ul.items td.key { + color: #3b4186; +} + +.foldable img.fold_closed { + /* fold is initially open */ + display: none; +} + + +/* + * hierarchical list (of projects) + */ +div.hierlist h2 { + text-indent: -20px; + padding-left: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #9da0c2; + margin-bottom: 0; +} + +div.hierlist h2 img { + padding: 0 3px 3px 3px; +} + +div.hierlist h2 a { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +div.hierlist ul { + list-style-type: none; + padding: 0 0 10px 0; + margin: 0; + border-bottom: 1px solid #9da0c2; +} + +div.hierlist li { + padding: 5px 0 5px 16px; + text-indent: -13px; +} + +div.hierlist li a { + /* icon is more distant from text */ + padding-left: 13px; +} + +div.hierlist li.indent { + padding-bottom: 10px; + border-bottom: 1px solid #9da0c2; + margin-bottom: 5px; +} + +div.hierlist li.level_1 { + font-size: 14px; +} + +div.hierlist li.level_2 { + font-weight: bold; + margin-left: 20px; +} + +div.hierlist li.level_3 { + margin-left: 40px; +} + +div.hierlist li.level_4 { + margin-left: 60px; +} + +div.hierlist li.level_5 { + margin-left: 80px; +} + + +/* + * figures (in project descriptions and features) + */ +div.figure, +div.image_small { + float: left; + width: 230px; + padding: 0.5em 1em 0 0; +} +div.image_small.right { + float: right; + padding: 0.5em 0 0 1em; +} +div.figure div.image img, +div.image_small img { + width: 230px; +} +div.figure div.figcaption, +div.banner_large div.caption, +div.image_small div.caption { + font-size: 10px; + line-height: 1.5; + color: #9f917a; + margin-top: 0.5em; +} +div.banner_large img { + width: 460px; +} + +/* + * project description and feature + */ +h3.authors { + margin-top: 0.5em; +} + +p.maintext_authors { + font-size: 14px; + color: #3b4186; +} + +div.description { + margin-top: 0.5em; + line-height: 1.5; +} + +h2.type { + color: #9f917a; +} + +/* + * feature story archive + */ +ul.items.features li { + padding: 0 0 0.5em 0; + border-bottom: 1px solid #9da0c2; +} +ul.items.features .thumb { + float:left; + padding: 8px 10px 0 0; +} +ul.items.features .thumb img { + width: 55px; + height: 34px; +} +ul.items.features .feature_number { + font-family: Georgia, Times, serif; + font-size: 24px; + color: #696968; +} +ul.items.features .date { + font-size: 11px; + color: #696968; +} +ul.items.features h1 { + font-size: 12px; + font-weight: bold; + margin: 0; +} + +/* + * sidebar + */ +div.sidebar { + display: table-cell; + width: 220px; + border-top: 3px solid #9da0c2; + background-color: white; + padding: 20px 10px 20px 0; +} + +div.sideblock h2 { + position: relative; + font-size: 12px; + margin-top: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #9da0c2; + margin-bottom: 0; +} + +div.sideblock:first-child h2 { + margin-top: 0; +} + +div.sideblock h3 { + font-size: 12px; + margin-top: 5px; + padding-bottom: 5px; + border-bottom: 1px solid #9da0c2; +} + +/* like sidebar without line */ +div.sidebox { + width: 220px; + padding: 0 0 20px 0; +} + +/* + * project sidebars + */ +div.sideblock h2 .proj_state { + position: absolute; + right: 0; +} + +div.sideblock .project { + background: url(../images/internal.png) no-repeat scroll 6px 11px #F5DAAF; + border-bottom: 1px solid #F3BE7C; + padding: 6px 6px 6px 20px; +} +div.sideblock .project.inactive { + background: url(../images/internal.png) no-repeat scroll 6px 11px #f6e6cc; +} +div.sideblock .project.parent { + background: url(../images/up.png) no-repeat scroll 6px 11px #F9F4E1; + border-bottom: 1px solid #9da0c2; + color: #666666; +} +div.sideblock .project a { + color: #333; +} + +/* + * special sideblocks + */ +div.sideblock .item.link, +div.sideblock .item.internal { + background: url(../images/internal.png) 0 11px no-repeat; + padding-left: 10px; +} + +div.sideblock .item.external { + background: url(../images/external.png) 0 11px no-repeat; + padding-left: 13px; +} + +div.sideblock .item.download { + background: url(../images/download.png) 0 8px no-repeat; + padding-left: 15px; +} + +div.sideblock .item.thumb img { + width: 55px; + height: 34px; +} +div.sideblock .item.thumb .text { + padding-left: 0.5em; +} + +/* + * footer + */ +#footer { + position: relative; + height: 50px; + border-top: 1px solid #9da0c2; + padding-top: 8px; + margin-top: 29px; +} + +#footer div.text { + display: inline-block; + color: #c10000; + font-size: 11px; +} + +#footer div.logo { + display: inline-block; + position: absolute; + right: 0; +} + +#footer div.logo img { + width: 204px; + height: 41px; +} +/* extension */ + +#header div.title { + width: 750px; + top: 15px; + +} + +/*#header div.logo img { + width: 100%; + height: 100px; + +}*/ + +a.email { + background: url("./icons/blue/Email.png") center left no-repeat; + padding-left: 23px; +} \ No newline at end of file diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/layout_orig.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/layout_orig.css Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,1312 @@ + +/** + * @file + * Stark layout method + * + * To avoid obscuring CSS added to the page by Drupal or a contrib module, the + * Stark theme itself has no styling, except just enough CSS to arrange the page + * in a traditional "Header, sidebars, content, and footer" layout. + * + * This layout method works reasonably well, but shouldn't be used on a + * production site because it can break. For example, if an over-large image + * (one that is wider than 20% of the viewport) is in the left sidebar, the + * image will overlap with the #content to the right. The exception to this + * is IE6 which will just hide the navigation block completely in these + * instances due to a positioning bug. + */ + + +/* MPIWG addtional syles for drupal */ + +/* + * return to mpiwg + */ + + + +td.key{ + color :rgb(59, 65, 134); + + } + + div.main{ + width: 75%; + } + +.mpiwg_internal_view{ + background-color: red; + } + .webSearch_hl { + font-weight: bold; + } + + + +#mpiwg_link a { + background-color: #ebecf3; + color: #3b4186; + padding: 3px 10px 3px 10px; + font-style: italic; + +} + +#header div.title img.logo_left { + width: 180px; + height: 100%; + vertical-align: top; + padding: 0px 5px 20px 0px; +} + +div.logo_left { +float: left; +} + +#header div.title { + width: 750px; + + +} + +div.subnav .sn_off { + padding-left: 0px; + background-color: #fcf2df; + font-weight: normal; +} + +div.subnav li.sn_off a{ + padding-left: 0px; + color: #d45a00; + font-weight: normal; +} + +ul li.leaf{ + list-style-image: none; +} + +ul li.expanded{ + list-style-image: none; +} + +ul li.collapsed{ + list-style-image: none; +} + +ul.menu li{ + margin-left : 0px; +} + +ul.inline li { + display: inline; + padding: 0; +} + +/* MPIWG website style sheet + * + * (c) 2013 MPIWG Berlin + * Author: Robert Casties + */ +body { + margin: 0; + font-family: Verdana, Arial, sans-serif; + background-color: #fdf8ef; + font-size: 12px; +} + +/* + * global styles + */ +img { + border: none; +} + +a { + color: #d45a00; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* link-classes (with icons) */ +a.internal, +a.more { + background: url(../images/internal.png) center left no-repeat; + padding-left: 10px; +} + +a.download { + background: url(../images/download.png) center left no-repeat; + padding-left: 15px; +} + +a.external { + background: url(../images/external.png) center left no-repeat; + padding-left: 15px; +} + +a.down { + background: url(../images/down.png) center left no-repeat; + padding-left: 15px; +} + +a.jumptop { + background: url(../images/jump_top.png) center left no-repeat; + padding-left: 15px; +} + +a.email { + background: url(../images/email.png) center left no-repeat; + padding-left: 23px; +} + +/* old blue mail link */ +a.maillink { + color: #3b4186 !important; +} + +h1 { + color: #3b4186; + font-size: 20px; + font-weight: normal; + margin: 0.5em 0; +} + +h1:first-child { + /* does this work correctly? */ + margin-top: 0; +} + +h2 { + color: #3b4186; + font-size: 16px; + font-weight: normal; + margin: 0.5em 0; +} + +h3 { + color: #3b4186; + font-size: 14px; + font-weight: normal; + margin: 0; +} + +h3 + h3 { + /* h3 directly after h3 has a margin */ + margin-top: 0.5em; +} + +h1 a { + /* a header with a link looks like a header */ + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +p { + margin: 0.5em 0; + line-height: 1.5; +} + +ul.plain { + list-style-type: none; + padding-left: 0.5em; +} +ul.plain > li { + margin-top: 0.5em; +} + +ul.inline { + display: inline; + padding: 0; + margin: 0; +} + +ul.inline li { + display: inline; +} + +/* + * table-like rows and columns + */ + +div.rows { + display: table; +} + +div.row { + display: table-row; +} + +div.row>div.col { + display: table-cell; + vertical-align: top; +} + +div.row>div.col:first-child { + /* first block has no left margin */ + padding-left: 0 !important; +} + +/* + * page wrapper and header + */ +#wrapper { + width: 900px; + margin: 0 auto; + padding: 10px 40px 10px 20px; + background-color: #fcf2df; + box-shadow: 0 0 5px 3px #d0d0d0; +} + +#header { + display: block; + position: relative; + height: 95px; + margin: 0 0 10px 0; +} + +#header div.title { + position: absolute; + left: 0; + top: 25px; +} + +#header div.title img { + width: 661px; + height: 40px; +} + +#header div.logo { + position: absolute; + right: 0; +} + +#header div.logo img { + width: 87px; + height: 88px; +} + +/* + * Main Navigation + */ +#mainnav { + display: block; + position: relative; + font-family: Verdana, Arial, sans-serif; + font-size: 12px; + border-top: 8px solid #fab775; + padding-top: 5px; + margin-bottom: 15px; + /* make iPhone-Safari not botch text size */ + -webkit-text-size-adjust: 100%; +} + +#mainnav ul { + padding: 0; + margin: 0; +} + +#mainnav li.sec { + display: inline; + border-top: 8px solid #fab775; + margin: 0 35px 0 0; + padding-top: 5px; +} + +#mainnav li.sec:hover { + border-top: 8px solid #d45a00; +} + +#mainnav a { + color: #696968; + outline: none; +} + +#mainnav a:hover { + color: #000000; + text-decoration: none; +} + +#mainnav li.sec.on { + border-top: 8px solid #d45a00; +} + +#mainnav li.sec.on a { + color: #d45a00; + outline: none; + font-weight: bold; +} + +#mainnav li.sec.lang { + position: absolute; + right: 0; + top: -8px; + margin-right: 0; +} + +/* + * breadcrumbs + */ +#breadcrumbs { + font-size: 11px; + color: #6a4d3c; + margin-bottom: 5px; +} + +#breadcrumbs a { + color: #6a4d3c; +} + +#breadcrumbs .selected { + font-weight: bold; +} + +/* + * main section wrapper + */ +#mainrow { + display: table-row; +} + +/* + * subnavigation + */ +div.leftbox { + display: table-cell; + vertical-align: top; + width: 170px; + border-top: 3px solid #dccbae; +} + +div.subnav { + font-size: 11px; +} + +div.subnav ul { + margin: 0; + padding: 0; +} + +div.subnav li { + list-style-type: none; + border-bottom: 1px solid #dccbae; + padding: 3px 6px 6px 3px; +} + +div.subnav .sn_on { + padding-left: 6px; + background-color: #d45a00; +} + +div.subnav .sn_on a { + color: #fcf2df; + font-weight: bold; +} + +div.subnav h2.subnavhead { + background-color: white; + color: #d45a00; + border-bottom: 1px solid #dccbae; + font-size: 11px; + font-weight: bold; + margin: 0; + padding: 5px 3px; +} + +div.subnav li.dept { + padding-left: 18px; + list-style-position: inside; + list-style-image: url(../images/internal.png); + text-indent: -14px; +} + +div.subnav li.dept .type { + font-style: italic; + color: #6a4d3c; +} + +div.subnav li.dept.sn_on .type { + color: #f9f4e1; +} + +div.subnavbox { + /* navigation below e.g. research projects */ + font-size: 11px; + margin-top: 30px; +} + +div.subnavbox h2 { + font-size: 11px; + color: #b69f7b; + padding-bottom: 3px; + border-bottom: 1px solid #dccbae; + margin-top: 20px; +} + +/* + * main section + */ +div.main { + display: table-cell; + vertical-align: top; + background-color: white; + border-top: 3px solid #dccbae; + padding: 20px; +} + +div.main div.row>div.col { + /* col default padding */ + padding-left: 20px; +} + +div.main div.center { + /* make sure center does not collapse */ + min-width: 460px; +} + + +/* + * frontpage + */ +div.main.home { + background-color: transparent; + border-top: 0; + padding: 0; +} + +div.main.home b { + /* bold is blue */ + color: #3b4186; +} + +div.main.home div.row.triple>div.col { + /* frontpage col width */ + width: 286px; + padding-left: 21px; +} + +div.box h2, +h2.line { + /* more-links in title are right */ + position: relative; +} + +div.box h2 a:link, +h2.line a.more:link { + /* more-links in title are right */ + position: absolute; + right: 0; + top: 3px; + /* more-links in title are small */ + font-size: 12px; +} + +/* + * front page feature teaser + */ +div.main.home div.teaser { + background-color: #f4e0c7; +} + +div.main.home div.teaser div.box { + /* teaser box needs no margins */ + margin: 0; +} + +div.teaser_image { + position: relative; + width: 593px; + height: 351px; +} + +div.teaser_image div.caption { + /* caption inside image */ + position: absolute; + bottom: 0; + padding: 3px; + text-align: right; + color: white; + font-size: 10px; + opacity: 0.8; + background-color: gray; + /* background-color: rgba(80,80,80,0.6); */ +} + +div.teaser_text { + padding-right: 21px; +} +/* feature number */ +div.teaser_text div.feature_number { + position: absolute; + right: 14px; + top: 5px; + font-family: Georgia, Times, serif; + font-size: 26px; + color: #887163; +} +/* feature title */ +div.teaser_text h1 { + font-size: 16px; + font-weight: normal; + margin-top: 0; +} +/* current research topic */ +div.teaser_text h3 { + color: #887163; + margin-bottom: 3px; + margin-top: 15px; +} +/* feature date */ +div.teaser_text div.date { + color: #3b4186; + padding-top: 20px; + margin-bottom: 20px; +} + +/* teaser slider */ +div.teaser div.box div.slider { + top: 150px; + width: 18px; + height: 24px; + padding-top: 11px; +} + +div.teaser div.box div.slider.prev { + background-image: url(../images/slider_prev_bg.png); +} + +div.teaser div.box div.slider.next { + background-image: url(../images/slider_next_bg.png); +} + +/* + * toolboxes + */ +div.tool.box { + font-family: Georgia, Times, serif; + font-style: italic; + font-size: 16px; + color: #887163; + background-color: #efeeec; + padding: 20px; + box-shadow: 0 0 1px 1px #e0e0e0; +} + +div.tool.box a { + color: #3b4186; +} + +div.tool.box div.searchbox { + /* only to make it shrink-wrap */ + display: table-cell; +} + +div.tool.box div.searchbox input.text { + height: 20px; + width: 370px; + background-color: #9fa0a7; + color: white; + font-size: 14px; + font-style: italic; + border: 0; + padding: 3px; +} + +div.tool.box div.searchbox input.submit { + /* submit uses border-box. don't ask */ + height: 26px; + width: 26px; + border: 0; + padding: 2px 3px 4px 3px; + /* TODO: this should be an image */ + background-color: #ee7f2d; + color: white; + font-size: 14px; +} + +div.tool.box div.searchbox div.extended { + font-family: Verdana, Arial, sans-serif; + font-size: 12px; + text-align: right; + padding: 5px 30px 5px 3px; +} + +div.tool.box h3 { + font-family: Georgia, Times, serif; + font-style: italic; + font-size: 16px; + color: #887163; + margin: 0.5em 0; +} + +div.tool.box h3:first-child { + /* top header has no margin */ + margin-top: 0; +} + +div.tool.box ul { + margin: 0; + padding: 0; +} + +div.tool.box div.options { + margin-top: 10px; +} + +div.tool.box div.options li { + list-style-type: none; + margin: 0.5em 0; +} + +div.tool.box div.atoz { + color: #887163; + font-family: Verdana, Arial, sans-serif; + font-size: 14px; + font-style: normal; + margin-top: 10px; +} + +div.tool.box input.checkbox, +div.tool.box input.radio { + /* checkbox uses border-box. don't ask */ + width: 20px; + height: 20px; + margin: 0; + /* TODO: this should be an image */ + background-color: white; +} + +/* project page toolbox with thumbs */ +div.tool.box div.box.thumbs { + width: 420px; +} + +div.box.thumbs div.thumb { + display: inline; +} +div.box.thumbs div.thumb img { + width: 55px; + height: 34px; +} + + +/* + * service box on frontpage + */ +div.box h2.service { + /* title has no line */ + border: 0; +} + +div.tool.box.service { + margin-top: 0; + padding: 0; +} + +div.tool.box.service div.row > div.col { + padding: 0; +} +div.tool.box.service div.search { + width: 200px; + height: 20px; + padding: 26px 0 26px 16px; + border-bottom: 1px solid #9fa0a7; +} +div.tool.box.service div.search.both { + height: 44px; + padding: 14px 0 14px 16px; +} +div.tool.box.service div.searchicon { + width: 38px; + height: 40px; + text-align: center; + padding: 16px; + border-bottom: 1px solid #9fa0a7; +} +div.tool.box.service div.icon { + width: 71px; + height: 70px; + text-align: center; + line-height: 2.5; + padding: 1px 0; + border-left: 1px solid #9fa0a7; +} +div.tool.box.service div.icon a { + color: black; + font-size: 12px; + font-family: Verdana, Arial, sans-serif; + font-style: normal; +} +div.tool.box.service div.icon.first { + border-left: 0; + width: 70px; +} +div.tool.box.service div.searchbox { + display: block; +} +div.tool.box.service div.searchbox input.text { + width: 170px; + height: 17px; + font-size: 12px; + padding: 1px 3px; +} +div.tool.box.service div.searchbox input.submit { + height: 19px; + width: 19px; + padding: 0; + margin: 0; + vertical-align: bottom; +} +div.tool.box.service select.quickfinder { + width: 200px; + height: 19px; + font-size: 12px; + background-color: white; + margin-bottom: 5px; + border: 0; +} + +/* + * other boxes + */ +div.box { + margin: 20px 0; + position: relative; +} + +div.box h2, +h2.line { + /* h2 is title with line */ + padding-bottom: 5px; + border-bottom: 3px solid #dccbae; + margin-bottom: 5px; +} + +div.box.line, +div.line { + border-bottom: 1px solid #dccbae; +} + +div.box h3 { + /* h3 is subtitle/type */ + font-size: 12px; + color: #6a4d3c; +} + +div.box h3 + h2 { + /* directly over h2 */ + margin-top: 0; +} + +/* + * small box with thumbnail + */ +div.mini { + padding: 10px 0; + /* line-height: 1; */ +} + +div.row.quintuple div.mini { + max-width: 122px; + font-size: 11px; +} + +div.mini div.thumb { + background-color: #f8f8f8; + text-align: center; + margin-top: 0.5em; +} +div.mini div.thumb:first-child { + margin-top: 0; +} +div.row.quintuple div.mini div.thumb img { + width: 120px; + height: 75px; +} + +div.row.triple div.mini { + width: 140px; +} + +div.row.triple div.mini div.thumb img { + width: 140px; + height: 87px; +} + +div.row.triple div.mini.source div.thumb img { + /* thumb size for sources given by digilib */ + width: auto; + height: auto; +} + +div.row.double div.mini div.thumb img { + height: 180px; +} + +div.mini div.type { + color: #9f917a; + margin-top: 0.5em; +} + +div.mini div.title { + margin-top: 0.5em; +} + +div.mini div.author { + color: #3b4186; + margin-top: 0.5em; +} + +div.mini div.link { + margin-top: 0.5em; +} + +div.mini h2 { + /* e.g. title for mini-books */ + font-size: 14px; +} + +div.mini.website div.description { + /* initially hidden */ + display: none; +} + +div.box.foldable div.fold { + /* initially hidden */ + display: none; + position: absolute; + bottom: 0; + right: 0; + /* TODO: this should be an image */ + background-color: #ee7f2d; + color: white; + font-size: 14px; +} + +/* + * slider buttons + */ +div.box div.slider { + position: absolute; + top: 110px; + width: 16px; + height: 18px; +} + +div.box div.slider.prev { + left: 0; +} + +div.box div.slider.next { + right: 0; + text-align: right; +} + +/* + * paragraph with line + */ +div.item { + padding-top: 5px; + padding-bottom: 5px; + border-bottom: 1px solid #dccbae; + line-height: 1.5; +} + +/* + * pubman references + */ +.reference .Italic { + font-style: italic; +} + + +/* + * table with items (articles) + */ +table.items { + /* no space between cells */ + border-collapse: collapse; +} + +table.items td { + vertical-align: top; + text-align: left; + line-height: 1.5; + padding: 1em 1em 0.5em 0; +} +table.items td:last-child { + padding-right: 0; +} +table.items.shorter td { + padding: 0.5em 0.5em 0.5em 0; +} + +table.items th { + font-family: Georgia, Times, serif; + font-size: 16px; + font-weight: normal; + font-style: italic; + color: #887163; + text-align: left; + border-bottom: 3px solid #dccbae; +} + +table.items h2 { + border-bottom: 3px solid #dccbae; + margin-bottom: 0; +} +table.items h2 a { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +table.items img.thumb { + width: 55px; + height: 34px; +} + +table.items td.line, +table.items tr.line td { + border-bottom: 1px solid #dccbae; +} + +table.items td.topline, +table.items tr.topline td { + border-top: 1px solid #dccbae; +} + +table.items tr.last_item td { + /* last_item has no padding-top */ + padding: 0 0 0.5em 0; +} + +table.items h3.fold_head { + font-size: 12px; + background-color: #f6f2eb; + padding: 3px; +} + +table.items h3.fold_head img { + margin: 0 5px 0 3px; +} + +table.items div.fold_body { + /* e.g. preprint abstracts */ + padding: 0.5em 0 0 20px; +} + +ul.items { + list-style-type: none; + padding: 0; +} + +ul.items li { + padding: 0 0 0.5em 20px; + border-bottom: 3px solid #dccbae; + margin-bottom: 0.5em; +} + +ul.items .fold_head { + text-indent: -20px; +} +ul.items .fold_head img.fold_open, +ul.items .fold_head img.fold_closed { + padding-right: 5px; +} + +ul.items .type { + /* font-size: 11px; */ + color: #696968; +} + +ul.items h2 { + font-size: 12px; +} + +ul.items td.key { + color: #3b4186; +} + +.foldable img.fold_closed { + /* fold is initially open */ + display: none; +} + + +/* + * hierarchical list (of projects) + */ +div.hierlist h2 { + text-indent: -20px; + padding-left: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #dccbae; + margin-bottom: 0; +} + +div.hierlist h2 img { + padding: 0 3px 3px 3px; +} + +div.hierlist h2 a { + color: inherit; + font-weight: inherit; + text-decoration: none; +} + +div.hierlist ul { + list-style-type: none; + padding: 0 0 10px 0; + margin: 0; + border-bottom: 1px solid #dccbae; +} + +div.hierlist li { + padding: 5px 0 5px 16px; + text-indent: -13px; +} + +div.hierlist li a { + /* icon is more distant from text */ + padding-left: 13px; +} + +div.hierlist li.indent { + padding-bottom: 10px; + border-bottom: 1px solid #dccbae; + margin-bottom: 5px; +} + +div.hierlist li.level_1 { + font-size: 14px; +} + +div.hierlist li.level_2 { + font-weight: bold; + margin-left: 20px; +} + +div.hierlist li.level_3 { + margin-left: 40px; +} + +div.hierlist li.level_4 { + margin-left: 60px; +} + +div.hierlist li.level_5 { + margin-left: 80px; +} + + +/* + * figures (in project descriptions and features) + */ +div.figure, +div.image_small { + float: left; + width: 230px; + padding: 0.5em 1em 0 0; +} +div.image_small.right { + float: right; + padding: 0.5em 0 0 1em; +} +div.figure div.image img, +div.image_small img { + width: 230px; +} +div.figure div.figcaption, +div.banner_large div.caption, +div.image_small div.caption { + font-size: 10px; + line-height: 1.5; + color: #9f917a; + margin-top: 0.5em; +} +div.banner_large img { + width: 460px; +} + +/* + * project description and feature + */ +h3.authors { + margin-top: 0.5em; +} + +p.maintext_authors { + font-size: 14px; + color: #3b4186; +} + +div.description { + margin-top: 0.5em; + line-height: 1.5; +} + +h2.type { + color: #9f917a; +} + +/* + * feature story archive + */ +ul.items.features li { + padding: 0 0 0.5em 0; + border-bottom: 1px solid #dccbae; +} +ul.items.features .thumb { + float:left; + padding: 8px 10px 0 0; +} +ul.items.features .thumb img { + width: 55px; + height: 34px; +} +ul.items.features .feature_number { + font-family: Georgia, Times, serif; + font-size: 24px; + color: #696968; +} +ul.items.features .date { + font-size: 11px; + color: #696968; +} +ul.items.features h1 { + font-size: 12px; + font-weight: bold; + margin: 0; +} + +/* + * sidebar + */ +div.sidebar { + display: table-cell; + width: 220px; + border-top: 3px solid #dccbae; + background-color: white; + padding: 20px 10px 20px 0; +} + +div.sideblock h2 { + position: relative; + font-size: 12px; + margin-top: 20px; + padding-bottom: 5px; + border-bottom: 3px solid #dccbae; + margin-bottom: 0; +} + +div.sideblock:first-child h2 { + margin-top: 0; +} + +div.sideblock h3 { + font-size: 12px; + margin-top: 5px; + padding-bottom: 5px; + border-bottom: 1px solid #dccbae; +} + +/* like sidebar without line */ +div.sidebox { + width: 220px; + padding: 0 0 20px 0; +} + +/* + * project sidebars + */ +div.sideblock h2 .proj_state { + position: absolute; + right: 0; +} + +div.sideblock .project { + background: url(../images/internal.png) no-repeat scroll 6px 11px #F5DAAF; + border-bottom: 1px solid #F3BE7C; + padding: 6px 6px 6px 20px; +} +div.sideblock .project.inactive { + background: url(../images/internal.png) no-repeat scroll 6px 11px #f6e6cc; +} +div.sideblock .project.parent { + background: url(../images/up.png) no-repeat scroll 6px 11px #F9F4E1; + border-bottom: 1px solid #DCCBAE; + color: #666666; +} +div.sideblock .project a { + color: #333; +} + +/* + * special sideblocks + */ +div.sideblock .item.link, +div.sideblock .item.internal { + background: url(../images/internal.png) 0 11px no-repeat; + padding-left: 10px; +} + +div.sideblock .item.external { + background: url(../images/external.png) 0 11px no-repeat; + padding-left: 13px; +} + +div.sideblock .item.download { + background: url(../images/download.png) 0 8px no-repeat; + padding-left: 15px; +} + +div.sideblock .item.thumb img { + width: 55px; + height: 34px; +} +div.sideblock .item.thumb .text { + padding-left: 0.5em; +} + +/* + * footer + */ +#footer { + position: relative; + height: 50px; + border-top: 1px solid #dccbae; + padding-top: 8px; + margin-top: 29px; +} + +#footer div.text { + display: inline-block; + color: #d45a00; + font-size: 11px; +} + +#footer div.logo { + display: inline-block; + position: absolute; + right: 0; +} + +#footer div.logo img { + width: 204px; + height: 41px; +} +/* extension */ + +#header div.title { + width: 750px; + top: 15px; + +} + +#header div.logo img { + width: 100%; + height: 100px; + +} diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/logo.png Binary file sites/all/themes/mpiwgDev/logo.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/logo_BW.png Binary file sites/all/themes/mpiwgDev/logo_BW.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/logo_old.png Binary file sites/all/themes/mpiwgDev/logo_old.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/main_template.zpt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/main_template.zpt Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,108 @@ + + + + +Max Planck Institute for the History of Science + + + + + +
          +
          + + + + + + +
          +
          + + +
          + +
          + +
          + + +
          + + + + + +
          + + + + +
          + + + diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/mpiwgDev.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/mpiwgDev.info Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,12 @@ +name = MPIWGDev +description = MPIWGDev theme +package = Core +version = VERSION +core = 7.x +stylesheets[all][] = layout.css + +; Information added by drupal.org packaging script on 2013-02-20 +version = "7.20" +project = "drupal" + + diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/node.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/node.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,30 @@ + +
          > + + + + > + + + + + +
          > + +
          + +
          + + + + + +
          + +
          diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/page.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/page.tpl.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,126 @@ + +
          +
          + + + + +
          + + + + + + +
          + +
          + +
          + + +
          +
          + + + + +
          + +
          + + + +
          + + + +

          > + + + +

          + + + + +
          + + + + + + + +
          + +
          + + +
          + + +
          + + + + + + + +
          + +
          + +
          + diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/page.tpl_orig.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/page.tpl_orig.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,68 @@ + + + +
          +
          + + + + + + + +
          + +
          + +
          + + + > + + +
          + + + + +
          + +
          + + +
          + + + + + +
          +
          diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/screenshot.png Binary file sites/all/themes/mpiwgDev/screenshot.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/screenshot_old.png Binary file sites/all/themes/mpiwgDev/screenshot_old.png has changed diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/template.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/template.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,241 @@ +' . t('You are here') . ''; + + $output .= ''; + return $output; + } +} + +/** + * Override or insert variables into the maintenance page template. + */ +function mpiwgDev_preprocess_maintenance_page(&$vars) { + // While markup for normal pages is split into page.tpl.php and html.tpl.php, + // the markup for the maintenance page is all in the single + // maintenance-page.tpl.php template. So, to have what's done in + // mpiwgDev_preprocess_html() also happen on the maintenance page, it has to be + // called here. + mpiwgDev_preprocess_html($vars); +} + +/** + * Override or insert variables into the html template. + */ +function mpiwgDev_preprocess_html(&$vars) { + // Toggle fixed or fluid width. + if (theme_get_setting('mpiwgDev_width') == 'fluid') { + $vars['classes_array'][] = 'fluid-width'; + } + // Add conditional CSS for IE6. + drupal_add_css(path_to_theme() . '/fix-ie.css', array('group' => CSS_THEME, 'browsers' => array('IE' => 'lt IE 7', '!IE' => FALSE), 'preprocess' => FALSE)); +} + +/** + * Override or insert variables into the html template. + */ +function mpiwgDev_process_html(&$vars) { + // Hook into color.module + if (module_exists('color')) { + _color_html_alter($vars); + } +} + +/** + * Override or insert variables into the page template. + */ +function mpiwgDev_preprocess_page(&$vars) { + // Move secondary tabs into a separate variable. + + + $vars['MPIWGlogo'] = base_path() . path_to_theme() . '/logo.png'; + $vars['tabs2'] = array( + '#theme' => 'menu_local_tasks', + '#secondary' => $vars['tabs']['#secondary'], + ); + unset($vars['tabs']['#secondary']); + + if (isset($vars['main_menu'])) { + /* $vars['primary_nav'] = theme('mpiwgDev_links__system__main_menu', array( + 'links' => $vars['main_menu'], + 'attributes' => array( + 'class' => array('links', 'inline', 'main-menu'), + ), + 'heading' => array( + 'text' => t('Main menu'), + 'level' => 'h2', + 'class' => array('element-invisible'), + ) + ));*/ + $vars['primary_nav'] = mpiwgDev_links__system__main_menu($vars['main_menu']); + + } + else { + $vars['primary_nav'] = FALSE; + } + if (isset($vars['secondary_menu'])) { + $vars['secondary_nav'] = theme('links__system_secondary_menu', array( + 'links' => $vars['secondary_menu'], + 'attributes' => array( + 'class' => array('links', 'inline', 'secondary-menu'), + ), + 'heading' => array( + 'text' => t('Secondary menu'), + 'level' => 'h2', + 'class' => array('element-invisible'), + ) + )); + } + else { + $vars['secondary_nav'] = FALSE; + } + + // Prepare header. + $site_fields = array(); + if (!empty($vars['site_name'])) { + $site_fields[] = $vars['site_name']; + } + if (!empty($vars['site_slogan'])) { + $site_fields[] = $vars['site_slogan']; + } + $vars['site_title'] = implode(' ', $site_fields); + if (!empty($site_fields)) { + $site_fields[0] = '' . $site_fields[0] . ''; + } + $vars['site_html'] = implode(' ', $site_fields); + + // Set a variable for the site name title and logo alt attributes text. + $slogan_text = $vars['site_slogan']; + $site_name_text = $vars['site_name']; + $vars['site_name_and_slogan'] = $site_name_text . ' ' . $slogan_text; +} + +/** + * Override or insert variables into the node template. + */ +function mpiwgDev_preprocess_node(&$vars) { + $vars['submitted'] = $vars['date'] . ' — ' . $vars['name']; +} + +/** + * Override or insert variables into the comment template. + */ +function mpiwgDev_preprocess_comment(&$vars) { + $vars['submitted'] = $vars['created'] . ' — ' . $vars['author']; +} + +/** + * Override or insert variables into the block template. + */ +function mpiwgDev_preprocess_block(&$vars) { + $vars['title_attributes_array']['class'][] = 'title'; + //$vars['classes_array'][] = 'clearfix'; + $vars['classes_array']= array('MPIWGDEV_block'); +} + +/** + * Override or insert variables into the page template. + */ +function mpiwgDev_process_page(&$vars) { + // Hook into color.module + if (module_exists('color')) { + _color_page_alter($vars); + } +} + +/** + * Override or insert variables into the region template. + */ +function mpiwgDev_preprocess_region(&$vars) { + if ($vars['region'] == 'header') { + $vars['classes_array'][] = 'clearfix'; + } +} + +function mpiwgDev_links__system__main_menu($variables) { + $html = "
            \n"; + + #$tree = menu_tree_all_data('main_menu',$variables['links']); + + foreach ($variables as $link) { + $path=$link['href']; + if (($path == $_GET['q'] || + ($path == '' && drupal_is_front_page())) && (empty($options['language']) )) { + $html .= '
          • '.l($link['title'], $link['href'], $link)."
          • "; + }else{ + + $html .= '
          • '.l($link['title'], $link['href'], $link)."
          • "; + } + } + + global $user; + + // soll login im hauptmenu angezeigt werden? + if (theme_get_setting('mpiwg_login_in_main_menu')){ + if (!$user->uid) { + + $html .='
          • '.l(t("Login"), 'user')."
          • "; + } elseif ($user->name=='ip_login'){ + $html .='
          • '.l(t("Login"), 'user/logout')."
          • "; + } else + { + $html .='
          • '.l(t("Logout").'('.$user->name . ')', 'user/logout')."
          • "; + } + } + $html .= "
          \n"; + + + return $html; +} + + + +function mpiwgDev_menu_tree($variables) { + return ''; +} +function mpiwgDev_menu_link ($variables){ + + + $element = $variables['element']; + $sub_menu = ''; + + if ($element['#below']) { + $sub_menu = drupal_render($element['#below']); + + } + $output = l($element['#title'], $element['#href'], $element['#localized_options']); + + $active=false; + foreach ($element['#attributes']['class'] as $attr){ + if ($attr == "active"){ + $active=true; + } + } + + if ($active) { + array_push($element['#attributes']['class'],'sn_on'); + } else { + array_push($element['#attributes']['class'],'sn_off'); + } + + return '' . $output . $sub_menu . "
        • \n"; +} + +function mpiwgDev_block_view_locale_language_alter(&$vars,$block){ + + $vars['content']=''; +} + + + + diff -r 000000000000 -r 015d06b10d37 sites/all/themes/mpiwgDev/theme-settings.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/themes/mpiwgDev/theme-settings.php Wed Jul 31 13:49:13 2013 +0200 @@ -0,0 +1,36 @@ + 'textfield', + '#title' => t('Link behind Logo'), + + '#default_value' => theme_get_setting('mpiwg_logo_link'), + '#description' => t('Specify the link behing the logo.'), + // Place this above the color scheme options. + '#weight' => -2, + ); + + $form['mpiwg_login_in_main_menu'] = array( + '#type' => 'checkbox', + '#title' => t('Login link in main menu?'), + '#default_value' => theme_get_setting('mpiwg_login_in_main_menu'), + '#description' => t('Puts a login link in the main menu.'), + // Place this above the color scheme options. + '#weight' => -3, + ); +}