/[drupal]/contributions/modules/apachesolr/Solr_Base_Query.php
ViewVC logotype

Contents of /contributions/modules/apachesolr/Solr_Base_Query.php

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.9 - (show annotations) (download) (as text)
Mon Jun 29 23:23:06 2009 UTC (4 months, 4 weeks ago) by pwolanin
Branch: MAIN
CVS Tags: HEAD
Changes since 1.8: +10 -10 lines
File MIME type: text/x-php
sync with DRUPAL-6--1
1 <?php
2 // $Id: Solr_Base_Query.php,v 1.1.4.36 2009/06/18 20:45:36 pwolanin Exp $
3
4 class Solr_Base_Query implements Drupal_Solr_Query_Interface {
5
6 /**
7 * Extract all uses of one named field from a filter string e.g. 'type:book'
8 */
9 public function filter_extract(&$filterstring, $name) {
10 $extracted = array();
11 // Range queries. The "TO" is case-sensitive.
12 $patterns[] = '/(^| |-)'. $name .':([\[\{](\S+) TO (\S+)[\]\}])/';
13 // Match quoted values.
14 $patterns[] = '/(^| |-)'. $name .':"([^"]*)"/';
15 // Match unquoted values.
16 $patterns[] = '/(^| |-)'. $name .':([^ ]*)/';
17 foreach ($patterns as $p) {
18 if (preg_match_all($p, $filterstring, $matches, PREG_SET_ORDER)) {
19 foreach($matches as $match) {
20 $filter = array();
21 $filter['#query'] = $match[0];
22 $filter['#exclude'] = ($match[1] == '-');
23 $filter['#value'] = trim($match[2]);
24 if (isset($match[3])) {
25 // Extra data for range queries
26 $filter['#start'] = $match[3];
27 $filter['#end'] = $match[4];
28 }
29 $extracted[] = $filter;
30 // Update the local copy of $filters by removing the match.
31 $filterstring = str_replace($match[0], '', $filterstring);
32 }
33 }
34 }
35 return $extracted;
36 }
37
38 /**
39 * Takes an array $field and combines the #name and #value in a way
40 * suitable for use in a Solr query.
41 */
42 public function make_filter(array $filter) {
43 // If the field value has spaces, or : in it, wrap it in double quotes.
44 // unless it is a range query.
45 if (preg_match('/[ :]/', $filter['#value']) && !isset($filter['#start']) && !preg_match('/[\[\{]\S+ TO \S+[\]\}]/', $filter['#value'])) {
46 $filter['#value'] = '"'. $filter['#value']. '"';
47 }
48 $prefix = empty($filter['#exclude']) ? '' : '-';
49 return $prefix . $filter['#name'] . ':' . $filter['#value'];
50 }
51
52 /**
53 * Static shared by all instances, used to increment ID numbers.
54 */
55 protected static $idCount = 0;
56
57 /**
58 * Each query/subquery will have a unique ID
59 */
60 public $id;
61
62 /**
63 * A keyed array where the key is a position integer and the value
64 * is an array with #name and #value properties. Each value is a
65 * used for filter queries, e.g. array('#name' => 'uid', '#value' => 0)
66 * for anonymous content.
67 */
68 protected $fields;
69
70 /**
71 * The complete filter string for a query. Usually from $_GET['filters']
72 * Contains name:value pairs for filter queries. For example,
73 * "type:book" for book nodes.
74 */
75 protected $filterstring;
76
77 /**
78 * A mapping of field names from the URL to real index field names.
79 */
80 protected $field_map = array();
81
82 /**
83 * An array of subqueries.
84 */
85 protected $subqueries = array();
86
87 /**
88 * The search keywords.
89 */
90 protected $keys;
91
92 /**
93 * The search base path.
94 */
95 protected $base_path;
96
97 /**
98 * Apache_Solr_Service object
99 */
100 protected $solr;
101
102 protected $available_sorts;
103
104 /**
105 * @param $solr
106 * An instantiated Apache_Solr_Service Object.
107 * Can be instantiated from apachesolr_get_solr().
108 *
109 * @param $keys
110 * The string that a user would type into the search box. Suitable input
111 * may come from search_get_keys().
112 *
113 * @param $filterstring
114 * Key and value pairs that are applied as a filter query.
115 *
116 * @param $sortstring
117 * Visible string telling solr how to sort - added to output querystring.
118 *
119 * @param $base_path
120 * The search base path (without the keywords) for this query.
121 */
122 function __construct($solr, $keys, $filterstring, $sortstring, $base_path) {
123 $this->solr = $solr;
124 $this->keys = trim($keys);
125 $this->filterstring = trim($filterstring);
126 $this->solrsort = trim($sortstring);
127 $this->base_path = $base_path;
128 $this->id = ++self::$idCount;
129 $this->parse_filters();
130 $this->available_sorts = $this->default_sorts();
131 }
132
133 function __clone() {
134 $this->id = ++self::$idCount;
135 }
136
137 public function add_filter($field, $value, $exclude = FALSE) {
138 $this->fields[] = array('#exclude' => $exclude, '#name' => $field, '#value' => trim($value));
139 }
140
141 /**
142 * Get all filters, or the subset of filters for one field.
143 *
144 * @param $name
145 * Optional name of a Solr field.
146 */
147 public function get_filters($name = NULL) {
148 if (empty($name)) {
149 return $this->fields;
150 }
151 reset($this->fields);
152 $matches = array();
153 foreach ($this->fields as $filter) {
154 if ($filter['#name'] == $name) {
155 $matches[] = $filter;
156 }
157 }
158 return $matches;
159 }
160
161 public function remove_filter($name, $value = NULL) {
162 // We can only remove named fields.
163 if (empty($name)) {
164 return;
165 }
166 if (!isset($value)) {
167 foreach ($this->fields as $pos => $values) {
168 if ($values['#name'] == $name) {
169 unset($this->fields[$pos]);
170 }
171 }
172 }
173 else {
174 foreach ($this->fields as $pos => $values) {
175 if ($values['#name'] == $name && $values['#value'] == $value) {
176 unset($this->fields[$pos]);
177 }
178 }
179 }
180 }
181
182 public function has_filter($name, $value) {
183 foreach ($this->fields as $pos => $values) {
184 if (isset($values['#name']) && isset($values['#value']) && $values['#name'] == $name && $values['#value'] == $value) {
185 return TRUE;
186 }
187 }
188 return FALSE;
189 }
190
191 /**
192 * Handle aliases for field to make nicer URLs
193 *
194 * @param $field_map
195 * An array keyed with real Solr index field names, with value being the alias.
196 */
197 function add_field_aliases($field_map) {
198 $this->field_map = array_merge($this->field_map, $field_map);
199 // We have to re-parse the filters.
200 $this->parse_filters();
201 }
202
203 function get_field_aliases() {
204 return $this->field_map;
205 }
206
207 function clear_field_aliases() {
208 $this->field_map = array();
209 // We have to re-parse the filters.
210 $this->parse_filters();
211 }
212
213 /**
214 * A subquery is another instance of a Solr_Base_Query that should be joined
215 * to the query. The operator determines whether it will be joined with AND or
216 * OR.
217 *
218 * @param $query
219 * An instance of Drupal_Solr_Query_Interface.
220 *
221 * @param $operator
222 * 'AND' or 'OR'
223 */
224 public function add_subquery(Drupal_Solr_Query_Interface $query, $fq_operator = 'OR', $q_operator = 'AND') {
225 $this->subqueries[$query->id] = array('#query' => $query, '#fq_operator' => $fq_operator, '#q_operator' => $q_operator);
226 }
227
228 public function remove_subquery(Drupal_Solr_Query_Interface $query) {
229 unset($this->subqueries[$query->id]);
230 }
231
232 public function remove_subqueries() {
233 $this->subqueries = array();
234 }
235
236 public function set_solrsort($sortstring) {
237 $this->solrsort = trim($sortstring);
238 }
239
240 public function get_available_sorts() {
241 return $this->available_sorts;
242 }
243
244 public function set_available_sort($field, $sort) {
245 $this->available_sorts[$field] = $sort;
246 }
247
248 /**
249 * Returns a default list of sorts.
250 */
251 protected function default_sorts() {
252 return array(
253 'relevancy' => array('name' => t('Relevancy'), 'default' => 'asc'),
254 'sort_title' => array('name' => t('Title'), 'default' => 'asc'),
255 'type' => array('name' => t('Type'), 'default' => 'asc'),
256 'sort_name' => array('name' => t('Author'), 'default' => 'asc'),
257 'created' => array('name' => t('Date'), 'default' => 'desc'),
258 );
259 }
260
261 /**
262 * Return filters and sort in a form suitable for a query param to url().
263 */
264 public function get_url_querystring() {
265 $querystring = '';
266 if ($fq = $this->rebuild_fq(TRUE)) {
267 $querystring = 'filters='. rawurlencode(implode(' ', $fq));
268 }
269 if ($this->solrsort) {
270 $querystring .= ($querystring ? '&' : '') .'solrsort='. rawurlencode($this->solrsort);
271 }
272 return $querystring;
273 }
274
275 public function get_fq() {
276 return $this->rebuild_fq();
277 }
278
279 /**
280 * A function to get just the keyword components of the query,
281 * omitting any field:value portions.
282 */
283 public function get_query_basic() {
284 return $this->rebuild_query();
285 }
286
287 /**
288 * Return the search path.
289 *
290 * @param string $new_keywords
291 * Optional. When set, this string overrides the query's current keywords.
292 */
293 public function get_path($new_keywords = NULL) {
294 if ($new_keywords) {
295 return $this->base_path . '/' . $new_keywords;
296 }
297 return $this->base_path . '/' . $this->get_query_basic();
298 }
299
300 /**
301 * Build additional breadcrumb elements relative to $base.
302 */
303 public function get_breadcrumb($base = NULL) {
304 $progressive_crumb = array();
305 if (!isset($base)) {
306 $base = $this->get_path();
307 }
308
309 $search_keys = $this->get_query_basic();
310 if ($search_keys) {
311 $breadcrumb[] = l($search_keys, $base);
312 }
313
314 foreach ($this->fields as $field) {
315 $name = $field['#name'];
316 // Look for a field alias.
317 if (isset($this->field_map[$name])) {
318 $field['#name'] = $this->field_map[$name];
319 }
320 $progressive_crumb[] = $this->make_filter($field);
321 $options = array('query' => 'filters=' . rawurlencode(implode(' ', $progressive_crumb)));
322 if ($themed = theme("apachesolr_breadcrumb_" . $name, $field['#value'], $field['#exclude'])) {
323 $breadcrumb[] = l($themed, $base, $options);
324 }
325 else {
326 $breadcrumb[] = l($field['#value'], $base, $options);
327 }
328 }
329 // The last breadcrumb is the current page, so it shouldn't be a link.
330 $last = count($breadcrumb) - 1;
331 $breadcrumb[$last] = strip_tags($breadcrumb[$last]);
332
333 return $breadcrumb;
334 }
335
336 /**
337 * Parse the filter string in $this->filters into $this->fields.
338 *
339 * Builds an array of field name/value pairs.
340 */
341 protected function parse_filters() {
342 $this->fields = array();
343 $filterstring = $this->filterstring;
344
345 // Gets information about the fields already in solr index.
346 $index_fields = $this->solr->getFields();
347
348 foreach ((array) $index_fields as $name => $data) {
349 // Look for a field alias.
350 $alias = isset($this->field_map[$name]) ? $this->field_map[$name] : $name;
351 // Get the values for $name
352 $extracted = $this->filter_extract($filterstring, $alias);
353 if (count($extracted)) {
354 foreach ($extracted as $filter) {
355 $pos = strpos($this->filterstring, $filter['#query']);
356 // $solr_keys and $solr_crumbs are keyed on $pos so that query order
357 // is maintained. This is important for breadcrumbs.
358 $filter['#name'] = $name;
359 $this->fields[$pos] = $filter;
360 }
361 }
362 }
363 // Even though the array has the right keys they are likely in the wrong
364 // order. ksort() sorts the array by key while maintaining the key.
365 ksort($this->fields);
366 }
367
368 /**
369 * Builds a set of filter queries from $this->fields and all subqueries.
370 *
371 * Returns an array of strings that can be combined into
372 * a URL query parameter or passed to Solr as fq paramters.
373 */
374 protected function rebuild_fq($aliases = FALSE) {
375 $fq = array();
376 $fields = array();
377 foreach ($this->fields as $pos => $field) {
378 // Look for a field alias.
379 if ($aliases && isset($this->field_map[$field['#name']])) {
380 $field['#name'] = $this->field_map[$field['#name']];
381 }
382 $fq[] = $this->make_filter($field);
383 }
384 foreach ($this->subqueries as $id => $data) {
385 $subfq = $data['#query']->rebuild_fq($aliases);
386 if ($subfq) {
387 $operator = $data['#fq_operator'];
388 $fq[] = "(" . implode(" {$operator} ", $subfq) .")";
389 }
390 }
391 return $fq;
392 }
393
394 protected function rebuild_query() {
395 $query = $this->keys;
396 foreach ($this->subqueries as $id => $data) {
397 $operator = $data['#q_operator'];
398 $subquery = $data['#query']->get_query_basic();
399 if ($subquery) {
400 $query .= " {$operator} ({$subquery})";
401 }
402 }
403 return $query;
404 }
405 }

  ViewVC Help
Powered by ViewVC 1.1.2