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