source: trunk/forum/includes/search/fulltext_mysql.php@ 448

Last change on this file since 448 was 400, checked in by george, 16 years ago
  • Přidáno: Nové forum phpBB 3.
File size: 23.8 KB
Line 
1<?php
2/**
3*
4* @package search
5* @version $Id: fulltext_mysql.php 8814 2008-09-04 12:01:47Z acydburn $
6* @copyright (c) 2005 phpBB Group
7* @license http://opensource.org/licenses/gpl-license.php GNU Public License
8*
9*/
10
11/**
12* @ignore
13*/
14if (!defined('IN_PHPBB'))
15{
16 exit;
17}
18
19/**
20* @ignore
21*/
22include_once($phpbb_root_path . 'includes/search/search.' . $phpEx);
23
24/**
25* fulltext_mysql
26* Fulltext search for MySQL
27* @package search
28*/
29class fulltext_mysql extends search_backend
30{
31 var $stats = array();
32 var $word_length = array();
33 var $split_words = array();
34 var $search_query;
35 var $common_words = array();
36 var $pcre_properties = false;
37 var $mbstring_regex = false;
38
39 function fulltext_mysql(&$error)
40 {
41 global $config;
42
43 $this->word_length = array('min' => $config['fulltext_mysql_min_word_len'], 'max' => $config['fulltext_mysql_max_word_len']);
44
45 if (version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>=')))
46 {
47 // While this is the proper range of PHP versions, PHP may not be linked with the bundled PCRE lib and instead with an older version
48 if (@preg_match('/\p{L}/u', 'a') !== false)
49 {
50 $this->pcre_properties = true;
51 }
52 }
53
54 if (function_exists('mb_ereg'))
55 {
56 $this->mbstring_regex = true;
57 mb_regex_encoding('UTF-8');
58 }
59
60 $error = false;
61 }
62
63 /**
64 * Checks for correct MySQL version and stores min/max word length in the config
65 */
66 function init()
67 {
68 global $db, $user;
69
70 if ($db->sql_layer != 'mysql4' && $db->sql_layer != 'mysqli')
71 {
72 return $user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_VERSION'];
73 }
74
75 $result = $db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\'');
76 $info = $db->sql_fetchrow($result);
77 $db->sql_freeresult($result);
78
79 $engine = '';
80 if (isset($info['Engine']))
81 {
82 $engine = $info['Engine'];
83 }
84 else if (isset($info['Type']))
85 {
86 $engine = $info['Type'];
87 }
88
89 if ($engine != 'MyISAM')
90 {
91 return $user->lang['FULLTEXT_MYSQL_NOT_MYISAM'];
92 }
93
94 $sql = 'SHOW VARIABLES
95 LIKE \'ft\_%\'';
96 $result = $db->sql_query($sql);
97
98 $mysql_info = array();
99 while ($row = $db->sql_fetchrow($result))
100 {
101 $mysql_info[$row['Variable_name']] = $row['Value'];
102 }
103 $db->sql_freeresult($result);
104
105 set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']);
106 set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']);
107
108 return false;
109 }
110
111 /**
112 * Splits keywords entered by a user into an array of words stored in $this->split_words
113 * Stores the tidied search query in $this->search_query
114 *
115 * @param string &$keywords Contains the keyword as entered by the user
116 * @param string $terms is either 'all' or 'any'
117 * @return bool false if no valid keywords were found and otherwise true
118 */
119 function split_keywords(&$keywords, $terms)
120 {
121 global $config;
122
123 if ($terms == 'all')
124 {
125 $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#\+#', '#-#', '#\|#');
126 $replace = array(' +', ' |', ' -', ' +', ' -', ' |');
127
128 $keywords = preg_replace($match, $replace, $keywords);
129 }
130
131 // Filter out as above
132 $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords)));
133
134 // Split words
135 if ($this->pcre_properties)
136 {
137 $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords)));
138 }
139 else if ($this->mbstring_regex)
140 {
141 $split_keywords = mb_ereg_replace('([^\w\'*"()])', '\\1\\1', str_replace('\'\'', '\' \'', trim($split_keywords)));
142 }
143 else
144 {
145 $split_keywords = preg_replace('#([^\w\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords)));
146 }
147
148 if ($this->pcre_properties)
149 {
150 $matches = array();
151 preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches);
152 $this->split_words = $matches[1];
153 }
154 else if ($this->mbstring_regex)
155 {
156 mb_ereg_search_init($split_keywords, '(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)');
157
158 while (($word = mb_ereg_search_regs()))
159 {
160 $this->split_words[] = $word[1];
161 }
162 }
163 else
164 {
165 $matches = array();
166 preg_match_all('#(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)#u', $split_keywords, $matches);
167 $this->split_words = $matches[1];
168 }
169
170 // to allow phrase search, we need to concatenate quoted words
171 $tmp_split_words = array();
172 $phrase = '';
173 foreach ($this->split_words as $word)
174 {
175 if ($phrase)
176 {
177 $phrase .= ' ' . $word;
178 if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1)
179 {
180 $tmp_split_words[] = $phrase;
181 $phrase = '';
182 }
183 }
184 else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1)
185 {
186 $phrase = $word;
187 }
188 else
189 {
190 $tmp_split_words[] = $word . ' ';
191 }
192 }
193 if ($phrase)
194 {
195 $tmp_split_words[] = $phrase;
196 }
197
198 $this->split_words = $tmp_split_words;
199
200 unset($tmp_split_words);
201 unset($phrase);
202
203 foreach ($this->split_words as $i => $word)
204 {
205 $clean_word = preg_replace('#^[+\-|"]#', '', $word);
206
207 // check word length
208 $clean_len = utf8_strlen(str_replace('*', '', $clean_word));
209 if (($clean_len < $config['fulltext_mysql_min_word_len']) || ($clean_len > $config['fulltext_mysql_max_word_len']))
210 {
211 $this->common_words[] = $word;
212 unset($this->split_words[$i]);
213 }
214 }
215
216 if ($terms == 'any')
217 {
218 $this->search_query = '';
219 foreach ($this->split_words as $word)
220 {
221 if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0))
222 {
223 $word = substr($word, 1);
224 }
225 $this->search_query .= $word . ' ';
226 }
227 }
228 else
229 {
230 $this->search_query = '';
231 foreach ($this->split_words as $word)
232 {
233 if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0))
234 {
235 $this->search_query .= $word . ' ';
236 }
237 else if (strpos($word, '|') === 0)
238 {
239 $this->search_query .= substr($word, 1) . ' ';
240 }
241 else
242 {
243 $this->search_query .= '+' . $word . ' ';
244 }
245 }
246 }
247
248 $this->search_query = utf8_htmlspecialchars($this->search_query);
249
250 if ($this->search_query)
251 {
252 $this->split_words = array_values($this->split_words);
253 sort($this->split_words);
254 return true;
255 }
256 return false;
257 }
258
259 /**
260 * Turns text into an array of words
261 */
262 function split_message($text)
263 {
264 global $config;
265
266 // Split words
267 if ($this->pcre_properties)
268 {
269 $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text)));
270 }
271 else if ($this->mbstring_regex)
272 {
273 $text = mb_ereg_replace('([^\w\'*])', '\\1\\1', str_replace('\'\'', '\' \'', trim($text)));
274 }
275 else
276 {
277 $text = preg_replace('#([^\w\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text)));
278 }
279
280 if ($this->pcre_properties)
281 {
282 $matches = array();
283 preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches);
284 $text = $matches[1];
285 }
286 else if ($this->mbstring_regex)
287 {
288 mb_ereg_search_init($text, '(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)');
289
290 $text = array();
291 while (($word = mb_ereg_search_regs()))
292 {
293 $text[] = $word[1];
294 }
295 }
296 else
297 {
298 $matches = array();
299 preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#u', $text, $matches);
300 $text = $matches[1];
301 }
302
303 // remove too short or too long words
304 $text = array_values($text);
305 for ($i = 0, $n = sizeof($text); $i < $n; $i++)
306 {
307 $text[$i] = trim($text[$i]);
308 if (utf8_strlen($text[$i]) < $config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_mysql_max_word_len'])
309 {
310 unset($text[$i]);
311 }
312 }
313
314 return array_values($text);
315 }
316
317 /**
318 * Performs a search on keywords depending on display specific params. You have to run split_keywords() first.
319 *
320 * @param string $type contains either posts or topics depending on what should be searched for
321 * @param string &$fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched)
322 * @param string &$terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words)
323 * @param array &$sort_by_sql contains SQL code for the ORDER BY part of a query
324 * @param string &$sort_key is the key of $sort_by_sql for the selected sorting
325 * @param string &$sort_dir is either a or d representing ASC and DESC
326 * @param string &$sort_days specifies the maximum amount of days a post may be old
327 * @param array &$ex_fid_ary specifies an array of forum ids which should not be searched
328 * @param array &$m_approve_fid_ary specifies an array of forum ids in which the searcher is allowed to view unapproved posts
329 * @param int &$topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched
330 * @param array &$author_ary an array of author ids if the author should be ignored during the search the array is empty
331 * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
332 * @param int $start indicates the first index of the page
333 * @param int $per_page number of ids each page is supposed to contain
334 * @return boolean|int total number of results
335 *
336 * @access public
337 */
338 function keyword_search($type, &$fields, &$terms, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page)
339 {
340 global $config, $db;
341
342 // No keywords? No posts.
343 if (!$this->search_query)
344 {
345 return false;
346 }
347
348 // generate a search_key from all the options to identify the results
349 $search_key = md5(implode('#', array(
350 implode(', ', $this->split_words),
351 $type,
352 $fields,
353 $terms,
354 $sort_days,
355 $sort_key,
356 $topic_id,
357 implode(',', $ex_fid_ary),
358 implode(',', $m_approve_fid_ary),
359 implode(',', $author_ary)
360 )));
361
362 // try reading the results from cache
363 $result_count = 0;
364 if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
365 {
366 return $result_count;
367 }
368
369 $id_ary = array();
370
371 $join_topic = ($type == 'posts') ? false : true;
372
373 // Build sql strings for sorting
374 $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC');
375 $sql_sort_table = $sql_sort_join = '';
376
377 switch ($sql_sort[0])
378 {
379 case 'u':
380 $sql_sort_table = USERS_TABLE . ' u, ';
381 $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster ';
382 break;
383
384 case 't':
385 $join_topic = true;
386 break;
387
388 case 'f':
389 $sql_sort_table = FORUMS_TABLE . ' f, ';
390 $sql_sort_join = ' AND f.forum_id = p.forum_id ';
391 break;
392 }
393
394 // Build some display specific sql strings
395 switch ($fields)
396 {
397 case 'titleonly':
398 $sql_match = 'p.post_subject';
399 $sql_match_where = ' AND p.post_id = t.topic_first_post_id';
400 $join_topic = true;
401 break;
402
403 case 'msgonly':
404 $sql_match = 'p.post_text';
405 $sql_match_where = '';
406 break;
407
408 case 'firstpost':
409 $sql_match = 'p.post_subject, p.post_text';
410 $sql_match_where = ' AND p.post_id = t.topic_first_post_id';
411 $join_topic = true;
412 break;
413
414 default:
415 $sql_match = 'p.post_subject, p.post_text';
416 $sql_match_where = '';
417 break;
418 }
419
420 if (!sizeof($m_approve_fid_ary))
421 {
422 $m_approve_fid_sql = ' AND p.post_approved = 1';
423 }
424 else if ($m_approve_fid_ary === array(-1))
425 {
426 $m_approve_fid_sql = '';
427 }
428 else
429 {
430 $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';
431 }
432
433 $sql_select = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : '';
434 $sql_select = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id';
435 $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : '';
436 $field = ($type == 'posts') ? 'post_id' : 'topic_id';
437 $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(', ', $author_ary) . ')';
438
439 $sql_where_options = $sql_sort_join;
440 $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : '';
441 $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : '';
442 $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '';
443 $sql_where_options .= $m_approve_fid_sql;
444 $sql_where_options .= (sizeof($author_ary)) ? ' AND p.poster_id ' . $sql_author : '';
445 $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';
446 $sql_where_options .= $sql_match_where;
447
448 $sql = "SELECT $sql_select
449 FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p
450 WHERE MATCH ($sql_match) AGAINST ('" . $db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE)
451 $sql_where_options
452 ORDER BY $sql_sort";
453 $result = $db->sql_query_limit($sql, $config['search_block_size'], $start);
454
455 while ($row = $db->sql_fetchrow($result))
456 {
457 $id_ary[] = $row[$field];
458 }
459 $db->sql_freeresult($result);
460
461 $id_ary = array_unique($id_ary);
462
463 if (!sizeof($id_ary))
464 {
465 return false;
466 }
467
468 // if the total result count is not cached yet, retrieve it from the db
469 if (!$result_count)
470 {
471 $sql = 'SELECT FOUND_ROWS() as result_count';
472 $result = $db->sql_query($sql);
473 $result_count = (int) $db->sql_fetchfield('result_count');
474 $db->sql_freeresult($result);
475
476 if (!$result_count)
477 {
478 return false;
479 }
480 }
481
482 // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page
483 $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir);
484 $id_ary = array_slice($id_ary, 0, (int) $per_page);
485
486 return $result_count;
487 }
488
489 /**
490 * Performs a search on an author's posts without caring about message contents. Depends on display specific params
491 *
492 * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
493 * @param int $start indicates the first index of the page
494 * @param int $per_page number of ids each page is supposed to contain
495 * @return total number of results
496 */
497 function author_search($type, $firstpost_only, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$m_approve_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page)
498 {
499 global $config, $db;
500
501 // No author? No posts.
502 if (!sizeof($author_ary))
503 {
504 return 0;
505 }
506
507 // generate a search_key from all the options to identify the results
508 $search_key = md5(implode('#', array(
509 '',
510 $type,
511 ($firstpost_only) ? 'firstpost' : '',
512 '',
513 '',
514 $sort_days,
515 $sort_key,
516 $topic_id,
517 implode(',', $ex_fid_ary),
518 implode(',', $m_approve_fid_ary),
519 implode(',', $author_ary)
520 )));
521
522 // try reading the results from cache
523 $result_count = 0;
524 if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
525 {
526 return $result_count;
527 }
528
529 $id_ary = array();
530
531 // Create some display specific sql strings
532 $sql_author = $db->sql_in_set('p.poster_id', $author_ary);
533 $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '';
534 $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : '';
535 $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';
536 $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : '';
537
538 // Build sql strings for sorting
539 $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC');
540 $sql_sort_table = $sql_sort_join = '';
541 switch ($sql_sort[0])
542 {
543 case 'u':
544 $sql_sort_table = USERS_TABLE . ' u, ';
545 $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster ';
546 break;
547
548 case 't':
549 $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : '';
550 $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : '';
551 break;
552
553 case 'f':
554 $sql_sort_table = FORUMS_TABLE . ' f, ';
555 $sql_sort_join = ' AND f.forum_id = p.forum_id ';
556 break;
557 }
558
559 if (!sizeof($m_approve_fid_ary))
560 {
561 $m_approve_fid_sql = ' AND p.post_approved = 1';
562 }
563 else if ($m_approve_fid_ary == array(-1))
564 {
565 $m_approve_fid_sql = '';
566 }
567 else
568 {
569 $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';
570 }
571
572 // If the cache was completely empty count the results
573 $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS ';
574
575 // Build the query for really selecting the post_ids
576 if ($type == 'posts')
577 {
578 $sql = "SELECT {$calc_results}p.post_id
579 FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . "
580 WHERE $sql_author
581 $sql_topic_id
582 $sql_firstpost
583 $m_approve_fid_sql
584 $sql_fora
585 $sql_sort_join
586 $sql_time
587 ORDER BY $sql_sort";
588 $field = 'post_id';
589 }
590 else
591 {
592 $sql = "SELECT {$calc_results}t.topic_id
593 FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
594 WHERE $sql_author
595 $sql_topic_id
596 $sql_firstpost
597 $m_approve_fid_sql
598 $sql_fora
599 AND t.topic_id = p.topic_id
600 $sql_sort_join
601 $sql_time
602 GROUP BY t.topic_id
603 ORDER BY $sql_sort";
604 $field = 'topic_id';
605 }
606
607 // Only read one block of posts from the db and then cache it
608 $result = $db->sql_query_limit($sql, $config['search_block_size'], $start);
609
610 while ($row = $db->sql_fetchrow($result))
611 {
612 $id_ary[] = $row[$field];
613 }
614 $db->sql_freeresult($result);
615
616 // retrieve the total result count if needed
617 if (!$result_count)
618 {
619 $sql = 'SELECT FOUND_ROWS() as result_count';
620 $result = $db->sql_query($sql);
621 $result_count = (int) $db->sql_fetchfield('result_count');
622 $db->sql_freeresult($result);
623
624 if (!$result_count)
625 {
626 return false;
627 }
628 }
629
630 if (sizeof($id_ary))
631 {
632 $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir);
633 $id_ary = array_slice($id_ary, 0, $per_page);
634
635 return $result_count;
636 }
637 return false;
638 }
639
640 /**
641 * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated.
642 *
643 * @param string $mode contains the post mode: edit, post, reply, quote ...
644 */
645 function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
646 {
647 global $db;
648
649 // Split old and new post/subject to obtain array of words
650 $split_text = $this->split_message($message);
651 $split_title = ($subject) ? $this->split_message($subject) : array();
652
653 $words = array_unique(array_merge($split_text, $split_title));
654
655 unset($split_text);
656 unset($split_title);
657
658 // destroy cached search results containing any of the words removed or added
659 $this->destroy_cache($words, array($poster_id));
660
661 unset($words);
662 }
663
664 /**
665 * Destroy cached results, that might be outdated after deleting a post
666 */
667 function index_remove($post_ids, $author_ids, $forum_ids)
668 {
669 $this->destroy_cache(array(), $author_ids);
670 }
671
672 /**
673 * Destroy old cache entries
674 */
675 function tidy()
676 {
677 global $db, $config;
678
679 // destroy too old cached search results
680 $this->destroy_cache(array());
681
682 set_config('search_last_gc', time(), true);
683 }
684
685 /**
686 * Create fulltext index
687 */
688 function create_index($acp_module, $u_action)
689 {
690 global $db;
691
692 // Make sure we can actually use MySQL with fulltext indexes
693 if ($error = $this->init())
694 {
695 return $error;
696 }
697
698 if (empty($this->stats))
699 {
700 $this->get_stats();
701 }
702
703 $alter = array();
704
705 if (!isset($this->stats['post_subject']))
706 {
707 if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>='))
708 {
709 //$alter[] = 'MODIFY post_subject varchar(100) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL';
710 }
711 else
712 {
713 $alter[] = 'MODIFY post_subject text NOT NULL';
714 }
715 $alter[] = 'ADD FULLTEXT (post_subject)';
716 }
717
718 if (!isset($this->stats['post_text']))
719 {
720 if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>='))
721 {
722 $alter[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL';
723 }
724 else
725 {
726 $alter[] = 'MODIFY post_text mediumtext NOT NULL';
727 }
728 $alter[] = 'ADD FULLTEXT (post_text)';
729 }
730
731 if (!isset($this->stats['post_content']))
732 {
733 $alter[] = 'ADD FULLTEXT post_content (post_subject, post_text)';
734 }
735
736 if (sizeof($alter))
737 {
738 $db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));
739 }
740
741 $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
742
743 return false;
744 }
745
746 /**
747 * Drop fulltext index
748 */
749 function delete_index($acp_module, $u_action)
750 {
751 global $db;
752
753 // Make sure we can actually use MySQL with fulltext indexes
754 if ($error = $this->init())
755 {
756 return $error;
757 }
758
759 if (empty($this->stats))
760 {
761 $this->get_stats();
762 }
763
764 $alter = array();
765
766 if (isset($this->stats['post_subject']))
767 {
768 $alter[] = 'DROP INDEX post_subject';
769 }
770
771 if (isset($this->stats['post_text']))
772 {
773 $alter[] = 'DROP INDEX post_text';
774 }
775
776 if (isset($this->stats['post_content']))
777 {
778 $alter[] = 'DROP INDEX post_content';
779 }
780
781 if (sizeof($alter))
782 {
783 $db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));
784 }
785
786 $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
787
788 return false;
789 }
790
791 /**
792 * Returns true if both FULLTEXT indexes exist
793 */
794 function index_created()
795 {
796 if (empty($this->stats))
797 {
798 $this->get_stats();
799 }
800
801 return (isset($this->stats['post_text']) && isset($this->stats['post_subject']) && isset($this->stats['post_content'])) ? true : false;
802 }
803
804 /**
805 * Returns an associative array containing information about the indexes
806 */
807 function index_stats()
808 {
809 global $user;
810
811 if (empty($this->stats))
812 {
813 $this->get_stats();
814 }
815
816 return array(
817 $user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0,
818 );
819 }
820
821 function get_stats()
822 {
823 global $db;
824
825 if (strpos($db->sql_layer, 'mysql') === false)
826 {
827 $this->stats = array();
828 return;
829 }
830
831 $sql = 'SHOW INDEX
832 FROM ' . POSTS_TABLE;
833 $result = $db->sql_query($sql);
834
835 while ($row = $db->sql_fetchrow($result))
836 {
837 // deal with older MySQL versions which didn't use Index_type
838 $index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment'];
839
840 if ($index_type == 'FULLTEXT')
841 {
842 if ($row['Key_name'] == 'post_text')
843 {
844 $this->stats['post_text'] = $row;
845 }
846 else if ($row['Key_name'] == 'post_subject')
847 {
848 $this->stats['post_subject'] = $row;
849 }
850 else if ($row['Key_name'] == 'post_content')
851 {
852 $this->stats['post_content'] = $row;
853 }
854 }
855 }
856 $db->sql_freeresult($result);
857
858 $sql = 'SELECT COUNT(post_id) as total_posts
859 FROM ' . POSTS_TABLE;
860 $result = $db->sql_query($sql);
861 $this->stats['total_posts'] = (int) $db->sql_fetchfield('total_posts');
862 $db->sql_freeresult($result);
863 }
864
865 /**
866 * Display a note, that UTF-8 support is not available with certain versions of PHP
867 */
868 function acp()
869 {
870 global $user, $config;
871
872 $tpl = '
873 <dl>
874 <dt><label>' . $user->lang['FULLTEXT_MYSQL_PCRE'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_PCRE_EXPLAIN'] . '</span></dt>
875 <dd>' . (($this->pcre_properties) ? $user->lang['YES'] : $user->lang['NO']) . ' (PHP ' . PHP_VERSION . ')</dd>
876 </dl>
877 <dl>
878 <dt><label>' . $user->lang['FULLTEXT_MYSQL_MBSTRING'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MBSTRING_EXPLAIN'] . '</span></dt>
879 <dd>' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '</dd>
880 </dl>
881 ';
882
883 // These are fields required in the config table
884 return array(
885 'tpl' => $tpl,
886 'config' => array()
887 );
888 }
889}
890
891?>
Note: See TracBrowser for help on using the repository browser.