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

Contents of /contributions/modules/sphinxsearch/sphinxsearch.module

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


Revision 1.5 - (show annotations) (download) (as text)
Fri Sep 12 02:44:22 2008 UTC (14 months, 2 weeks ago) by markuspetrux
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
Changes since 1.4: +129 -98 lines
File MIME type: text/x-php
- Ported module from D5 to D6.
- Bugfix: undefined class method in sphinxsearch_check_connection_page().
- Bugfix: added criterion class to taxonomy elements in advanced search form.
- Bugfix: added support for mysqli and pgsql to _sphinxsearch_db_reconnect().
1 <?php
2 // $Id: sphinxsearch.module,v 1.2.2.11 2008/09/03 01:38:55 markuspetrux Exp $
3
4 /**
5 * @file
6 * Sphinx search integration for Drupal, based on XMLPipe source type
7 * and support for main+delta index scheme.
8 */
9
10 /**
11 * Implementation of hook_help().
12 */
13 function sphinxsearch_help($path, $arg) {
14 switch ($path) {
15 case 'admin/help#sphinxsearch':
16 $output = '<p>'. t('Sphinx search module provides a fast and scalable alternative to Drupal core search for content. Sphinx search implementation is based on XMLPipe source type and support for main+delta index scheme.') .'</p>';
17 return $output;
18 case 'admin/settings/sphinxsearch':
19 return '<p>'. t('You can adjust the settings below to tweak the Sphinx behaviour.') .'</p>';
20 case 'sphinxsearch#noresults':
21 return t('Suggestions:<ul>
22 <li>Make sure all words are spelled correctly.</li>
23 <li>Try different or more general keywords.</li>
24 <li>Consider using less restrictive filters.</li>
25 </ul>');
26 }
27 }
28
29 /**
30 * Implementation of hook_theme()
31 */
32 function sphinxsearch_theme() {
33 return array(
34 'sphinxsearch_search_results' => array(
35 'arguments' => array('search_options' => NULL, 'search_results' => NULL),
36 ),
37 'sphinxsearch_faceted_block' => array(
38 'arguments' => array('facets' => NULL),
39 ),
40 'sphinxsearch_tagadelic_block' => array(
41 'arguments' => array('tags' => NULL),
42 ),
43 'sphinxsearch_tagadelic_more' => array(
44 'arguments' => array('vid' => NULL),
45 ),
46 'sphinxsearch_tagadelic_page' => array(
47 'arguments' => array('blocks' => NULL),
48 ),
49 );
50 }
51
52 /**
53 * Implementation of hook_perm().
54 */
55 function sphinxsearch_perm() {
56 return array('use sphinxsearch', 'administer sphinxsearch');
57 }
58
59 /**
60 * Implementation of hook_init().
61 */
62 function sphinxsearch_init() {
63 // Load module common functions.
64 $module_path = drupal_get_path('module', 'sphinxsearch');
65 require_once($module_path .'/sphinxsearch.common.inc');
66 require_once($module_path .'/sphinxsearch.taxonomy.inc');
67
68 // We need our own CSS in all pages because of tagadelic and similar blocks.
69 drupal_add_css($module_path .'/sphinxsearch.css');
70 }
71
72 /**
73 * Implementation of hook_menu().
74 */
75 function sphinxsearch_menu() {
76 $items = array();
77 $items['admin/settings/sphinxsearch'] = array(
78 'title' => 'Sphinx search',
79 'page callback' => 'drupal_get_form',
80 'page arguments' => array('sphinxsearch_settings'),
81 'access arguments' => array('administer sphinxsearch'),
82 'file' => 'sphinxsearch.admin.inc',
83 );
84 $items['admin/settings/sphinxsearch/settings'] = array(
85 'title' => 'Settings',
86 'description' => 'Administer Sphinx search module settings',
87 'access arguments' => array('administer sphinxsearch'),
88 'weight' => -10,
89 'type' => MENU_DEFAULT_LOCAL_TASK,
90 );
91 $items['admin/settings/sphinxsearch/check-connection'] = array(
92 'title' => 'Check connection',
93 'description' => 'Check connection to Sphinx searchd daemon',
94 'page callback' => 'sphinxsearch_check_connection_page',
95 'access arguments' => array('administer sphinxsearch'),
96 'weight' => 10,
97 'type' => MENU_LOCAL_TASK,
98 'file' => 'sphinxsearch.admin.inc',
99 );
100 $items[sphinxsearch_get_search_path()] = array(
101 'title' => 'Search',
102 'page callback' => 'sphinxsearch_search_page',
103 'access arguments' => array('use sphinxsearch'),
104 'type' => MENU_SUGGESTED_ITEM,
105 'file' => 'sphinxsearch.pages.inc',
106 );
107 if (module_exists('taxonomy') && !module_exists('tagadelic')) {
108 $items['tagadelic'] = array(
109 'title' => 'Tags',
110 'page callback' => 'sphinxsearch_tagadelic_page',
111 'access arguments' => array('use sphinxsearch'),
112 'type' => MENU_SUGGESTED_ITEM,
113 );
114 }
115 return $items;
116 }
117
118 /**
119 * Implementation of hook_block().
120 */
121 function sphinxsearch_block($op = 'list', $delta = 0, $edit = array()) {
122 if ($op == 'list') {
123 $blocks = array(
124 'searchbox' => array('info' => t('Sphinx search box')),
125 );
126 if (module_exists('taxonomy')) {
127 foreach (sphinxsearch_get_enabled_vocabularies() as $vid => $vocabulary) {
128 $blocks['tags'. $vid] = array('info' => t('Sphinx tags in @vocabulary', array('@vocabulary' => $vocabulary->name)));
129 }
130 $blocks['faceted'] = array('info' => t('Sphinx faceted search'));
131 }
132 return $blocks;
133 }
134 else if ($op == 'configure') {
135 if ($delta != 'searchbox') {
136 if (preg_match('#^(faceted|tags([0-9]+))$#', $delta, $match)) {
137 $vid = (isset($match[2]) ? (int)$match[2] : 0);
138 $blockmode = ($match[1] == 'faceted' ? 'faceted' : 'tagadelic');
139 $vocabularies = sphinxsearch_get_enabled_vocabularies();
140 if ($blockmode == 'faceted' || ($blockmode == 'tagadelic' && isset($vocabularies[$vid]))) {
141 $suffix = ($blockmode == 'tagadelic' ? '_'. $vid : '');
142 $form['sortmode'] = array(
143 '#type' => 'radios',
144 '#title' => t('Tagadelic sort order'),
145 '#options' => array(
146 'weight,asc' => t('by weight, ascending'),
147 'weight,desc' => t('by weight, descending'),
148 'title,asc' => t('by title, ascending'),
149 'title,desc' => t('by title, descending'),
150 'random,none' => t('random')
151 ),
152 '#default_value' => variable_get('sphinxsearch_block_'. $blockmode .'_sortmode'. $suffix, 'title,asc'),
153 '#description' => t('Determines the sort order of the tags in the cloud.'),
154 );
155 $form['tags'] = array(
156 '#type' => 'select',
157 '#title' => t('Tags to show'),
158 '#options' => drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100)),
159 '#default_value' => variable_get('sphinxsearch_block_'. $blockmode .'_tags'. $suffix, 20),
160 '#description' => t('The number of tags to show in this block.'),
161 );
162 $form['levels'] = array(
163 '#type' => 'select',
164 '#title' => t('Number of levels'),
165 '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)),
166 '#default_value' => variable_get('sphinxsearch_block_'. $blockmode .'_levels'. $suffix, 10),
167 '#description' => t('The number of levels between the least popular tags and the most popular ones. Different levels will be assigned a different class to be themed (see <code>sphinxsearch.css</code>).'),
168 );
169 if ($blockmode == 'tagadelic') {
170 $form['linkto'] = array(
171 '#type' => 'radios',
172 '#title' => t('Terms in the cloud link to'),
173 '#options' => array(
174 'taxonomy' => t('Taxonomy managed.'),
175 'search' => t('Search results with predefined filters.'),
176 ),
177 '#default_value' => variable_get('sphinxsearch_block_'. $blockmode .'_linkto'. $suffix, 'search'),
178 '#description' => t('Determines where the links of terms in the tag cloud should point to.'),
179 );
180 }
181 return $form;
182 }
183 }
184 }
185 }
186 else if ($op == 'save') {
187 if ($delta != 'searchbox') {
188 if (preg_match('#^(faceted|tags([0-9]+))$#', $delta, $match)) {
189 $vid = (isset($match[2]) ? (int)$match[2] : 0);
190 $blockmode = ($match[1] == 'faceted' ? 'faceted' : 'tagadelic');
191 $vocabularies = sphinxsearch_get_enabled_vocabularies();
192 if ($blockmode == 'faceted' || ($blockmode == 'tagadelic' && isset($vocabularies[$vid]))) {
193 $suffix = ($blockmode == 'tagadelic' ? '_'. $vid : '');
194 variable_set('sphinxsearch_block_'. $blockmode .'_sortmode'. $suffix, $edit['sortmode']);
195 variable_set('sphinxsearch_block_'. $blockmode .'_tags'. $suffix, $edit['tags']);
196 variable_set('sphinxsearch_block_'. $blockmode .'_levels'. $suffix, $edit['levels']);
197 if ($blockmode == 'tagadelic') {
198 variable_set('sphinxsearch_block_'. $blockmode .'_linkto'. $suffix, $edit['linkto']);
199 }
200 }
201 }
202 }
203 }
204 else if ($op == 'view') {
205 if (!user_access('use sphinxsearch') || sphinxsearch_flood_limit_exceeded()) {
206 return;
207 }
208 if ($delta == 'searchbox') {
209 // Hide block if current page is search.
210 if (!sphinxsearch_is_search_path()) {
211 return array(
212 'subject' => t('Search'),
213 'content' => drupal_get_form('sphinxsearch_search_box'),
214 );
215 }
216 }
217 else if (preg_match('#^(faceted|tags([0-9]+))$#', $delta, $match)) {
218 $vid = (isset($match[2]) ? (int)$match[2] : 0);
219 $blockmode = ($match[1] == 'faceted' ? 'faceted' : 'tagadelic');
220 if ($blockmode == 'tagadelic') {
221 // Tagadelic box for selected vocabulary.
222 $vocabularies = sphinxsearch_get_enabled_vocabularies();
223 if (isset($vocabularies[$vid])) {
224 $tags = sphinxsearch_tagadelic_build_data(array(
225 'type' => 'taxonomy', 'vid' => $vid,
226 'count' => (int)variable_get('sphinxsearch_block_tagadelic_tags_'. $vid, 20),
227 'levels' => (int)variable_get('sphinxsearch_block_tagadelic_levels_'. $vid, 10),
228 'sortmode' => variable_get('sphinxsearch_block_tagadelic_sortmode_'. $vid, 'title,asc'),
229 'linkto' => variable_get('sphinxsearch_block_tagadelic_linkto_'. $vid, 'search'),
230 ));
231 if (!empty($tags)) {
232 $content = theme('sphinxsearch_tagadelic_block', $tags);
233 if (variable_get('sphinxsearch_page_tagadelic_tags', 100) > variable_get('sphinxsearch_block_tagadelic_tags_'. $vid, 20)) {
234 $content .= theme('sphinxsearch_tagadelic_more', $vid);
235 }
236 return array('content' => $content, 'subject' => t('Tags in @vocabulary', array('@vocabulary' => $vocabularies[$vid]->name)));
237 }
238 }
239 }
240 else if ($blockmode == 'faceted') {
241 // Faceted search box for all supported grouping methods.
242 // Rendered only within the context of search pages.
243 if (sphinxsearch_is_search_path()) {
244 $facets = sphinxsearch_faceted_build_data(array(
245 'count' => (int)variable_get('sphinxsearch_block_faceted_tags', 20),
246 'levels' => (int)variable_get('sphinxsearch_block_faceted_levels', 10),
247 'sortmode' => variable_get('sphinxsearch_block_faceted_sortmode', 'title,asc'),
248 ));
249 if (!empty($facets)) {
250 $content = theme('sphinxsearch_faceted_block', $facets);
251 return array('content' => $content, 'subject' => t('Faceted search'));
252 }
253 }
254 }
255 }
256 }
257 }
258
259 /**
260 * Render a search box form.
261 */
262 function sphinxsearch_search_box() {
263 $form = array();
264 // Build basic search box form.
265 $form['inline'] = array('#prefix' => '<div class="container-inline">', '#suffix' => '</div>');
266 $form['inline']['keys'] = array(
267 '#type' => 'textfield',
268 '#size' => 30,
269 '#default_value' => '',
270 '#attributes' => array('title' => t('Enter the terms you wish to search for.')),
271 );
272 $form['inline']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
273 return $form;
274 }
275
276 /**
277 * Process a search box form submission.
278 */
279 function sphinxsearch_search_box_submit($form, &$form_state) {
280 $query = array();
281 $keys = preg_replace('#\s+#', ' ', trim($form_state['values']['keys']));
282 if (!empty($keys)) {
283 $query['keys'] = $keys;
284 }
285 // Transform POST into a GET request.
286 sphinxsearch_goto_search($query);
287 }
288
289 /**
290 * Implementation of hook_nodeapi().
291 */
292 function sphinxsearch_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
293 if ($op == 'delete') {
294 // Update Sphinx documents when node is deleted.
295 $enabled_node_types = sphinxsearch_get_enabled_node_types();
296 if (empty($enabled_node_types) || in_array($node->type, $enabled_node_types)) {
297 sphinxsearch_delete_node_from_index($node->nid);
298 }
299 }
300 }
301
302 /**
303 * Delete a node from Sphinx indexes.
304 *
305 * Sphinx document deletions are updated in realtime using UpdateAttributes()
306 * API to set the is_deleted attribute of the document to 1.
307 * These Sphinx updates are sent to all indexes behind a distributed index.
308 * To completely remove all deleted nodes from Sphinx indexes, it is
309 * necessary to rebuild main indexes from time to time.
310 *
311 * @param int $nid
312 * Node Identifier.
313 */
314 function sphinxsearch_delete_node_from_index($nid) {
315 $sphinxsearch_query_index = variable_get('sphinxsearch_query_index', '');
316 $sphinxsearch = &sphinxsearch_get_client();
317
318 $count = $sphinxsearch->UpdateAttributes($sphinxsearch_query_index, array('is_deleted'), array($nid => array(1)));
319
320 // Note: count should be number of updated documents, -1 if error.
321 if ($count <= 0) {
322 watchdog('sphinxsearch', 'Node @nid could not be deleted from Sphinx index. Sphinx error message: !message', array(
323 '@nid' => $nid,
324 '!message' => $sphinxsearch->GetLastError()
325 ), WATCHDOG_WARNING);
326 }
327 }
328
329 /**
330 * Obtain PHP memory_limit.
331 *
332 * Requirements: PHP needs to be compiled with --enable-memory-limit.
333 * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
334 *
335 * @return int
336 * Memory limit in bytes, -1 if error.
337 */
338 function sphinxsearch_get_memory_limit() {
339 if (!function_exists('memory_get_usage')) {
340 return -1;
341 }
342 $memory_limit = trim(@ini_get('memory_limit'));
343 if (is_numeric($memory_limit)) {
344 $memory_limit = (int)$memory_limit;
345 }
346 else {
347 if (!preg_match('#([0-9]+)(K|M|G)#', strtoupper($memory_limit), $matches)) {
348 return -1;
349 }
350 $memory_limit = (int)$matches[1];
351 switch ($matches[2]) {
352 case 'G':
353 $memory_limit *= 1024;
354 case 'M':
355 $memory_limit *= 1024;
356 case 'K':
357 $memory_limit *= 1024;
358 }
359 }
360 return $memory_limit;
361 }
362
363 /**
364 * Obtain the text representation of a node.
365 * All HTML is removed.
366 *
367 * @param object reference $node
368 * Node reference to extract text from.
369 * @return string
370 * Text representation of the node.
371 */
372 function sphinxsearch_get_node_text(&$node) {
373 // Build the node body.
374 $node = node_build_content($node, FALSE, FALSE);
375 $node->body = drupal_render($node->content);
376
377 // Allow modules to modify the fully-built node.
378 node_invoke_nodeapi($node, 'alter');
379
380 $text = $node->body;
381
382 // Fetch extra data normally not visible
383 $extra = node_invoke_nodeapi($node, 'update index');
384 foreach ($extra as $t) {
385 $text .= $t;
386 }
387 unset($extra, $t);
388
389 // Strip control characters that aren't valid in xml.
390 // See http://www.w3.org/International/questions/qa-controls
391 $text = preg_replace('#[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]#S', ' ', $text);
392
393 // Strip off all tags, but insert space before/after them to keep word boundaries.
394 $text = str_replace(array('<', '>', '[', ']'), array(' <', '> ', ' ', ' '), $text);
395 $text = preg_replace('#<(script|style)[^>]*>.*</\1>#', ' ', $text);
396 $text = strip_tags($text);
397
398 // Reduce size a little removing redudant spaces and line breaks.
399 $text = preg_replace("# +#", ' ', $text);
400 $text = preg_replace("#(\s*)\n+#", "\n", $text);
401
402 return $text;
403 }
404
405 /**
406 * Get a unique numeric ID for the given node type (XMLPipe documents).
407 * Note: Used to convert content types to and from Sphinx attributes.
408 *
409 * @param string $op
410 * 'id', 'name' or 'reset'.
411 * @param mixed $type
412 * string When $op == 'id'.
413 * int When $op == 'name'.
414 * @return mixed
415 * int When $op == 'id'.
416 * string When $op == 'name'.
417 * TRUE When $op == 'reset'.
418 * FALSE Not found
419 */
420 function sphinxsearch_xmlpipe_nodetype($op, $type = '') {
421 static $node_type_ids;
422 if (!isset($node_type_ids)) {
423 $node_type_ids = variable_get('sphinxsearch_node_type_ids', array(''));
424 }
425 if ($op == 'id') {
426 if (!in_array($type, $node_type_ids)) {
427 $node_type_ids[] = $type;
428 variable_set('sphinxsearch_node_type_ids', $node_type_ids);
429 }
430 return (int)array_search($type, $node_type_ids);
431 }
432 else if ($op == 'name') {
433 if (isset($node_type_ids[$type])) {
434 return $node_type_ids[$type];
435 }
436 }
437 else if ($op == 'reset') {
438 variable_set('sphinxsearch_node_type_ids', array(''));
439 return TRUE;
440 }
441 return FALSE;
442 }
443
444 /**
445 * Obtain list of node types enabled to be indexed.
446 *
447 * @return array
448 * list of node types.
449 */
450 function sphinxsearch_get_enabled_node_types() {
451 static $node_types;
452 if (!isset($node_types)) {
453 $node_types = array();
454 foreach (node_get_types() as $node_type) {
455 if (variable_get('sphinxsearch_include_node_type_'. $node_type->type, 0)) {
456 $node_types[] = $node_type->type;
457 }
458 }
459 }
460 return $node_types;
461 }
462
463 /**
464 * Build SQL condition for filtering nodes by enabled node types.
465 *
466 * @param string $table_alias
467 * Table alias. Empty by default. Tipical alias for node table is 'n'.
468 * @return string
469 * SQL condition.
470 */
471 function sphinxsearch_get_enabled_node_types_condition($table_alias = '') {
472 static $condition;
473 if (!isset($condition)) {
474 $condition = '';
475 $enabled_node_types = sphinxsearch_get_enabled_node_types();
476 if (!empty($enabled_node_types)) {
477 if (count($enabled_node_types) == 1) {
478 $condition .= "= '". db_escape_string($enabled_node_types[0]) ."'";
479 }
480 else {
481 $types = array();
482 foreach ($enabled_node_types as $type) {
483 $types[] = "'". db_escape_string($type) ."'";
484 }
485 $condition .= 'IN ('. implode(', ', $types) .')';
486 }
487 }
488 }
489 if (empty($condition)) {
490 return '';
491 }
492 return (!empty($table_alias) ? $table_alias .'.' : '') .'type '. $condition;
493 }

  ViewVC Help
Powered by ViewVC 1.1.2