? SolrPhpClient ? Zend ? apachesolr-6.x-beta7-beta6.diff ? current-query-fix-254565-97.patch ? negative-fq-348029-8.patch ? svn-commit.tmp.save ? views-254565-87.patch Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/Attic/CHANGELOG.txt,v retrieving revision 1.1.2.48 retrieving revision 1.1.2.54 diff -u -p -r1.1.2.48 -r1.1.2.54 --- CHANGELOG.txt 3 Apr 2009 14:46:49 -0000 1.1.2.48 +++ CHANGELOG.txt 16 Apr 2009 14:34:15 -0000 1.1.2.54 @@ -1,4 +1,4 @@ -// $Id: CHANGELOG.txt,v 1.1.2.48 2009/04/03 14:46:49 pwolanin Exp $ +// $Id: CHANGELOG.txt,v 1.1.2.54 2009/04/16 14:34:15 pwolanin Exp $ Apache Solr integration x.x-x.x, xxxx-xx-xx ------------------------------ @@ -9,6 +9,15 @@ Apache Solr integration 6.x-1.x, xxxx-xx Apache Solr integration 6.x-1.0-xxxxx, xxxx-xx-xx ------------------------------ +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. Index: Drupal_Apache_Solr_Service.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/Drupal_Apache_Solr_Service.php,v retrieving revision 1.1.2.15 retrieving revision 1.1.2.16 diff -u -p -r1.1.2.15 -r1.1.2.16 --- Drupal_Apache_Solr_Service.php 3 Apr 2009 14:45:15 -0000 1.1.2.15 +++ Drupal_Apache_Solr_Service.php 13 Apr 2009 14:51:15 -0000 1.1.2.16 @@ -120,7 +120,7 @@ class Drupal_Apache_Solr_Service extends $summary = array( '@pending_docs' => '', '@autocommit_time_seconds' => '', - '@autocommit_time_minutes' => '', + '@autocommit_time' => '', '@deletes_by_id' => '', '@deletes_by_query' => '', '@deletes_total' => '', @@ -132,8 +132,8 @@ class Drupal_Apache_Solr_Service extends $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_minutes'] = $max_time/60000; + $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($deletes_id_xpath[0]); $deletes_query_xpath = $stats->xpath('//stat[@name="deletesByQuery"]'); Index: Solr_Base_Query.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/Solr_Base_Query.php,v retrieving revision 1.1.4.24 retrieving revision 1.1.4.28 diff -u -p -r1.1.4.24 -r1.1.4.28 --- Solr_Base_Query.php 3 Apr 2009 14:37:32 -0000 1.1.4.24 +++ Solr_Base_Query.php 16 Apr 2009 15:26:44 -0000 1.1.4.28 @@ -1,42 +1,50 @@ $queries, 'values' => $values); + return $extracted; } /** * Takes an array $field and combines the #name and #value in a way * suitable for use in a Solr query. */ - static function make_field(array $field) { + public function make_filter(array $filter) { // If the field value has spaces, or : in it, wrap it in double quotes. // unless it is a range query. - if (preg_match('/[ :]/', $field['#value']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $field['#value'])) { - $field['#value'] = '"'. $field['#value']. '"'; + if (preg_match('/[ :]/', $filter['#value']) && !isset($filter['#start']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $filter['#value'])) { + $filter['#value'] = '"'. $filter['#value']. '"'; } - return $field['#name'] . ':' . $field['#value']; + return $filter['#name'] . ':' . $filter['#value']; } /** @@ -47,14 +55,13 @@ class Solr_Base_Query { /** * Each query/subquery will have a unique ID */ - public $id; + protected $id; /** * A keyed array where the key is a position integer and the value * is an array with #name and #value properties. Each value is a * used for filter queries, e.g. array('#name' => 'uid', '#value' => 0) * for anonymous content. - */ protected $fields; @@ -63,7 +70,7 @@ class Solr_Base_Query { * Contains name:value pairs for filter queries. For example, * "type:book" for book nodes. */ - protected $filters; + protected $filterstring; /** * A mapping of field names from the URL to real index field names. @@ -85,6 +92,8 @@ class Solr_Base_Query { */ protected $solr; + protected $available_sorts; + /** * @param $solr * An instantiated Apache_Solr_Service Object. @@ -103,25 +112,36 @@ class Solr_Base_Query { function __construct($solr, $querypath, $filterstring, $sortstring) { $this->solr = $solr; $this->querypath = trim($querypath); - $this->filters = trim($filterstring); + $this->filterstring = trim($filterstring); $this->solrsort = trim($sortstring); $this->id = ++self::$idCount; $this->parse_filters(); + $this->available_sorts = $this->default_sorts(); } function __clone() { $this->id = ++self::$idCount; } - function add_field($field, $value) { + public function add_filter($field, $value) { $this->fields[] = array('#name' => $field, '#value' => trim($value)); } - public function get_fields() { - return $this->fields; + public function get_filters($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 remove_field($name, $value = NULL) { + public function remove_filter($name, $value = NULL) { // We can only remove named fields. if (empty($name)) { return; @@ -142,7 +162,7 @@ class Solr_Base_Query { } } - public function has_field($name, $value) { + public function has_filter($name, $value) { foreach ($this->fields as $pos => $values) { if (!empty($values['#name']) && !empty($values['#value']) && $values['#name'] == $name && $values['#value'] == $value) { return TRUE; @@ -179,16 +199,16 @@ class Solr_Base_Query { * OR. * * @param $query - * An instance of Solr_Base_Query. + * An instance of Drupal_Solr_Query_Interface. * * @param $operator * 'AND' or 'OR' */ - function add_subquery(Solr_Base_Query $query, $fq_operator = 'OR', $q_operator = 'AND') { + public function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND') { $this->subqueries[$query->id] = array('#query' => $query, '#fq_operator' => $fq_operator, '#q_operator' => $q_operator); } - function remove_subquery(Solr_Base_Query $query) { + public function remove_subquery(Solr_Base_Query $query) { unset($this->subqueries[$query->id]); } @@ -199,6 +219,28 @@ class Solr_Base_Query { public function set_solrsort($sortstring) { $this->solrsort = trim($sortstring); } + + public function get_available_sorts() { + return $this->available_sorts; + } + + public function set_available_sort($field, $sort) { + $this->available_sorts[$field] = $sort; + } + + /** + * Returns a default list of sorts. + */ + protected function default_sorts() { + return array( + 'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'), + 'sort_title' => array('name' => t('Title'), 'default' => 'asc'), + 'type' => array('name' => t('Type'), 'default' => 'asc'), + 'sort_name' => array('name' => t('Author'), 'default' => 'asc'), + 'created' => array('name' => t('Date'), 'default' => 'desc'), + ); + } + /** * Return filters and sort in a form suitable for a query param to url(). */ @@ -227,6 +269,20 @@ class Solr_Base_Query { public function get_query_basic() { return $this->rebuild_query(); } + + /** + * return the search path + * this class assumes its always through the search api + * + * @param string $new_keywords + * if we are using new keywords as our query string + */ + public function get_path($new_keywords = NULL) { + if ($new_keywords) { + return 'search/' . arg(1) . '/' . $new_keywords; + } + return 'search/' . arg(1) . '/' . $this->get_query_basic(); + } /** * Build additional breadcrumb elements relative to $base. @@ -241,11 +297,17 @@ class Solr_Base_Query { foreach ($this->fields as $field) { $name = $field['#name']; + $prefix = ''; + // Handle negative queries. + if ($name[0] == '-') { + $name = substr($name, 1); + $prefix = '-'; + } // Look for a field alias. if (isset($this->field_map[$name])) { - $field['#name'] = $this->field_map[$name]; + $field['#name'] = $prefix . $this->field_map[$name]; } - $progressive_crumb[] = Solr_Base_Query::make_field($field); + $progressive_crumb[] = $this->make_filter($field); $options = array('query' => 'filters=' . implode(' ', $progressive_crumb)); if ($themed = theme("apachesolr_breadcrumb_{$name}", $field['#value'])) { $breadcrumb[] = l($themed, $base, $options); @@ -268,7 +330,7 @@ class Solr_Base_Query { */ protected function parse_filters() { $this->fields = array(); - $filters = $this->filters; + $filterstring = $this->filterstring; // Gets information about the fields already in solr index. $index_fields = $this->solr->getFields(); @@ -276,14 +338,18 @@ class Solr_Base_Query { foreach ((array) $index_fields as $name => $data) { // Look for a field alias. $alias = isset($this->field_map[$name]) ? $this->field_map[$name] : $name; - // Get the values for $name - $extracted = Solr_Base_Query::field_extract($filters, $alias); - if (count($extracted['values'])) { - foreach ($extracted['values'] as $index => $value) { - $pos = strpos($this->filters, $extracted['queries'][$index]); - // $solr_keys and $solr_crumbs are keyed on $pos so that query order - // is maintained. This is important for breadcrumbs. - $this->fields[$pos] = array('#name' => $name, '#value' => trim($value)); + // Look for normal and negative queries for the same field. + foreach(array('', '-') as $prefix) { + // Get the values for $name + $extracted = $this->filter_extract($filterstring, $prefix . $alias); + if (count($extracted)) { + foreach ($extracted as $filter) { + $pos = strpos($this->filterstring, $filter['#query']); + // $solr_keys and $solr_crumbs are keyed on $pos so that query order + // is maintained. This is important for breadcrumbs. + $filter['#name'] = $prefix . $name; + $this->fields[$pos] = $filter; + } } } } @@ -306,7 +372,7 @@ class Solr_Base_Query { if ($aliases && isset($this->field_map[$field['#name']])) { $field['#name'] = $this->field_map[$field['#name']]; } - $fq[] = Solr_Base_Query::make_field($field); + $fq[] = $this->make_filter($field); } foreach ($this->subqueries as $id => $data) { $subfq = $data['#query']->rebuild_fq($aliases); Index: apachesolr.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/apachesolr.admin.inc,v retrieving revision 1.1.2.13 retrieving revision 1.1.2.14 diff -u -p -r1.1.2.13 -r1.1.2.14 --- apachesolr.admin.inc 30 Mar 2009 22:50:49 -0000 1.1.2.13 +++ apachesolr.admin.inc 13 Apr 2009 14:51:15 -0000 1.1.2.14 @@ -1,5 +1,5 @@ getStatsSummary(); - $delay_msg = '
' . t('The server has a @autocommit_time_minutes minute delay before updates are processed.', $stats_summary) . "
\n"; + $delay_msg = '' . t('The server has a @autocommit_time delay before updates are processed.', $stats_summary) . "
\n"; $delete_msg = '' . t('Numer of pending deletions: @deletes_total', $stats_summary) . "
\n"; } catch (Exception $e) { Index: apachesolr.index.inc =================================================================== RCS file: apachesolr.index.inc diff -N apachesolr.index.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ apachesolr.index.inc 16 Apr 2009 13:33:16 -0000 1.1.2.1 @@ -0,0 +1,232 @@ +'), array(' <', '> '), $text)); +} + +/** + * Given a node ID, return a document representing that node. + */ +function apachesolr_node_to_document($nid) { + // Set reset = TRUE to avoid static caching of all nodes that get indexed. + $node = node_load($nid, NULL, TRUE); + if (empty($node)) { + return FALSE; + } + + $document = FALSE; + // Let any module exclude this node from the index. + $build_document = TRUE; + foreach (module_implements('apachesolr_node_exclude') as $module) { + $exclude = module_invoke($module, 'apachesolr_node_exclude', $node); + if (!empty($exclude)) { + $build_document = FALSE; + } + } + + if ($build_document) { + // Build the node body. + $node->build_mode = NODE_BUILD_SEARCH_INDEX; + $node = node_build_content($node, FALSE, FALSE); + $node->body = drupal_render($node->content); + $node->title = apachesolr_clean_text($node->title); + + $text = $node->body; + + // Fetch extra data normally not visible, including comments. + $extra = node_invoke_nodeapi($node, 'update index'); + $text .= "\n\n" . implode(' ', $extra); + $text = apachesolr_strip_ctl_chars($text); + + $document = new Apache_Solr_Document(); + $document->id = apachesolr_document_id($node->nid); + $document->site = url(NULL, array('absolute' => TRUE)); + $document->hash = apachesolr_site_hash(); + $document->nid = $node->nid; + $document->uid = $node->uid; + $document->title = $node->title; + $document->status = $node->status; + $document->sticky = $node->sticky; + $document->promote = $node->promote; + $document->moderate = $node->moderate; + $document->tnid = $node->tnid; + $document->translate = $node->translate; + if (!empty($node->language)) { + $document->language = $node->language; + } + $document->body = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text)); + $document->type = $node->type; + $document->type_name = apachesolr_strip_ctl_chars(node_get_types('name', $node)); + $document->created = apachesolr_date_iso($node->created); + $document->changed = apachesolr_date_iso($node->changed); + $last_change = (isset($node->last_comment_timestamp) && $node->last_comment_timestamp > $node->changed) ? $node->last_comment_timestamp : $node->changed; + $document->last_comment_or_change = apachesolr_date_iso($last_change); + $document->comment_count = isset($node->comment_count) ? $node->comment_count : 0; + $document->name = apachesolr_strip_ctl_chars($node->name); + + $path = 'node/' . $node->nid; + $document->url = url($path, array('absolute' => TRUE)); + $document->path = $path; + // 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) ? '' : $node->language; + $output = drupal_get_path_alias($path, $language); + if ($output && $output != $path) { + $document->path_alias = apachesolr_strip_ctl_chars($output); + } + } + + // Get CCK fields list + $cck_fields = apachesolr_cck_fields(); + foreach ($cck_fields as $key => $cck_info) { + if (isset($node->$key)) { + // Got a CCK field. See if it is to be indexed. + $function = $cck_info['callback']; + if ($cck_info['callback'] && function_exists($function)) { + $field = call_user_func_array($function, array($node, $key)); + } + else { + $field = $node->$key; + } + $index_key = apachesolr_index_key($cck_info); + foreach ($field as $value) { + // Don't index NULLs or empty strings + if (isset($value['safe']) && strlen($value['safe'])) { + if ($cck_info['multiple']) { + $document->setMultiValue($index_key, apachesolr_clean_text($value['safe'])); + } + else { + $document->$index_key = apachesolr_clean_text($value['safe']); + } + } + } + } + } + // Index book module data. + if (!empty($node->book['bid'])) { + // Hard-coded - must change if apachesolr_index_key() changes. + $document->is_book_bid = (int) $node->book['bid']; + } + apachesolr_add_tags_to_document($document, $text); + apachesolr_add_taxonomy_to_document($document, $node); + + // Let modules add to the document - TODO convert to drupal_alter(). + foreach (module_implements('apachesolr_update_index') as $module) { + $function = $module .'_apachesolr_update_index'; + $function($document, $node); + } + } + return $document; +} + +/** + * Extract taxonomy from $node and add to dynamic fields. + */ +function apachesolr_add_taxonomy_to_document(&$document, $node) { + if (isset($node->taxonomy) && is_array($node->taxonomy)) { + foreach ($node->taxonomy as $term) { + // Double indexing of tids lets us do effecient searches (on tid) + // and do accurate 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. + $ancestors = taxonomy_get_parents_all($term->tid); + foreach ($ancestors as $ancestor) { + $document->setMultiValue('tid', $ancestor->tid); + $document->setMultiValue('im_vid_'. $ancestor->vid, $ancestor->tid); + $name = apachesolr_clean_text($ancestor->name); + $document->setMultiValue('vid', $ancestor->vid); + $document->{'ts_vid_'. $ancestor->vid .'_names'} .= ' '. $name; + // We index each name as a string for cross-site faceting + // using the vocab name rather than vid in field construction . + $document->setMultiValue('sm_vid_'. apachesolr_vocab_name($ancestor->vid), $name); + } + } + } +} + +/** + * Helper function - return a safe (PHP identifier) vocabulary name. + */ +function apachesolr_vocab_name($vid) { + static $names = array(); + + if (!isset($names[$vid])) { + $vocab_name = db_result(db_query('SELECT v.name FROM {vocabulary} v WHERE v.vid = %d', $vid)); + $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]; +} + +/** + * Extract HTML tag contents from $text and add to boost fields. + * + * $text must be stripped of control characters before hand. + */ +function apachesolr_add_tags_to_document(&$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)) .')[^>]*>(.*)\1>@Ui', $text, $matches); + foreach ($matches[1] as $key => $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])) { + $document->{$tags_to_index[$tag]} .= ' '. $matches[2][$key]; + } + } +} + Index: apachesolr.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/apachesolr.install,v retrieving revision 1.1.4.14 retrieving revision 1.1.4.15 diff -u -p -r1.1.4.14 -r1.1.4.15 --- apachesolr.install 30 Mar 2009 20:16:31 -0000 1.1.4.14 +++ apachesolr.install 14 Apr 2009 00:12:18 -0000 1.1.4.15 @@ -1,5 +1,5 @@ getMessage(), NULL, WATCHDOG_ERROR); return FALSE; } - + include_once(drupal_get_path('module', 'apachesolr') .'/apachesolr.index.inc') ; $documents = array(); $old_position = apachesolr_get_last_index($namespace); $position = $old_position; @@ -300,144 +300,6 @@ function apachesolr_index_nodes($result, } /** - * Add a document to the $documents array based on a node ID. - */ -function apachesolr_add_node_document(&$documents, $nid) { - if ($document = apachesolr_node_to_document($nid)) { - $documents[] = $document; - } -} - -/** - * Strip control characters that cause Jetty/Solr to fail. - */ -function apachesolr_strip_ctl_chars($text) { - // 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]@', ' ', $text); -} - -/** - * Strip html tags and also control characters that cause Jetty/Solr to fail. - */ -function apachesolr_clean_text($text) { - $text = preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $text); - // Add spaces before stripping tags to avoid running words together. - return strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text)); -} - -/** - * Given a node ID, return a document representing that node. - */ -function apachesolr_node_to_document($nid) { - // Set reset = TRUE to avoid static caching of all nodes that get indexed. - $node = node_load($nid, NULL, TRUE); - if (empty($node)) { - return FALSE; - } - - $document = FALSE; - // Let any module exclude this node from the index. - $build_document = TRUE; - foreach (module_implements('apachesolr_node_exclude') as $module) { - $exclude = module_invoke($module, 'apachesolr_node_exclude', $node); - if (!empty($exclude)) { - $build_document = FALSE; - } - } - - if ($build_document) { - // Build the node body. - $node->build_mode = NODE_BUILD_SEARCH_INDEX; - $node = node_build_content($node, FALSE, FALSE); - $node->body = drupal_render($node->content); - $node->title = apachesolr_clean_text($node->title); - - $text = $node->body; - - // Fetch extra data normally not visible, including comments. - $extra = node_invoke_nodeapi($node, 'update index'); - $text .= "\n\n" . implode(' ', $extra); - $text = apachesolr_strip_ctl_chars($text); - - $document = new Apache_Solr_Document(); - $document->id = apachesolr_document_id($node->nid); - $document->site = url(NULL, array('absolute' => TRUE)); - $document->hash = apachesolr_site_hash(); - $document->nid = $node->nid; - $document->uid = $node->uid; - $document->title = $node->title; - $document->status = $node->status; - $document->sticky = $node->sticky; - $document->promote = $node->promote; - $document->moderate = $node->moderate; - $document->tnid = $node->tnid; - $document->translate = $node->translate; - $document->language = $node->language; - $document->body = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text)); - $document->type = $node->type; - $document->type_name = apachesolr_strip_ctl_chars(node_get_types('name', $node)); - $document->created = apachesolr_date_iso($node->created); - $document->changed = apachesolr_date_iso($node->changed); - $last_change = (isset($node->last_comment_timestamp) && $node->last_comment_timestamp > $node->changed) ? $node->last_comment_timestamp : $node->changed; - $document->last_comment_or_change = apachesolr_date_iso($last_change); - $document->comment_count = isset($node->comment_count) ? $node->comment_count : 0; - $document->name = apachesolr_strip_ctl_chars($node->name); - - $path = 'node/' . $node->nid; - $document->url = url($path, array('absolute' => TRUE)); - $document->path = $path; - // 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) ? '' : $node->language; - $output = drupal_get_path_alias($path, $language); - if ($output && $output != $path) { - $document->path_alias = apachesolr_strip_ctl_chars($output); - } - } - - // Get CCK fields list - $cck_fields = apachesolr_cck_fields(); - foreach ($cck_fields as $key => $cck_info) { - if (isset($node->$key)) { - // Got a CCK field. See if it is to be indexed. - $function = $cck_info['callback']; - if ($cck_info['callback'] && function_exists($function)) { - $field = call_user_func_array($function, array($node, $key)); - } - else { - $field = $node->$key; - } - $index_key = apachesolr_index_key($cck_info); - foreach ($field as $value) { - // Don't index NULLs or empty strings - if (isset($value['safe']) && strlen($value['safe'])) { - if ($cck_info['multiple']) { - $document->setMultiValue($index_key, apachesolr_clean_text($value['safe'])); - } - else { - $document->$index_key = apachesolr_clean_text($value['safe']); - } - } - } - } - } - apachesolr_add_tags_to_document($document, $text); - apachesolr_add_taxonomy_to_document($document, $node); - - // Let modules add to the document - TODO convert to drupal_alter(). - foreach (module_implements('apachesolr_update_index') as $module) { - $function = $module .'_apachesolr_update_index'; - $function($document, $node); - } - } - return $document; -} - -/** * Convert date from timestamp into ISO 8601 format. * http://lucene.apache.org/solr/api/org/apache/solr/schema/DateField.html */ @@ -445,85 +307,6 @@ function apachesolr_date_iso($date_times return gmdate('Y-m-d\TH:i:s\Z', $date_timestamp); } -/** - * Extract taxonomy from $node and add to dynamic fields. - */ -function apachesolr_add_taxonomy_to_document(&$document, $node) { - if (isset($node->taxonomy) && is_array($node->taxonomy)) { - foreach ($node->taxonomy as $term) { - // Double indexing of tids lets us do effecient searches (on tid) - // and do accurate 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. - $ancestors = taxonomy_get_parents_all($term->tid); - foreach ($ancestors as $ancestor) { - $document->setMultiValue('tid', $ancestor->tid); - $document->setMultiValue('im_vid_'. $ancestor->vid, $ancestor->tid); - $name = apachesolr_clean_text($ancestor->name); - $document->setMultiValue('vid', $ancestor->vid); - $document->{'ts_vid_'. $ancestor->vid .'_names'} .= ' '. $name; - // We index each name as a string for cross-site faceting - // using the vocab name rather than vid in field construction . - $document->setMultiValue('sm_vid_'. apachesolr_vocab_name($ancestor->vid), $name); - } - } - } -} - -/** - * Helper function - return a safe (PHP identifier) vocabulary name. - */ -function apachesolr_vocab_name($vid) { - static $names = array(); - - if (!isset($names[$vid])) { - $vocab_name = db_result(db_query('SELECT v.name FROM {vocabulary} v WHERE v.vid = %d', $vid)); - $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]; -} - -/** - * Extract HTML tag contents from $text and add to boost fields. - * - * $text must be stripped of control characters before hand. - */ -function apachesolr_add_tags_to_document(&$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)) .')[^>]*>(.*)\1>@Ui', $text, $matches); - foreach ($matches[1] as $key => $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])) { - $document->{$tags_to_index[$tag]} .= ' '. $matches[2][$key]; - } - } -} - function apachesolr_delete_node_from_index($node) { try { $solr = apachesolr_get_solr(); @@ -595,7 +378,7 @@ function _apachesolr_nodeapi_delete($nod // There was no exception, so delete from the table. db_query("DELETE FROM {apachesolr_search_node} WHERE nid = %d", $node->nid); if ($set_message && user_access('administer site configuration') && variable_get('apachesolr_set_nodeapi_messages', 1)) { - apachesolr_set_stats_message('Deleted content will be removed from the Apache Solr search index in approximately @autocommit_time_minutes minutes'); + apachesolr_set_stats_message('Deleted content will be removed from the Apache Solr search index in approximately @autocommit_time.'); } } } @@ -610,7 +393,7 @@ function _apachesolr_nodeapi_update($nod // There was no exception, so update the table. db_query("UPDATE {apachesolr_search_node} SET changed = %d, status = %d WHERE nid = %d", time(), $node->status, $node->nid); if ($set_message && user_access('administer site configuration') && variable_get('apachesolr_set_nodeapi_messages', 1)) { - apachesolr_set_stats_message('Unpublished content will be removed from the Apache Solr search index in approximately @autocommit_time_minutes minutes'); + apachesolr_set_stats_message('Unpublished content will be removed from the Apache Solr search index in approximately @autocommit_time.'); } } } @@ -677,13 +460,7 @@ function apachesolr_block($op = 'list', switch ($delta) { case 'sort': - $sorts = array( - 'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'), - 'sort_title' => array('name' => t('Title'), 'default' => 'asc'), - 'type' => array('name' => t('Type'), 'default' => 'asc'), - 'sort_name' => array('name' => t('Author'), 'default' => 'asc'), - 'created' => array('name' => t('Date'), 'default' => 'desc'), - ); + $sorts = $query->get_available_sorts(); $solrsorts = array(); $sort_parameter = isset($_GET['solrsort']) ? check_plain($_GET['solrsort']) : FALSE; @@ -695,7 +472,7 @@ function apachesolr_block($op = 'list', } $sort_links = array(); - $path = 'search/' . arg(1) . '/' . $query->get_query_basic(); + $path = $query->get_path(); $new_query = clone $query; foreach ($sorts as $type => $sort) { $new_sort = isset($solrsorts[$type]) ? $solrsorts[$type] == 'asc' ? 'desc' : 'asc' : $sort['default']; @@ -734,42 +511,48 @@ function apachesolr_facet_block($respons $contains_active = FALSE; $items = array(); foreach ($response->facet_counts->facet_fields->$facet_field as $facet => $count) { + $countsort = 1000000 - $count; + $options = array(); // Solr sends this back if it's empty. if ($facet == '_empty_') { - continue; + $field_name = '-'. $facet_field; + $facet = '[* TO *]'; + $facet_text = theme('placeholder', t('Missing this field')); + $options['html'] = TRUE; + // Put this just below any active facets. + $countsort = 1; + } + else { + $field_name = $facet_field; + $facet_text = $facet; } - $unclick_link = ''; - unset($active); + if ($facet_callback && function_exists($facet_callback)) { - $facet_text = $facet_callback($facet); - } - else { - $facet_text = $facet; + $facet_text = $facet_callback($facet, $options); } + $unclick_link = ''; + $active = FALSE; $new_query = clone $query; - if ($active = $query->has_field($facet_field, $facet)) { - $contains_active = TRUE; - $new_query->remove_field($facet_field, $facet); + if ($query->has_filter($field_name, $facet)) { + $contains_active = $active = TRUE; + $countsort = 0; + $new_query->remove_filter($field_name, $facet); // TODO: don't assume 'search' - find the real path. - $path = 'search/'. arg(1) .'/'. $new_query->get_query_basic(); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); $unclick_link = theme('apachesolr_unclick_link', $path, $querystring); } else { - $new_query->add_field($facet_field, $facet); - $path = 'search/'. arg(1) .'/'. $new_query->get_query_basic(); + $new_query->add_filter($field_name, $facet); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); } - $countsort = $count == 0 ? '' : 1 / $count; - // if numdocs == 1 and !active, don't add. - if ($response->numFound == 1 && !$active) { - // skip - } - else { - $items[$active ? $countsort . $facet : 1 + $countsort . $facet] = theme('apachesolr_facet_item', $facet_text, $count, $path, $querystring, $active, $unclick_link, $response->numFound); + if ($count || $active) { + $items[$countsort . '*' . $facet_text] = theme('apachesolr_facet_item', $facet_text, $count, $path, $querystring, $active, $unclick_link, $response->response->numFound, $options); } } - if (count($items) > 0) { + // Unless a facet is active only display 2 or more. + if ($items && ($response->response->numFound > 1 || $contains_active)) { ksort($items); // Get information needed by the rest of the blocks about limits. $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array()); @@ -787,6 +570,24 @@ function apachesolr_facet_block($respons * TODO: Refactor with apachesolr_facet_block(). */ function apachesolr_date_facet_block($response, $query, $module, $delta, $facet_field, $filter_by, $facet_callback = FALSE) { + $items = array(); + + $new_query = clone $query; + foreach (array_reverse($new_query->get_filters($facet_field)) as $filter) { + // Iteratively remove the date facets. + $new_query->remove_filter($facet_field, $filter['#value']); + $path = $new_query->get_path(); + $querystring = $new_query->get_url_querystring(); + $unclick_link = theme('apachesolr_unclick_link', $path, $querystring); + if ($facet_callback && function_exists($facet_callback)) { + $facet_text = $facet_callback($filter['#start']); + } + else { + $facet_text = apachesolr_date_format_iso_by_gap(apachesolr_date_find_query_gap($filter['#start'], $filter['#end']), $filter['#start']); + } + array_unshift($items, theme('apachesolr_facet_item', $facet_text, 0, $path, $querystring, TRUE, $unclick_link)); + } + // Add links for additional date filters. if (!empty($response->facet_counts->facet_dates->$facet_field)) { $field = clone $response->facet_counts->facet_dates->$facet_field; @@ -806,16 +607,12 @@ function apachesolr_date_facet_block($re $prev_facet = $facet; } $range_end[$prev_facet] = $end; - - $contains_active = FALSE; - $items = array(); + foreach ($field as $facet => $count) { // Solr sends this back if it's empty. - if ($facet == '_empty_') { + if ($facet == '_empty_' || $count == 0) { continue; } - $unclick_link = ''; - unset($active); if ($facet_callback && function_exists($facet_callback)) { $facet_text = $facet_callback($facet); } @@ -823,28 +620,20 @@ function apachesolr_date_facet_block($re $facet_text = apachesolr_date_format_iso_by_gap(substr($gap, 2), $facet); } $new_query = clone $query; - $new_query->add_field($facet_field, "[$facet TO {$range_end[$facet]}]"); - $path = 'search/'. arg(1) .'/'. $new_query->get_query_basic(); + $new_query->add_filter($facet_field, '['. $facet .' TO '. $range_end[$facet] .']'); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); - $countsort = $count == 0 ? '' : 1 / $count; - // if numdocs == 1 and !active, don't add. - if ($count == 0 || ($response->numFound == 1 && !$active)) { - // skip - } - else { - $items[$active ? $countsort . $facet : 1 + $countsort . $facet] = theme('apachesolr_facet_item', $facet_text, $count, $path, $querystring, $active, $unclick_link, $response->numFound); - } - } - if (count($items) > 0) { - // ksort($items); - // Get information needed by the rest of the blocks about limits. - $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array()); - $limit = isset($initial_limits[$module][$delta]) ? $initial_limits[$module][$delta] : variable_get('apachesolr_facet_query_initial_limit_default', 10); - $output = theme('apachesolr_facet_list', $items, $limit); - return array('subject' => $filter_by, 'content' => $output); + $items[] = theme('apachesolr_facet_item', $facet_text, $count, $path, $querystring, FALSE, '', $response->response->numFound); } } + if (count($items) > 0) { + // Get information needed by the rest of the blocks about limits. + $initial_limits = variable_get('apachesolr_facet_query_initial_limits', array()); + $limit = isset($initial_limits[$module][$delta]) ? $initial_limits[$module][$delta] : variable_get('apachesolr_facet_query_initial_limit_default', 10); + $output = theme('apachesolr_facet_list', $items, $limit); + return array('subject' => $filter_by, 'content' => $output); + } return NULL; } @@ -974,14 +763,8 @@ function apachesolr_date_determine_gap($ if ($end - $start > 86400) { return 'DAY'; } - else if ($end - $start > 3600) { - return 'HOUR'; - } - else if ($end - $start > 60) { - return 'MINUTE'; - } - - return 'SECOND'; + // For now, HOUR is a reasonable smallest gap. + return 'HOUR'; } /** @@ -999,7 +782,6 @@ function apachesolr_date_gap_drilldown($ 'DAY' => 'HOUR', // For now, HOUR is a reasonable smallest gap. // 'HOUR' => 'MINUTE', - // 'MINUTE' => 'SECOND', ); return isset($drill[$gap]) ? $drill[$gap] : NULL; } @@ -1012,6 +794,7 @@ function apachesolr_date_gap_drilldown($ function apachesolr_facetcount_form($module, $delta) { $initial = variable_get('apachesolr_facet_query_initial_limits', array()); $limits = variable_get('apachesolr_facet_query_limits', array()); + $facet_missing = variable_get('apachesolr_facet_missing', array()); $limit = drupal_map_assoc(array(50, 40, 30, 20, 15, 10, 5, 3)); @@ -1030,7 +813,13 @@ function apachesolr_facetcount_form($mod '#description' => t('The maximum number of filter links to show in this block.'), '#default_value' => isset($limits[$module][$delta]) ? $limits[$module][$delta] : variable_get('apachesolr_facet_query_limit_default', 20), ); - + $form['apachesolr_facet_missing'] = array( + '#type' => 'radios', + '#title' => t('Include a facet for missing'), + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#description' => t('A facet can be generated corresponding to all documents entirely missing this field.'), + '#default_value' => isset($facet_missing[$module][$delta]) ? $facet_missing[$module][$delta] : 0, + ); return $form; } @@ -1048,6 +837,9 @@ function apachesolr_facetcount_save($edi $initial = variable_get('apachesolr_facet_query_initial_limits', array()); $initial[$module][$delta] = (int)$edit['apachesolr_facet_query_initial_limit']; variable_set('apachesolr_facet_query_initial_limits', $initial); + $facet_missing = variable_get('apachesolr_facet_missing', array()); + $facet_missing[$module][$delta] = (int)$edit['apachesolr_facet_missing']; + variable_set('apachesolr_facet_missing', $facet_missing); } /** @@ -1057,7 +849,7 @@ function apachesolr_facetcount_save($edi * * function my_module_apachesolr_modify_query(&$query, &$params) { * // I only want to see articles by the admin! - * $query->add_field("uid", 1); + * $query->add_filter("uid", 1); * * } */ @@ -1100,7 +892,7 @@ function apachesolr_has_searched($search */ function apachesolr_get_solr($host = NULL, $port = NULL, $path = NULL) { static $solr_cache; - + if (empty($host)) { $host = variable_get('apachesolr_host', 'localhost'); } @@ -1144,7 +936,6 @@ function apachesolr_static_response_cach * The query object is built from the keys, filters, and sort. */ function apachesolr_drupal_query($keys = '', $filters = '', $solrsort = '') { - list($module, $class) = variable_get('apachesolr_query_class', array('apachesolr', 'Solr_Base_Query')); include_once drupal_get_path('module', $module) .'/'. $class .'.php'; @@ -1155,37 +946,20 @@ function apachesolr_drupal_query($keys = watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR); $query = NULL; } + return $query; } /** - * Factory function for query objects representing the current search URL. - * - * The query object is built from the keys in the URL, but these may be - * overridden by passing in parameters. + * Static getter/setter for the current query */ -function apachesolr_current_query($keys = '', $filters = '', $solrsort = '', $reset = FALSE) { - static $_queries = array(); - - if ($reset) { - $_queries = array(); +function apachesolr_current_query($query = NULL) { + static $saved_query = NULL; + if (is_object($query)) { + $saved_query = clone $query; } - if (empty($keys)) { - $keys = search_get_keys(); - } - if (empty($filters) && !empty($_GET['filters'])) { - $filters = $_GET['filters']; - } - if (empty($solrsort) && !empty($_GET['solrsort'])) { - $solrsort = $_GET['solrsort']; - } - $index = $keys . '&filters=' . $filters; - - if (empty($_queries) || !array_key_exists($index, $_queries)) { - $_queries[$index] = apachesolr_drupal_query($keys, $filters, $solrsort); - } - return is_object($_queries[$index]) ? clone $_queries[$index] : $_queries[$index]; + return empty($saved_query) ? $saved_query : clone $saved_query; } /** @@ -1228,8 +1002,7 @@ function apachesolr_index_key($field) { } /** - * This invokes the hook_apachesolr_cck_field_mappings to find out how to handle - * CCK fields. + * Invokes hook_apachesolr_cck_field_mappings to find out how to handle CCK fields. */ function apachesolr_cck_fields() { static $fields; @@ -1263,7 +1036,7 @@ function apachesolr_cck_fields() { function apachesolr_theme() { return array( 'apachesolr_facet_item' => array( - 'arguments' => array('name' => NULL, 'count' => NULL, 'path' => NULL, 'querystring' => '', 'active' => FALSE, 'unclick_link' => NULL, 'num_found' => NULL), + 'arguments' => array('name' => NULL, 'count' => NULL, 'path' => NULL, 'querystring' => '', 'active' => FALSE, 'unclick_link' => NULL, 'num_found' => NULL, 'options' => NULL), ), 'apachesolr_unclick_link' => array( 'arguments' => array('url' => NULL, 'querystring' => ''), @@ -1280,26 +1053,46 @@ function apachesolr_theme() { ); } -function theme_apachesolr_facet_item($name, $count, $path, $querystring = '', $active = FALSE, $unclick_link = NULL, $num_found = NULL) { - $attributes = array(); +function theme_apachesolr_facet_item($name, $count, $path, $querystring = '', $active = FALSE, $unclick_link = NULL, $num_found = NULL, $options = array()) { + if ($active) { - $attributes['class'] = 'active'; + if (isset($options['attributes']['class'])) { + $options['attributes']['class'] .= ' active'; + } + else { + $options['attributes']['class'] = 'active'; + } } if ($unclick_link) { - return $unclick_link . ' '. check_plain($name); + if (empty($options['html'])) { + $name = check_plain($name); + } + return $unclick_link . ' '. $name; } else { - return apachesolr_l($name ." ($count)", $path, array('attributes' => $attributes, 'query' => $querystring)); + $options['query'] = $querystring; + return apachesolr_l($name ." ($count)", $path, $options); } } +/** + * A replacement for l() - very similar but doesn't add the 'active' class. + * + * @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, ); + // Don't need this, and just to be safe. + unset($options['attributes']['title']); - return ''. check_plain($text) .''; + return ''. ($options['html'] ? $text : check_plain($text)) .''; } @@ -1343,3 +1136,76 @@ function theme_apachesolr_sort_list($ite return theme('item_list', $items); } +/** + * The interface for which all 'query' objects talk + */ +interface Drupal_Solr_Query_Interface { + /** + * Checks to see if a specific filter is already present. + * + * @param string $field + * the facet field to check + * + * @param string $value + * The facet value to check against + */ + function has_filter($field, $value); + + /** + * Remove a filter from the query + * + * @param string $field + * the facet field to remove + * + * @param string $value + * The facet value to remove + * This value can be NULL + */ + function remove_filter($field, $value = NULL); + + /** + * Add a filter to a query + * + * @param string $field + * the facet field to apply to this query + * + * @param string value + * the value of the facet to apply + */ + function add_filter($field, $value); + + /** + * return the search path + */ + function get_path(); + + /** + * Return any query string for use in the l function. + * + * @see l() + */ + function get_url_querystring(); + + /** + * return the basic string query + */ + function get_query_basic(); + + /** + * Set the solrsort. + * + * @param string raw string to set the sort to + */ + function set_solrsort($sortstring); + + /** + * Return an array of all filters. + */ + function get_filters($name = NULL); + + /** + * Add a subquery to the query. + */ + function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND'); +} + Index: apachesolr_search.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/apachesolr_search.install,v retrieving revision 1.1.2.4 retrieving revision 1.1.2.5 diff -u -p -r1.1.2.4 -r1.1.2.5 --- apachesolr_search.install 27 Jan 2009 13:41:22 -0000 1.1.2.4 +++ apachesolr_search.install 14 Apr 2009 00:12:18 -0000 1.1.2.5 @@ -1,5 +1,5 @@ $module_facets) { foreach($module_facets as $delta => $facet_field) { - if (isset($all_facets[$delta]['facet_date'])) { - $facet_date = $all_facets[$delta]['facet_date']; - $params['facet.date'][] = $facet_date; - - $callback = $all_facets[$delta]['facet_date_range_callback']; - list($start, $end, $gap) = $callback($query, $params, $delta); + // TODO: generalize handling of date and range facets. + if ($module == 'apachesolr_search' && ($facet_field == 'created' || $facet_field == 'changed')) { + list($start, $end, $gap) = apachesolr_search_date_range($query, $facet_field); if ($gap) { - $params['f.'. $facet_date .'.facet.date.start'] = $start; - $params['f.'. $facet_date .'.facet.date.end'] = $end; - $params['f.'. $facet_date .'.facet.date.gap'] = $gap; + $params['facet.date'][] = $facet_field; + $params['f.'. $facet_field .'.facet.date.start'] = $start; + $params['f.'. $facet_field .'.facet.date.end'] = $end; + $params['f.'. $facet_field .'.facet.date.gap'] = $gap; } } else { @@ -152,6 +154,10 @@ function apachesolr_search_search($op = if (isset($facet_query_limits[$module][$delta])) { $params['f.' . $facet_field . '.facet.limit'] = $facet_query_limits[$module][$delta]; } + // Facet missing + if (!empty($facet_missing[$module][$delta])) { + $params['f.' . $facet_field . '.facet.missing'] = 'true'; + } } } } @@ -240,7 +246,11 @@ function apachesolr_search_search($op = } } } - + + // Cache the built query. Since all the built queries go through + // this process, all the hook_invocations will happen later + apachesolr_current_query($query); + // This hook allows modules to modify the query and params objects. apachesolr_modify_query($query, $params, 'apachesolr_search'); if (!$query) { @@ -293,63 +303,37 @@ function apachesolr_search_search($op = } // try catch (Exception $e) { watchdog('Apache Solr', $e->getMessage(), NULL, WATCHDOG_ERROR); - apachesolr_failure(t('Solr search'), is_null($query) ? $keys : $query->get_query_basic()); + apachesolr_failure(t('Solr search'), empty($query) ? $keys : $query->get_query_basic()); } break; } // switch } -function _hack_parse_date_range($str) { - if (preg_match('/\[(\S+) TO (\S+)\]/', $str, $m)) { - return array($m[1], strtotime($m[1]), $m[2], strtotime($m[2])); - } - return array(); -} - -function apachesolr_search_date_range($query, &$params, $delta) { - // Find the smallest filter range for $delta in the query. $query - // has no method to retrieve filters for $delta, so we iterate - // through them all. - $fields = $query->get_fields(); - foreach ($fields as $info) { - if ($info['#name'] == $delta) { - // $info['#value'] is the filter value. Ideally it would also - // have #range, #from, and #to properties. Until it does, we - // parse them out ourselves. - // - // Also, if we had an ISO date library we could use them - // directly. Instead, we convert to Unix timestamps for comparison. - list($_start_iso, $_start, $_end_iso, $_end) = _hack_parse_date_range($info['#value']); - // Only use dates that we were able to parse into timestamps. - if ($_start > 0 && $_end > 0) { - if (!isset($start) || $_start > $start) { - $start = $_start; - $start_iso = $_start_iso; - } - if (!isset($end) || $_end < $end) { - $end = $_end; - $end_iso = $_end_iso; - } - - // Determine the drilldown gap for this range. - $gap = apachesolr_date_gap_drilldown(apachesolr_date_find_query_gap($start_iso, $end_iso)); - } +function apachesolr_search_date_range($query, $facet_field) { + foreach ($query->get_filters($facet_field) as $filter) { + // If we had an ISO date library we could use ISO dates + // directly. Instead, we convert to Unix timestamps for comparison. + // Only use dates if we are able to parse into timestamps. + $start = strtotime($filter['#start']); + $end = strtotime($filter['#end']); + if ($start && $end && ($start < $end)) { + $start_iso = $filter['#start']; + $end_iso = $filter['#end']; + // Determine the drilldown gap for this range. + $gap = apachesolr_date_gap_drilldown(apachesolr_date_find_query_gap($start_iso, $end_iso)); } } - // If there is no $delta field in query object, get initial // facet.date.* params from the DB and determine the best search // gap to use. This callback assumes $delta is 'changed' or 'created'. if (!isset($start_iso)) { - $start_iso = apachesolr_date_iso(db_result(db_query("SELECT MIN($delta) FROM {node} WHERE status = 1"))); - $end_iso = apachesolr_date_iso(db_result(db_query("SELECT MAX($delta) FROM {node} WHERE status = 1"))); + $start_iso = apachesolr_date_iso(db_result(db_query("SELECT MIN($facet_field) FROM {node} WHERE status = 1"))); + $end_iso = apachesolr_date_iso(db_result(db_query("SELECT MAX($facet_field) FROM {node} WHERE status = 1"))); $gap = apachesolr_date_determine_gap($start_iso, $end_iso); } - - // Return a query range from the beginning of a gap period to the - // beginning of the next gap period. We ALWAYS generate query - // ranges of this form and the apachesolr_date_*() helper functions - // require it. + // Return a query range from the beginning of a gap period to the beginning + // of the next gap period. We ALWAYS generate query ranges of this form + // and the apachesolr_date_*() helper functions require it. return array("$start_iso/$gap", "$end_iso+1$gap/$gap", "+1$gap"); } @@ -374,21 +358,23 @@ function apachesolr_search_apachesolr_fa 'facet_field' => 'language', ); - // TODO bjaspan: I need to include facet_field or the entry is - // ignored, even though facet_date is the key element. $facets['changed'] = array( 'info' => t('Apache Solr Search: Filter by updated date'), 'facet_field' => 'changed', - 'facet_date' => 'changed', - 'facet_date_range_callback' => 'apachesolr_search_date_range', ); $facets['created'] = array( 'info' => t('Apache Solr Search: Filter by post date'), 'facet_field' => 'created', - 'facet_date' => 'created', - 'facet_date_range_callback' => 'apachesolr_search_date_range', ); + // A book module facet. + if (module_exists('book')) { + $facets['is_book_bid'] = array( + 'info' => t('Apache Solr Search: Filter by Book'), + 'facet_field' => 'is_book_bid', + ); + } + // Get taxonomy vocabulary facets. if (module_exists('taxonomy')) { $vocabs = taxonomy_get_vocabularies(); @@ -396,7 +382,7 @@ function apachesolr_search_apachesolr_fa // In this case the delta and facet field are the same. $delta = 'im_vid_' . $vid; $facets[$delta] = array( - 'info' => t('Apache Solr Search: Filter by @name', array('@name' => $vocab->name)), + 'info' => t('Apache Solr Search: Filter by taxonomy @name', array('@name' => $vocab->name)), 'facet_field' => $delta, ); } @@ -465,29 +451,30 @@ function apachesolr_search_block($op = ' $terms = array(); foreach ($response->facet_counts->facet_fields->$delta as $tid => $count) { + if ($tid == '_empty_') { + // TODO - for now we don't handle facet missing. + continue; + } $unclick_link = ''; unset($active); $term = taxonomy_get_term($tid); $new_query = clone $query; - if ($active = $query->has_field('tid', $tid)) { + if ($active = $query->has_filter('tid', $tid)) { $contains_active = TRUE; - $new_query->remove_field('tid', $term->tid); - $path = 'search/' . arg(1) . '/' . $new_query->get_query_basic(); + $new_query->remove_filter('tid', $term->tid); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); $unclick_link = theme('apachesolr_unclick_link', $path, $querystring); } else { - $new_query->add_field('tid', $term->tid); - $path = 'search/' . arg(1) . '/' . $new_query->get_query_basic(); + $new_query->add_filter('tid', $term->tid); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); } $countsort = $count == 0 ? '' : 1 / $count; // if numdocs == 1 and !active, don't add. - if ($response->numFound == 1 && !$active) { - // skip - } - else { - $terms[$term->vid][$active ? $countsort . $term->name : 1 + $countsort . $term->name] = theme('apachesolr_facet_item', $term->name, $count, $path, $querystring, $active, $unclick_link, $response->numFound); + if ($response->response->numFound > 1 || $active) { + $terms[$term->vid][$active ? $countsort . $term->name : 1 + $countsort . $term->name] = theme('apachesolr_facet_item', $term->name, $count, $path, $querystring, $active, $unclick_link, $response->response->numFound); } } } @@ -506,9 +493,8 @@ function apachesolr_search_block($op = ' switch ($delta) { case 'currentsearch': - $fields = $query->get_fields(); - $search_keys = $query->get_query_basic(); - $path = 'search/' . arg(1) . '/' . $search_keys; + $fields = $query->get_filters(); + $path = $query->get_path(); $options = array(); if (!$fields) { $options['attributes']['class'] = 'active'; @@ -517,19 +503,20 @@ function apachesolr_search_block($op = ' foreach($fields as $field) { if ($field['#name']) { $new_query = clone $query; - $new_query->remove_field($field['#name'], $field['#value']); - $path = 'search/'. arg(1) .'/'. $new_query->get_query_basic(); + $new_query->remove_filter($field['#name'], $field['#value']); + $path = $new_query->get_path(); $querystring = $new_query->get_url_querystring(); $unclick_link = theme('apachesolr_unclick_link', $path, $querystring); if (! $fielddisplay = theme("apachesolr_breadcrumb_". $field['#name'], $field['#value'])) { $fielddisplay = $field['#value']; } - $links[] = theme('apachesolr_facet_item', $fielddisplay, NULL, $path, $querystring, $active, $unclick_link, $response->numFound); + $links[] = theme('apachesolr_facet_item', $fielddisplay, NULL, $path, $querystring, $active, $unclick_link, $response->response->numFound); } } $content = theme('apachesolr_currentsearch', $response->response->numFound, $links); return array('subject' => t('Current search'), 'content' => $content); - + case 'is_book_bid': + return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by book'), 'apachesolr_search_get_book'); case 'language': return apachesolr_facet_block($response, $query, 'apachesolr_search', $delta, $delta, t('Filter by language'), 'locale_language_name'); case 'uid': @@ -570,6 +557,19 @@ function apachesolr_search_block($op = ' } /** + * Callback function for the 'Filter by book' facet block. + */ +function apachesolr_search_get_book($facet, &$options) { + if (is_numeric($facet)) { + return db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $facet)); + } + else { + $options['html'] = TRUE; + return theme('placeholder', t('Not in any book')); + } +} + +/** * Callback function for the 'Filter by name' facet block. */ function apachesolr_search_get_username($facet) { @@ -615,7 +615,7 @@ function apachesolr_search_form_search_f '#suffix' => '', '#type' => 'item', '#title' => t('Did you mean'), - '#value' => l($new_keywords, 'search/'. arg(1) .'/'. $new_keywords), + '#value' => l($new_keywords, $query->get_path($new_keywords)), ); } } @@ -667,6 +667,9 @@ function apachesolr_search_build_spellch */ function apachesolr_search_theme() { return array( + 'apachesolr_breadcrumb_is_book_bid' => array( + 'arguments' => array('bid' => NULL), + ), 'apachesolr_breadcrumb_uid' => array( 'arguments' => array('uid' => NULL), ), @@ -692,11 +695,8 @@ function apachesolr_search_theme() { } function theme_apachesolr_breadcrumb_date_range($range) { - if (preg_match('@[\[\{](\S+) TO (\S+)[\]\}]@', $range, $m)) { - list($all, $start_iso, $end_iso) = $m; - return apachesolr_date_format_range($m[1], $m[2]); - $start_label = apachesolr_date_format_iso_by_gap($gap, $start_iso); - return "$start_label"; + if (preg_match('@[\[\{](\S+) TO (\S+)[\]\}]@', $range, $match)) { + return apachesolr_date_format_range($match[1], $match[2]); } return $range; } @@ -732,6 +732,18 @@ function theme_apachesolr_breadcrumb_typ } /** + * Return the title of a book. + */ +function theme_apachesolr_breadcrumb_is_book_bid($bid) { + if (is_numeric($bid)) { + return db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $bid)); + } + else { + return t('Not in any book'); + } +} + +/** * Return current search block contents */ function theme_apachesolr_currentsearch($total_found, $links) { @@ -739,7 +751,7 @@ function theme_apachesolr_currentsearch( } /** - * Returns the snipit text for a search entry + * Theme the highlighted snippet text for a search entry. * * @param object $doc * @param array $snippets Index: contrib/apachesolr_nodeaccess/apachesolr_nodeaccess.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/contrib/apachesolr_nodeaccess/Attic/apachesolr_nodeaccess.module,v retrieving revision 1.1.2.6 retrieving revision 1.1.2.8 diff -u -p -r1.1.2.6 -r1.1.2.8 --- contrib/apachesolr_nodeaccess/apachesolr_nodeaccess.module 20 Feb 2009 19:31:16 -0000 1.1.2.6 +++ contrib/apachesolr_nodeaccess/apachesolr_nodeaccess.module 16 Apr 2009 14:34:16 -0000 1.1.2.8 @@ -1,18 +1,30 @@ nid); + $result = db_query('SELECT * FROM {node_access} WHERE (nid = 0 OR nid = %d) AND grant_view = 1', $node->nid); while ($grant = db_fetch_object($result)) { - $key = 'nodeaccess_'. $grant->realm; + $key = 'nodeaccess_' . apachesolr_site_hash() . '_' . $grant->realm; $document->setMultiValue($key, $grant->gid); } } + else { + // Add the generic view grant if we are not using + // node access or the node is viewable by anonymous users. + $document->setMultiValue('nodeaccess_all', 0); + } } /** @@ -21,23 +33,26 @@ function apachesolr_nodeaccess_apachesol * @param $account an account to get grants for and build a solr query */ function _apachesolr_nodeaccess_build_subquery($account) { - if (!user_access('administer nodes', $account) && count(module_implements('node_grants'))) { + if (!user_access('access content', $account)) { + return NULL; + } + $node_access_query = apachesolr_drupal_query(); + if (user_access('administer nodes', $account)) { + // Access all content from the current site, or public content. + $node_access_query->add_filter('nodeaccess_all', 0); + $node_access_query->add_filter('hash', apachesolr_site_hash()); + } + else { // Get node access grants. $grants = node_access_grants('view', $account); - if (empty($grants)) { - // If they can't see any content, we might as well not bother searching. - // Catch the exception to null out the query. - throw new Exception("This user cannot access any content!"); - } - $node_access_query = apachesolr_drupal_query(); foreach ($grants as $realm => $gids) { foreach ($gids as $gid) { - $node_access_query->add_field('nodeaccess_' . $realm, $gid); + $node_access_query->add_filter('nodeaccess_' . apachesolr_site_hash() . '_' . $realm, $gid); } } - return $node_access_query; + $node_access_query->add_filter('nodeaccess_all', 0); } - return NULL; + return $node_access_query; } /** @@ -109,3 +124,7 @@ function apachesolr_nodeaccess_rebuild_n node_access_needs_rebuild(TRUE); apachesolr_clear_last_index(); } + +function apachesolr_nodeaccess_enable() { + drupal_set_message(t('Your content must be re-indexed before Apache Solr node access will be functional on searches.', array('@url' => url('admin/settings/apachesolr/index'))), 'error'); +} Index: contrib/apachesolr_nodeaccess/tests/apachesolr_nodeaccess.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/contrib/apachesolr_nodeaccess/tests/Attic/apachesolr_nodeaccess.test,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -p -r1.1.2.1 -r1.1.2.2 --- contrib/apachesolr_nodeaccess/tests/apachesolr_nodeaccess.test 22 Nov 2008 10:28:17 -0000 1.1.2.1 +++ contrib/apachesolr_nodeaccess/tests/apachesolr_nodeaccess.test 12 Apr 2009 03:16:31 -0000 1.1.2.2 @@ -1,5 +1,5 @@ drupalLogin($basic_user); include_once drupal_get_path('module', 'apachesolr') .'/Solr_Base_Query.php'; - $query = apachesolr_drupal_query(); + $query = apachesolr_current_query(); $params = array(); $subquery = _apachesolr_nodeaccess_build_subquery($basic_user); @@ -99,7 +99,7 @@ class DrupalApacheSolrNodeAccess extends "nodeaccess_nodeaccess_author" => $basic_user->uid, ); - $fields = $subquery->get_fields(); + $fields = $subquery->get_filters(); foreach ($fields as $field) { if (is_array($expected_criterion[$field['#name']])) { Index: tests/solr_base_query.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/apachesolr/tests/solr_base_query.test,v retrieving revision 1.1.4.9 retrieving revision 1.1.4.10 diff -u -p -r1.1.4.9 -r1.1.4.10 --- tests/solr_base_query.test 21 Nov 2008 21:23:36 -0000 1.1.4.9 +++ tests/solr_base_query.test 12 Apr 2009 03:16:33 -0000 1.1.4.10 @@ -1,5 +1,5 @@ queries as $string) { $query = apachesolr_drupal_query($string, TRUE); // force the query to be rebuilt without removing any fields. - $query->remove_field('fake-field-name'); + $query->remove_filter('fake-field-name'); $this->assertEqual($string, $query->get_query()); } } @@ -49,7 +49,7 @@ class DrupalSolrQueryTests extends Drupa function testAddTerm() { foreach ($this->queries as $string) { $query = apachesolr_drupal_query($string, TRUE); - $query->add_field('wham', '1'); + $query->add_filter('wham', '1'); $this->assertEqual($string . ' wham:1', $query->get_query()); } } @@ -57,27 +57,27 @@ class DrupalSolrQueryTests extends Drupa function testRemoveTerm() { $string = 'foo'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('', 'foo'); + $query->remove_filter('', 'foo'); $this->assertEqual('foo', $query->get_query()); $string = 'foo bar'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('', 'foo'); + $query->remove_filter('', 'foo'); $this->assertEqual('foo bar', $query->get_query()); $string = 'foo uid:1 bar'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('uid', '1'); + $query->remove_filter('uid', '1'); $this->assertEqual('foo bar', $query->get_query()); $string = 'foo uid:1 bar'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('uid'); + $query->remove_filter('uid'); $this->assertEqual('foo bar', $query->get_query()); $string = 'foo uid:1 bar uid:2 tid:3'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('uid', '1'); + $query->remove_filter('uid', '1'); // Not very beautiful, but probably best way there is: $pass = TRUE; @@ -103,7 +103,7 @@ class DrupalSolrQueryTests extends Drupa $string = 'foo uid:1 bar uid:2 tid:3'; $query = apachesolr_drupal_query($string, TRUE); - $query->remove_field('uid'); + $query->remove_filter('uid'); $this->assertEqual('foo bar tid:3', $query->get_query()); } }