/[drupal]/contributions/modules/faceted_search/faceted_search.module
ViewVC logotype

Contents of /contributions/modules/faceted_search/faceted_search.module

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


Revision 1.56 - (show annotations) (download) (as text)
Mon May 4 23:08:03 2009 UTC (6 months, 3 weeks ago) by davidlesieur
Branch: MAIN
CVS Tags: HEAD
Changes since 1.55: +3 -186 lines
File MIME type: text/x-php
#436586 by David Lesieur and EmTeedee: Allow environment to be saved when no facet can be enabled. Also moved some functions to .admin.inc file.
1 <?php
2 // $Id: faceted_search.module,v 1.55 2009/05/01 19:49:35 davidlesieur Exp $
3
4 /**
5 * @file
6 * An API for performing faceted searches.
7 */
8
9 require_once('./'. drupal_get_path('module', 'faceted_search') .'/faceted_search.inc');
10
11 /**
12 * Implementation of hook_help().
13 */
14 function faceted_search_help($path, $arg) {
15 switch ($path) {
16 case 'admin/help#faceted_search':
17 return '<p>'. t('A faceted search interface allows users to browse content in such a way that they can rapidly get acquainted with the scope and nature of the content without ever feeling lost. Such system relies on metadata (such as !categories) usually built specifically for !classification.', array('!categories' => l(t('categories'), 'admin/help/taxonomy'), '!classification' => l(t('faceted classification'), 'http://en.wikipedia.org/wiki/Faceted_classification'))) .'</p><p>'. t('Introductory information is provided in !article about when to use &mdash; and how to build &mdash; a faceted classification.', array('!article' => l(t('this article'), 'http://www.miskatonic.org/library/facet-web-howto.html'))) .'</p>';
18 }
19 }
20
21 /**
22 * Implementation of hook_perm().
23 */
24 function faceted_search_perm() {
25 return array('administer faceted search');
26 }
27
28 /**
29 * Implementation of hook_menu().
30 */
31 function faceted_search_menu() {
32 $items = array();
33 $items['admin/settings/faceted_search'] = array(
34 'title' => 'Faceted search',
35 'page callback' => 'faceted_search_list_page',
36 'access callback' => 'user_access',
37 'access arguments' => array('administer faceted search'),
38 'description' => 'Administer faceted search environments.',
39 'type' => MENU_NORMAL_ITEM,
40 'file' => 'faceted_search.admin.inc',
41 );
42 $items['admin/settings/faceted_search/list'] = array(
43 'title' => 'List',
44 'weight' => -10,
45 'type' => MENU_DEFAULT_LOCAL_TASK,
46 );
47 $items['admin/settings/faceted_search/add'] = array(
48 'title' => 'Add environment',
49 'page callback' => 'drupal_get_form',
50 'page arguments' => array('faceted_search_edit_form'),
51 'access callback' => 'user_access',
52 'access arguments' => array('administer faceted search'),
53 'type' => MENU_LOCAL_TASK,
54 'file' => 'faceted_search.admin.inc',
55 );
56 $items['admin/settings/faceted_search/delete/%faceted_search_env'] = array(
57 'load arguments' => array(5),
58 'page callback' => 'drupal_get_form',
59 'page arguments' => array('faceted_search_delete_form', 4),
60 'access arguments' => array('administer faceted search'),
61 'type' => MENU_CALLBACK,
62 'file' => 'faceted_search.admin.inc',
63 );
64 $items['admin/settings/faceted_search/%faceted_search_env'] = array(
65 'load arguments' => array(4),
66 'page callback' => 'drupal_get_form',
67 'page arguments' => array('faceted_search_edit_form', 3),
68 'access arguments' => array('administer faceted search'),
69 'type' => MENU_CALLBACK,
70 'file' => 'faceted_search.admin.inc',
71 );
72
73 return $items;
74 }
75
76 /**
77 * Similar to hook_faceted_search_collect(), this function collects the node
78 * keyword filters only and is called separately. Those filters allow searching
79 * keywords on the full nodes index.
80 *
81 * These are handled separately from other filters because they do not use a key
82 * in the search text and must therefore be processed after all other filters.
83 *
84 * @param $filters
85 * Array of filters into which this function should append new filters.
86 * @param $domain
87 * The domain where to look for filters. Possible values:
88 * - 'keyword filters': All possible keyword filters.
89 * - 'text': Filters specified in the specified search text.
90 * @param $env
91 * Id of the environment for which filters are collected. This is NULL in the
92 * case of a new environment.
93 * @param $text
94 * The search text. Only used when the domain is 'text'.
95 */
96 function faceted_search_collect_node_keyword_filters(&$filters, $domain, $env, $text = '') {
97 switch ($domain) {
98 case 'keyword filters':
99 $filter = new faceted_search_keyword_filter('node', t('Anywhere'));
100 $filter->set_weight(-999); // Default weight.
101 $filter->set_status(TRUE); // Default status.
102 $filters[] = $filter;
103 break;
104
105 case 'text':
106 $keys = faceted_search_parse_keywords($text);
107
108 // Create the filters.
109 foreach ($keys['positive'] as $keyword) {
110 if (is_array($keyword)) {
111 $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_or_category($keyword));
112 }
113 elseif (strpos($keyword, ' ')) {
114 $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_phrase_category($keyword));
115 }
116 else {
117 $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_and_category($keyword));
118 }
119 $filter->set_weight(-999); // Default weight.
120 $filter->set_status(TRUE); // Default status.
121 $filters[] = $filter;
122 }
123 foreach ($keys['negative'] as $keyword) {
124 $filter = new faceted_search_keyword_filter('node', '', new faceted_search_keyword_not_category($keyword));
125 $filter->set_weight(-999); // Default weight.
126 $filter->set_status(TRUE); // Default status.
127 $filters[] = $filter;
128 }
129 }
130 }
131
132 /**
133 * Return the collection of node types handled by a given environment.
134 *
135 * @return
136 * Array with type names. Empty when all types are allowed in the environment.
137 */
138 function faceted_search_types($env) {
139 $all_types = array();
140 foreach (array_keys(node_get_types('names')) as $type) {
141 $all_types[$type] = $type;
142 }
143 $types = array_filter($env->settings['types']);
144 if (!empty($types)) {
145 // Only return types that still exist.
146 $types = array_intersect($types, $all_types);
147 if (count($types) == count($all_types)) {
148 // All types are selected in the environment; do the same as if none was.
149 $types = array();
150 }
151 }
152 return $types;
153 }
154
155 /**
156 * Implementation of hook_locale().
157 */
158 function faceted_search_locale($op = 'groups', $group = NULL) {
159 switch ($op) {
160 case 'groups':
161 return array('faceted_search' => t('Faceted Search'));
162
163 case 'refresh':
164 if ($group == 'faceted_search') {
165 foreach (faceted_search_get_env_ids() as $env_id) {
166 $env = faceted_search_env_load($env_id);
167 faceted_search_env_locale_refresh($env);
168 }
169 }
170 }
171 }
172
173 /**
174 * Refresh the localized strings of an environment.
175 */
176 function faceted_search_env_locale_refresh($env) {
177 if (module_exists('i18nstrings')) {
178 i18nstrings_update_string('faceted_search:'. $env->env_id .':title', $env->settings['title']);
179 }
180 }
181
182 /**
183 * Delete the localized strings of an environment.
184 */
185 function faceted_search_env_locale_delete($env_id) {
186 if (module_exists('i18nstrings')) {
187 i18nstrings_remove_string('faceted_search:'. $env_id .':title');
188 }
189 }
190
191 /**
192 * Localize the name of a node type.
193 */
194 function faceted_search_localize_type($type, &$name) {
195 if (module_exists('i18ncontent')) {
196 $name = tt("nodetype:type:$type:name", $name);
197 }
198 }
199
200 /**
201 * Localize the names of all node types in a given array.
202 */
203 function faceted_search_localize_types(&$types) {
204 if (module_exists('i18ncontent')) {
205 foreach ($types as $type => $name) {
206 $types[$type] = tt("nodetype:type:$type:name", $name);
207 }
208 }
209 }
210
211 /**
212 * Load a search environment from the database, or from memory if its was
213 * already loaded.
214 *
215 * This function also acts as an argument loader for the menu system.
216 */
217 function faceted_search_env_load($env_id) {
218 static $env = NULL;
219
220 if (!is_numeric($env_id)) {
221 return FALSE;
222 }
223 if (!isset($env[$env_id])) {
224 $results = db_query('SELECT * FROM {faceted_search_env} WHERE env_id = %d', $env_id);
225 if ($record = db_fetch_object($results)) {
226 $env[$env_id] = new faceted_search($record);
227 }
228 }
229 return isset($env[$env_id]) ? $env[$env_id] : FALSE;
230 }
231
232 /**
233 * Return the ids of all existing environments.
234 */
235 function faceted_search_get_env_ids() {
236 static $env_ids = array();
237 if (!$env_ids) {
238 $results = db_query('SELECT env_id FROM {faceted_search_env}');
239 while ($result = db_fetch_object($results)) {
240 $env_ids[$result->env_id] = $result->env_id;
241 }
242 }
243 return $env_ids;
244 }
245
246 /**
247 * Load filter settings into an array.
248 *
249 * @param $env
250 * Environment whose filters should be loaded.
251 * @param $include_disabled
252 * Optional. When FALSE, only retrieve the settings of filters that are enabled. When
253 * TRUE, retrieve all settings. Defaults to FALSE.
254 * @param $filter_key
255 * Optional. Filter key to load the settings for. When not set, settings are
256 * retrieved for all filters.
257 * @return
258 * Array of filters settings keyed by filter key and filter id.
259 */
260 function faceted_search_load_filter_settings($env, $include_disabled = FALSE, $filter_key = NULL) {
261 $filter_settings = array();
262 if ($env->env_id) {
263 $where_status = $include_disabled ? '' : 'AND status = 1';
264 $where_filter_key = isset($filter_key) ? "AND filter_key = '%s'" : '';
265 $results = db_query("SELECT * FROM {faceted_search_filters} WHERE env_id = %d $where_status $where_filter_key", $env->env_id, $filter_key);
266 while ($settings = db_fetch_array($results)) {
267 $filter_settings[$settings['filter_key']][$settings['filter_id']] = $settings;
268 }
269 }
270 return $filter_settings;
271 }
272
273 /**
274 * Save filter settings.
275 *
276 * @param $env_id
277 * Environment whose filter settings are to be saved.
278 * @param $filter_settings
279 * Array where each element is itself an array of settings for an individual
280 * filter.
281 */
282 function faceted_search_save_filter_settings($env_id, $filter_settings) {
283 db_lock_table('faceted_search_filters');
284 db_query('DELETE FROM {faceted_search_filters} WHERE env_id = %d', $env_id);
285 foreach ($filter_settings as $settings) {
286 db_query("INSERT INTO {faceted_search_filters} (env_id, filter_key, filter_id, status, weight, sort, max_categories) VALUES (%d, '%s', '%s', %d, %d, '%s', %d)", $env_id, $settings['filter_key'], $settings['filter_id'], $settings['status'], $settings['weight'], isset($settings['sort']) ? $settings['sort'] : '', isset($settings['max_categories']) ? $settings['max_categories'] : 0);
287 }
288 db_unlock_tables();
289 }
290
291 /**
292 * Return a selection with all the filters from the given filter settings.
293 *
294 * The selection is an array keyed by filter key and filter id.
295 */
296 function faceted_search_get_filter_selection($all_filter_settings) {
297 $selection = array();
298 foreach ($all_filter_settings as $filter_key_settings) {
299 foreach ($filter_key_settings as $settings) {
300 $selection[$settings['filter_key']][$settings['filter_id']] = TRUE;
301 }
302 }
303 return $selection;
304 }
305
306 /**
307 * Build a search text from the specified array of filters.
308 *
309 * This can be seen as the opposite of class faceted_search's constructor, where
310 * a search text is parsed to build filters.
311 */
312 function faceted_search_build_text($filters) {
313 $texts_per_key = array();
314 foreach ($filters as $filter) {
315
316 $text = $filter->get_text();
317 if ($text != '') {
318 $texts_per_key[$filter->get_key()][] = $text;
319 }
320 }
321 // Build the combined search text
322 $text = '';
323 foreach ($texts_per_key as $key => $texts) {
324 if ($text) {
325 $text .= ' ';
326 }
327 if ($key == 'node') {
328 // This is a special case where the filter's key does not appear in text.
329 $text .= implode(' ', $texts);
330 }
331 else {
332 // TODO: It is really modules that should build this text since they are
333 // responsible for parsing it. Or maybe it should be both built and parsed
334 // for them.
335 $text .= $key .':'. implode(',', $texts);
336 }
337 }
338 return trim($text);
339 }
340
341 function faceted_search_quoted_query_extract($keys, $option) {
342 // Based on search_query_extract(), but matching a quoted value. Double-quotes
343 // are allowed into the value when escaped.
344 $escape_char = variable_get('faceted_search_escape_char', '\\');
345 if ($escape_char == '\\') {
346 $escape_char .= '\\'; // Special case for regex.
347 }
348 if (preg_match('/(^| )'. $option .':"(('. $escape_char .'.|[^"])*?)"( |$)/i', $keys, $matches)) {
349 return faceted_search_quoted_query_unescape($matches[2]);
350 }
351 }
352
353 function faceted_search_quoted_query_insert($keys, $option, $value = '') {
354 // Based on search_query_insert(), but matching a quoted value. Double-quotes
355 // are allowed into the value when escaped.
356 $escape_char = variable_get('faceted_search_escape_char', '\\');
357 if ($escape_char == '\\') {
358 $escape_char .= '\\'; // Special case for regex.
359 }
360 if (!is_null(search_query_extract($keys, $option))) {
361 $keys = trim(preg_replace('/(^| )'. $option .':"(('. $escape_char .'.|[^"])*?)"( |$)/i', ' ', $keys));
362 }
363 if ($value != '') {
364 $keys .= ' '. $option .':'. $value;
365 }
366 return $keys;
367 }
368
369 function faceted_search_quoted_query_escape($text) {
370 $escape_char = variable_get('faceted_search_escape_char', '\\');
371 return strtr($text, array('"' => $escape_char .'"', $escape_char => $escape_char . $escape_char));
372 }
373
374 function faceted_search_quoted_query_unescape($text) {
375 $escape_char = variable_get('faceted_search_escape_char', '\\');
376 return strtr($text, array($escape_char .'"' => '"', $escape_char . $escape_char => $escape_char));
377 }
378
379 /**
380 * Parse text for keyword search.
381 *
382 * @return Array with positive and negative keywords.
383 */
384 function faceted_search_parse_keywords($text) {
385 // Taken from search_parse_query() (search.module 1.207) - BEGIN
386 $keys = array('positive' => array(), 'negative' => array());
387
388 // Tokenize query string
389 $matches = array();
390 preg_match_all('/ (-?)("[^"]+"|[^" ]+)/i', ' '. $text, $matches, PREG_SET_ORDER);
391
392 // Classify tokens
393 $or = FALSE;
394 foreach ($matches as $match) {
395 $phrase = FALSE;
396 // Strip off phrase quotes
397 if ($match[2]{0} == '"') {
398 $match[2] = substr($match[2], 1, -1);
399 $phrase = TRUE;
400 }
401 // Simplify keyword according to indexing rules and external preprocessors
402 $words = search_simplify($match[2]);
403 // Re-explode in case simplification added more words, except when matching a phrase
404 $words = $phrase ? array($words) : preg_split('/ /', $words, -1, PREG_SPLIT_NO_EMPTY);
405 // Negative matches
406 if ($match[1] == '-') {
407 $keys['negative'] = array_merge($keys['negative'], $words);
408 }
409 // OR operator: instead of a single keyword, we store an array of all
410 // OR'd keywords.
411 elseif ($match[2] == 'OR' && count($keys['positive'])) {
412 $last = array_pop($keys['positive']);
413 // Starting a new OR?
414 if (!is_array($last)) {
415 $last = array($last);
416 }
417 $keys['positive'][] = $last;
418 $or = TRUE;
419 continue;
420 }
421 // Plain keyword
422 else {
423 if ($or) {
424 // Add to last element (which is an array)
425 $keys['positive'][count($keys['positive']) - 1] = array_merge($keys['positive'][count($keys['positive']) - 1], $words);
426 }
427 else {
428 $keys['positive'] = array_merge($keys['positive'], $words);
429 }
430 }
431 $or = FALSE;
432 }
433 // Taken from search_parse_query() - END
434 return $keys;
435 }
436
437 /**
438 * Assign settings to filters and sort them.
439 */
440 function faceted_search_prepare_filters(&$filters, $settings) {
441 if (count($filters)) {
442 // Assign settings to each filter.
443 foreach ($filters as $index => $filter) {
444 if (isset($settings[$filter->get_key()][$filter->get_id()])) {
445 $filters[$index]->set($settings[$filter->get_key()][$filter->get_id()]);
446 }
447 }
448
449 // Sort filters.
450 uasort($filters, '_faceted_search_compare_filters');
451 }
452 }
453
454 /**
455 * Implementation of hook_theme().
456 */
457 function faceted_search_theme() {
458 return array(
459 'faceted_search_facets_settings' => array(
460 'arguments' => array('form' => NULL),
461 'file' => 'faceted_search.admin.inc',
462 ),
463 'faceted_search_keyword_filters_settings' => array(
464 'arguments' => array('form' => NULL),
465 'file' => 'faceted_search.admin.inc',
466 ),
467 'faceted_search_keyword_and_label' => array(
468 'arguments' => array('keyword' => NULL),
469 ),
470 'faceted_search_keyword_phrase_label' => array(
471 'arguments' => array('phrase' => NULL),
472 ),
473 'faceted_search_keyword_or_label' => array(
474 'arguments' => array('keywords' => NULL),
475 ),
476 'faceted_search_keyword_not_label' => array(
477 'arguments' => array('keyword' => NULL),
478 ),
479 );
480 }
481
482 function theme_faceted_search_keyword_and_label($keyword) {
483 return check_plain($keyword);
484 }
485
486 function theme_faceted_search_keyword_phrase_label($phrase) {
487 return check_plain($phrase);
488 }
489
490 function theme_faceted_search_keyword_or_label($keywords) {
491 foreach ($keywords as $index => $keyword) {
492 $keywords[$index] = check_plain($keyword);
493 }
494 return implode(' <em>OR</em> ', $keywords);
495 }
496
497 function theme_faceted_search_keyword_not_label($keyword) {
498 return '-'. check_plain($keyword);
499 }
500
501 /**
502 * Utility function to sort filters.
503 */
504 function _faceted_search_compare_filters($a, $b) {
505 if ($a->get_weight() == $b->get_weight()) {
506 if ($a->get_key() == $b->get_key() && $a->get_id() == $b->get_id() && $a->is_active() && $b->is_active()) {
507 // Same filter, then sort by active category.
508 $a_cat = $a->get_active_category();
509 $b_cat = $b->get_active_category();
510 if ($a_cat->get_weight() == $b_cat->get_weight()) {
511 return strcmp($a_cat->get_label(), $b_cat->get_label());
512 }
513 return ($a_cat->get_weight() < $b_cat->get_weight()) ? -1 : 1;
514 }
515 return strcmp($a->get_label(), $b->get_label());
516 }
517 return ($a->get_weight() < $b->get_weight()) ? -1 : 1;
518 }
519
520 /**
521 * Function used by uasort in drupal_render() to sort structured arrays
522 * by weight.
523 */
524 function _faceted_search_element_sort($a, $b) {
525 $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
526 $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
527 if ($a_weight == $b_weight) {
528 return 0;
529 }
530 return ($a_weight < $b_weight) ? -1 : 1;
531 }

  ViewVC Help
Powered by ViewVC 1.1.2