[ Index ] |
PHP Cross Reference of phpBB-3.1.12-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * This file is part of the phpBB Forum Software package. 5 * 6 * @copyright (c) phpBB Limited <https://www.phpbb.com> 7 * @license GNU General Public License, version 2 (GPL-2.0) 8 * 9 * For full copyright and license information, please see 10 * the docs/CREDITS.txt file. 11 * 12 */ 13 14 namespace phpbb\search; 15 16 define('SPHINX_MAX_MATCHES', 20000); 17 define('SPHINX_CONNECT_RETRIES', 3); 18 define('SPHINX_CONNECT_WAIT_TIME', 300); 19 20 /** 21 * Fulltext search based on the sphinx search deamon 22 */ 23 class fulltext_sphinx 24 { 25 /** 26 * Associative array holding index stats 27 * @var array 28 */ 29 protected $stats = array(); 30 31 /** 32 * Holds the words entered by user, obtained by splitting the entered query on whitespace 33 * @var array 34 */ 35 protected $split_words = array(); 36 37 /** 38 * Holds unique sphinx id 39 * @var string 40 */ 41 protected $id; 42 43 /** 44 * Stores the names of both main and delta sphinx indexes 45 * separated by a semicolon 46 * @var string 47 */ 48 protected $indexes; 49 50 /** 51 * Sphinx searchd client object 52 * @var SphinxClient 53 */ 54 protected $sphinx; 55 56 /** 57 * Relative path to board root 58 * @var string 59 */ 60 protected $phpbb_root_path; 61 62 /** 63 * PHP Extension 64 * @var string 65 */ 66 protected $php_ext; 67 68 /** 69 * Auth object 70 * @var \phpbb\auth\auth 71 */ 72 protected $auth; 73 74 /** 75 * Config object 76 * @var \phpbb\config\config 77 */ 78 protected $config; 79 80 /** 81 * Database connection 82 * @var \phpbb\db\driver\driver_interface 83 */ 84 protected $db; 85 86 /** 87 * Database Tools object 88 * @var \phpbb\db\tools 89 */ 90 protected $db_tools; 91 92 /** 93 * Stores the database type if supported by sphinx 94 * @var string 95 */ 96 protected $dbtype; 97 98 /** 99 * phpBB event dispatcher object 100 * @var \phpbb\event\dispatcher_interface 101 */ 102 protected $phpbb_dispatcher; 103 104 /** 105 * User object 106 * @var \phpbb\user 107 */ 108 protected $user; 109 110 /** 111 * Stores the generated content of the sphinx config file 112 * @var string 113 */ 114 protected $config_file_data = ''; 115 116 /** 117 * Contains tidied search query. 118 * Operators are prefixed in search query and common words excluded 119 * @var string 120 */ 121 protected $search_query; 122 123 /** 124 * Constructor 125 * Creates a new \phpbb\search\fulltext_postgres, which is used as a search backend 126 * 127 * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false 128 * @param string $phpbb_root_path Relative path to phpBB root 129 * @param string $phpEx PHP file extension 130 * @param \phpbb\auth\auth $auth Auth object 131 * @param \phpbb\config\config $config Config object 132 * @param \phpbb\db\driver\driver_interface Database object 133 * @param \phpbb\user $user User object 134 * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object 135 */ 136 public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher) 137 { 138 $this->phpbb_root_path = $phpbb_root_path; 139 $this->php_ext = $phpEx; 140 $this->config = $config; 141 $this->phpbb_dispatcher = $phpbb_dispatcher; 142 $this->user = $user; 143 $this->db = $db; 144 $this->auth = $auth; 145 146 // Initialize \phpbb\db\tools object 147 $this->db_tools = new \phpbb\db\tools($this->db); 148 149 if (!$this->config['fulltext_sphinx_id']) 150 { 151 set_config('fulltext_sphinx_id', unique_id()); 152 } 153 $this->id = $this->config['fulltext_sphinx_id']; 154 $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; 155 156 if (!class_exists('SphinxClient')) 157 { 158 require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext); 159 } 160 161 // Initialize sphinx client 162 $this->sphinx = new \SphinxClient(); 163 164 $this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312)); 165 166 $error = false; 167 } 168 169 /** 170 * Returns the name of this search backend to be displayed to administrators 171 * 172 * @return string Name 173 */ 174 public function get_name() 175 { 176 return 'Sphinx Fulltext'; 177 } 178 179 /** 180 * Returns the search_query 181 * 182 * @return string search query 183 */ 184 public function get_search_query() 185 { 186 return $this->search_query; 187 } 188 189 /** 190 * Returns false as there is no word_len array 191 * 192 * @return false 193 */ 194 public function get_word_length() 195 { 196 return false; 197 } 198 199 /** 200 * Returns an empty array as there are no common_words 201 * 202 * @return array common words that are ignored by search backend 203 */ 204 public function get_common_words() 205 { 206 return array(); 207 } 208 209 /** 210 * Checks permissions and paths, if everything is correct it generates the config file 211 * 212 * @return string|bool Language key of the error/incompatiblity encountered, or false if successful 213 */ 214 public function init() 215 { 216 if ($this->db->get_sql_layer() != 'mysql' && $this->db->get_sql_layer() != 'mysql4' && $this->db->get_sql_layer() != 'mysqli' && $this->db->get_sql_layer() != 'postgres') 217 { 218 return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; 219 } 220 221 // Move delta to main index each hour 222 set_config('search_gc', 3600); 223 224 return false; 225 } 226 227 /** 228 * Generates content of sphinx.conf 229 * 230 * @return bool True if sphinx.conf content is correctly generated, false otherwise 231 */ 232 protected function config_generate() 233 { 234 // Check if Database is supported by Sphinx 235 if ($this->db->get_sql_layer() =='mysql' || $this->db->get_sql_layer() == 'mysql4' || $this->db->get_sql_layer() == 'mysqli') 236 { 237 $this->dbtype = 'mysql'; 238 } 239 else if ($this->db->get_sql_layer() == 'postgres') 240 { 241 $this->dbtype = 'pgsql'; 242 } 243 else 244 { 245 $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE'); 246 return false; 247 } 248 249 // Check if directory paths have been filled 250 if (!$this->config['fulltext_sphinx_data_path']) 251 { 252 $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA'); 253 return false; 254 } 255 256 include($this->phpbb_root_path . 'config.' . $this->php_ext); 257 258 /* Now that we're sure everything was entered correctly, 259 generate a config for the index. We use a config value 260 fulltext_sphinx_id for this, as it should be unique. */ 261 $config_object = new \phpbb\search\sphinx\config($this->config_file_data); 262 $config_data = array( 263 'source source_phpbb_' . $this->id . '_main' => array( 264 array('type', $this->dbtype . ' # mysql or pgsql'), 265 // This config value sql_host needs to be changed incase sphinx and sql are on different servers 266 array('sql_host', $dbhost . ' # SQL server host sphinx connects to'), 267 array('sql_user', '[dbuser]'), 268 array('sql_pass', '[dbpassword]'), 269 array('sql_db', $dbname), 270 array('sql_port', $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'), 271 array('sql_query_pre', 'SET NAMES \'utf8\''), 272 array('sql_query_pre', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'), 273 array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), 274 array('sql_range_step', '5000'), 275 array('sql_query', 'SELECT 276 p.post_id AS id, 277 p.forum_id, 278 p.topic_id, 279 p.poster_id, 280 p.post_visibility, 281 CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, 282 p.post_time, 283 p.post_subject, 284 p.post_subject as title, 285 p.post_text as data, 286 t.topic_last_post_time, 287 0 as deleted 288 FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t 289 WHERE 290 p.topic_id = t.topic_id 291 AND p.post_id >= $start AND p.post_id <= $end'), 292 array('sql_query_post', ''), 293 array('sql_query_post_index', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'), 294 array('sql_query_info', 'SELECT * FROM ' . POSTS_TABLE . ' WHERE post_id = $id'), 295 array('sql_attr_uint', 'forum_id'), 296 array('sql_attr_uint', 'topic_id'), 297 array('sql_attr_uint', 'poster_id'), 298 array('sql_attr_uint', 'post_visibility'), 299 array('sql_attr_bool', 'topic_first_post'), 300 array('sql_attr_bool', 'deleted'), 301 array('sql_attr_timestamp', 'post_time'), 302 array('sql_attr_timestamp', 'topic_last_post_time'), 303 array('sql_attr_string', 'post_subject'), 304 ), 305 'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( 306 array('sql_query_pre', ''), 307 array('sql_query_range', ''), 308 array('sql_range_step', ''), 309 array('sql_query', 'SELECT 310 p.post_id AS id, 311 p.forum_id, 312 p.topic_id, 313 p.poster_id, 314 p.post_visibility, 315 CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, 316 p.post_time, 317 p.post_subject, 318 p.post_subject as title, 319 p.post_text as data, 320 t.topic_last_post_time, 321 0 as deleted 322 FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t 323 WHERE 324 p.topic_id = t.topic_id 325 AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), 326 ), 327 'index index_phpbb_' . $this->id . '_main' => array( 328 array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), 329 array('source', 'source_phpbb_' . $this->id . '_main'), 330 array('docinfo', 'extern'), 331 array('morphology', 'none'), 332 array('stopwords', ''), 333 array('min_word_len', '2'), 334 array('charset_type', 'utf-8'), 335 array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), 336 array('min_prefix_len', '0'), 337 array('min_infix_len', '0'), 338 ), 339 'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( 340 array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), 341 array('source', 'source_phpbb_' . $this->id . '_delta'), 342 ), 343 'indexer' => array( 344 array('mem_limit', $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'), 345 ), 346 'searchd' => array( 347 array('compat_sphinxql_magics' , '0'), 348 array('listen' , ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')), 349 array('log', $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'), 350 array('query_log', $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'), 351 array('read_timeout', '5'), 352 array('max_children', '30'), 353 array('pid_file', $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'), 354 array('max_matches', (string) SPHINX_MAX_MATCHES), 355 array('binlog_path', $this->config['fulltext_sphinx_data_path']), 356 ), 357 ); 358 359 $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); 360 $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); 361 362 /** 363 * Allow adding/changing the Sphinx configuration data 364 * 365 * @event core.search_sphinx_modify_config_data 366 * @var array config_data Array with the Sphinx configuration data 367 * @var array non_unique Array with the Sphinx non-unique variables to delete 368 * @var array delete Array with the Sphinx variables to delete 369 * @since 3.1.7-RC1 370 */ 371 $vars = array( 372 'config_data', 373 'non_unique', 374 'delete', 375 ); 376 extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars))); 377 378 foreach ($config_data as $section_name => $section_data) 379 { 380 $section = $config_object->get_section_by_name($section_name); 381 if (!$section) 382 { 383 $section = $config_object->add_section($section_name); 384 } 385 386 foreach ($delete as $key => $void) 387 { 388 $section->delete_variables_by_name($key); 389 } 390 391 foreach ($non_unique as $key => $void) 392 { 393 $section->delete_variables_by_name($key); 394 } 395 396 foreach ($section_data as $entry) 397 { 398 $key = $entry[0]; 399 $value = $entry[1]; 400 401 if (!isset($non_unique[$key])) 402 { 403 $variable = $section->get_variable_by_name($key); 404 if (!$variable) 405 { 406 $variable = $section->create_variable($key, $value); 407 } 408 else 409 { 410 $variable->set_value($value); 411 } 412 } 413 else 414 { 415 $variable = $section->create_variable($key, $value); 416 } 417 } 418 } 419 $this->config_file_data = $config_object->get_data(); 420 421 return true; 422 } 423 424 /** 425 * Splits keywords entered by a user into an array of words stored in $this->split_words 426 * Stores the tidied search query in $this->search_query 427 * 428 * @param string $keywords Contains the keyword as entered by the user 429 * @param string $terms is either 'all' or 'any' 430 * @return false if no valid keywords were found and otherwise true 431 */ 432 public function split_keywords(&$keywords, $terms) 433 { 434 if ($terms == 'all') 435 { 436 $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); 437 $replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', ''); 438 439 $replacements = 0; 440 $keywords = preg_replace($match, $replace, $keywords); 441 $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); 442 } 443 else 444 { 445 $this->sphinx->SetMatchMode(SPH_MATCH_ANY); 446 } 447 448 // Keep quotes and new lines 449 $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); 450 451 if (strlen($keywords) > 0) 452 { 453 $this->search_query = str_replace('"', '"', $keywords); 454 return true; 455 } 456 457 return false; 458 } 459 460 /** 461 * Performs a search on keywords depending on display specific params. You have to run split_keywords() first 462 * 463 * @param string $type contains either posts or topics depending on what should be searched for 464 * @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) 465 * @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) 466 * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query 467 * @param string $sort_key is the key of $sort_by_sql for the selected sorting 468 * @param string $sort_dir is either a or d representing ASC and DESC 469 * @param string $sort_days specifies the maximum amount of days a post may be old 470 * @param array $ex_fid_ary specifies an array of forum ids which should not be searched 471 * @param string $post_visibility specifies which types of posts the user can view in which forums 472 * @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 473 * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty 474 * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match 475 * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered 476 * @param int $start indicates the first index of the page 477 * @param int $per_page number of ids each page is supposed to contain 478 * @return boolean|int total number of results 479 */ 480 public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) 481 { 482 // No keywords? No posts. 483 if (!strlen($this->search_query) && !sizeof($author_ary)) 484 { 485 return false; 486 } 487 488 $id_ary = array(); 489 490 $join_topic = ($type != 'posts'); 491 492 // Sorting 493 494 if ($type == 'topics') 495 { 496 switch ($sort_key) 497 { 498 case 'a': 499 $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); 500 break; 501 502 case 'f': 503 $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); 504 break; 505 506 case 'i': 507 508 case 's': 509 $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); 510 break; 511 512 case 't': 513 514 default: 515 $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); 516 break; 517 } 518 } 519 else 520 { 521 switch ($sort_key) 522 { 523 case 'a': 524 $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); 525 break; 526 527 case 'f': 528 $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); 529 break; 530 531 case 'i': 532 533 case 's': 534 $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); 535 break; 536 537 case 't': 538 539 default: 540 $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); 541 break; 542 } 543 } 544 545 // Most narrow filters first 546 if ($topic_id) 547 { 548 $this->sphinx->SetFilter('topic_id', array($topic_id)); 549 } 550 551 /** 552 * Allow modifying the Sphinx search options 553 * 554 * @event core.search_sphinx_keywords_modify_options 555 * @var string type Searching type ('posts', 'topics') 556 * @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all') 557 * @var string terms Searching terms ('all', 'any') 558 * @var int sort_days Time, in days, of the oldest possible post to list 559 * @var string sort_key The sort type used from the possible sort types 560 * @var int topic_id Limit the search to this topic_id only 561 * @var array ex_fid_ary Which forums not to search on 562 * @var string post_visibility Post visibility data 563 * @var array author_ary Array of user_id containing the users to filter the results to 564 * @var string author_name The username to search on 565 * @var object sphinx The Sphinx searchd client object 566 * @since 3.1.7-RC1 567 */ 568 $sphinx = $this->sphinx; 569 $vars = array( 570 'type', 571 'fields', 572 'terms', 573 'sort_days', 574 'sort_key', 575 'topic_id', 576 'ex_fid_ary', 577 'post_visibility', 578 'author_ary', 579 'author_name', 580 'sphinx', 581 ); 582 extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars))); 583 $this->sphinx = $sphinx; 584 unset($sphinx); 585 586 $search_query_prefix = ''; 587 588 switch ($fields) 589 { 590 case 'titleonly': 591 // Only search the title 592 if ($terms == 'all') 593 { 594 $search_query_prefix = '@title '; 595 } 596 // Weight for the title 597 $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); 598 // 1 is first_post, 0 is not first post 599 $this->sphinx->SetFilter('topic_first_post', array(1)); 600 break; 601 602 case 'msgonly': 603 // Only search the body 604 if ($terms == 'all') 605 { 606 $search_query_prefix = '@data '; 607 } 608 // Weight for the body 609 $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); 610 break; 611 612 case 'firstpost': 613 // More relative weight for the title, also search the body 614 $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); 615 // 1 is first_post, 0 is not first post 616 $this->sphinx->SetFilter('topic_first_post', array(1)); 617 break; 618 619 default: 620 // More relative weight for the title, also search the body 621 $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); 622 break; 623 } 624 625 if (sizeof($author_ary)) 626 { 627 $this->sphinx->SetFilter('poster_id', $author_ary); 628 } 629 630 // As this is not simply possible at the moment, we limit the result to approved posts. 631 // This will make it impossible for moderators to search unapproved and softdeleted posts, 632 // but at least it will also cause the same for normal users. 633 $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED)); 634 635 if (sizeof($ex_fid_ary)) 636 { 637 // All forums that a user is allowed to access 638 $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true)))); 639 // All forums that the user wants to and can search in 640 $search_forums = array_diff($fid_ary, $ex_fid_ary); 641 642 if (sizeof($search_forums)) 643 { 644 $this->sphinx->SetFilter('forum_id', $search_forums); 645 } 646 } 647 648 $this->sphinx->SetFilter('deleted', array(0)); 649 650 $this->sphinx->SetLimits($start, (int) $per_page, SPHINX_MAX_MATCHES); 651 $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); 652 653 // Could be connection to localhost:9312 failed (errno=111, 654 // msg=Connection refused) during rotate, retry if so 655 $retries = SPHINX_CONNECT_RETRIES; 656 while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) 657 { 658 usleep(SPHINX_CONNECT_WAIT_TIME); 659 $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); 660 } 661 662 if ($this->sphinx->GetLastError()) 663 { 664 add_log('critical', 'LOG_SPHINX_ERROR', $this->sphinx->GetLastError()); 665 if ($this->auth->acl_get('a_')) 666 { 667 trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); 668 } 669 else 670 { 671 trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG')); 672 } 673 } 674 675 $result_count = $result['total_found']; 676 677 if ($result_count && $start >= $result_count) 678 { 679 $start = floor(($result_count - 1) / $per_page) * $per_page; 680 681 $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); 682 $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); 683 684 // Could be connection to localhost:9312 failed (errno=111, 685 // msg=Connection refused) during rotate, retry if so 686 $retries = SPHINX_CONNECT_RETRIES; 687 while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) 688 { 689 usleep(SPHINX_CONNECT_WAIT_TIME); 690 $result = $this->sphinx->Query($search_query_prefix . str_replace('"', '"', $this->search_query), $this->indexes); 691 } 692 } 693 694 $id_ary = array(); 695 if (isset($result['matches'])) 696 { 697 if ($type == 'posts') 698 { 699 $id_ary = array_keys($result['matches']); 700 } 701 else 702 { 703 foreach ($result['matches'] as $key => $value) 704 { 705 $id_ary[] = $value['attrs']['topic_id']; 706 } 707 } 708 } 709 else 710 { 711 return false; 712 } 713 714 $id_ary = array_slice($id_ary, 0, (int) $per_page); 715 716 return $result_count; 717 } 718 719 /** 720 * Performs a search on an author's posts without caring about message contents. Depends on display specific params 721 * 722 * @param string $type contains either posts or topics depending on what should be searched for 723 * @param boolean $firstpost_only if true, only topic starting posts will be considered 724 * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query 725 * @param string $sort_key is the key of $sort_by_sql for the selected sorting 726 * @param string $sort_dir is either a or d representing ASC and DESC 727 * @param string $sort_days specifies the maximum amount of days a post may be old 728 * @param array $ex_fid_ary specifies an array of forum ids which should not be searched 729 * @param string $post_visibility specifies which types of posts the user can view in which forums 730 * @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 731 * @param array $author_ary an array of author ids 732 * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match 733 * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered 734 * @param int $start indicates the first index of the page 735 * @param int $per_page number of ids each page is supposed to contain 736 * @return boolean|int total number of results 737 */ 738 public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) 739 { 740 $this->search_query = ''; 741 742 $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); 743 $fields = ($firstpost_only) ? 'firstpost' : 'all'; 744 $terms = 'all'; 745 return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page); 746 } 747 748 /** 749 * Updates wordlist and wordmatch tables when a message is posted or changed 750 * 751 * @param string $mode Contains the post mode: edit, post, reply, quote 752 * @param int $post_id The id of the post which is modified/created 753 * @param string &$message New or updated post content 754 * @param string &$subject New or updated post subject 755 * @param int $poster_id Post author's user id 756 * @param int $forum_id The id of the forum in which the post is located 757 */ 758 public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) 759 { 760 if ($mode == 'edit') 761 { 762 $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id))); 763 } 764 else if ($mode != 'post' && $post_id) 765 { 766 // Update topic_last_post_time for full topic 767 $sql_array = array( 768 'SELECT' => 'p1.post_id', 769 'FROM' => array( 770 POSTS_TABLE => 'p1', 771 ), 772 'LEFT_JOIN' => array(array( 773 'FROM' => array( 774 POSTS_TABLE => 'p2' 775 ), 776 'ON' => 'p1.topic_id = p2.topic_id', 777 )), 778 'WHERE' => 'p2.post_id = ' . ((int) $post_id), 779 ); 780 781 $sql = $this->db->sql_build_query('SELECT', $sql_array); 782 $result = $this->db->sql_query($sql); 783 784 $post_updates = array(); 785 $post_time = time(); 786 while ($row = $this->db->sql_fetchrow($result)) 787 { 788 $post_updates[(int) $row['post_id']] = array($post_time); 789 } 790 $this->db->sql_freeresult($result); 791 792 if (sizeof($post_updates)) 793 { 794 $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); 795 } 796 } 797 } 798 799 /** 800 * Delete a post from the index after it was deleted 801 */ 802 public function index_remove($post_ids, $author_ids, $forum_ids) 803 { 804 $values = array(); 805 foreach ($post_ids as $post_id) 806 { 807 $values[$post_id] = array(1); 808 } 809 810 $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); 811 } 812 813 /** 814 * Nothing needs to be destroyed 815 */ 816 public function tidy($create = false) 817 { 818 set_config('search_last_gc', time(), true); 819 } 820 821 /** 822 * Create sphinx table 823 * 824 * @return string|bool error string is returned incase of errors otherwise false 825 */ 826 public function create_index($acp_module, $u_action) 827 { 828 if (!$this->index_created()) 829 { 830 $table_data = array( 831 'COLUMNS' => array( 832 'counter_id' => array('UINT', 0), 833 'max_doc_id' => array('UINT', 0), 834 ), 835 'PRIMARY_KEY' => 'counter_id', 836 ); 837 $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data); 838 839 $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; 840 $this->db->sql_query($sql); 841 842 $data = array( 843 'counter_id' => '1', 844 'max_doc_id' => '0', 845 ); 846 $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data); 847 $this->db->sql_query($sql); 848 } 849 850 return false; 851 } 852 853 /** 854 * Drop sphinx table 855 * 856 * @return string|bool error string is returned incase of errors otherwise false 857 */ 858 public function delete_index($acp_module, $u_action) 859 { 860 if (!$this->index_created()) 861 { 862 return false; 863 } 864 865 $this->db_tools->sql_table_drop(SPHINX_TABLE); 866 867 return false; 868 } 869 870 /** 871 * Returns true if the sphinx table was created 872 * 873 * @return bool true if sphinx table was created 874 */ 875 public function index_created($allow_new_files = true) 876 { 877 $created = false; 878 879 if ($this->db_tools->sql_table_exists(SPHINX_TABLE)) 880 { 881 $created = true; 882 } 883 884 return $created; 885 } 886 887 /** 888 * Returns an associative array containing information about the indexes 889 * 890 * @return string|bool Language string of error false otherwise 891 */ 892 public function index_stats() 893 { 894 if (empty($this->stats)) 895 { 896 $this->get_stats(); 897 } 898 899 return array( 900 $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, 901 $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, 902 $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, 903 ); 904 } 905 906 /** 907 * Collects stats that can be displayed on the index maintenance page 908 */ 909 protected function get_stats() 910 { 911 if ($this->index_created()) 912 { 913 $sql = 'SELECT COUNT(post_id) as total_posts 914 FROM ' . POSTS_TABLE; 915 $result = $this->db->sql_query($sql); 916 $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts'); 917 $this->db->sql_freeresult($result); 918 919 $sql = 'SELECT COUNT(p.post_id) as main_posts 920 FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m 921 WHERE p.post_id <= m.max_doc_id 922 AND m.counter_id = 1'; 923 $result = $this->db->sql_query($sql); 924 $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts'); 925 $this->db->sql_freeresult($result); 926 } 927 } 928 929 /** 930 * Returns a list of options for the ACP to display 931 * 932 * @return associative array containing template and config variables 933 */ 934 public function acp() 935 { 936 $config_vars = array( 937 'fulltext_sphinx_data_path' => 'string', 938 'fulltext_sphinx_host' => 'string', 939 'fulltext_sphinx_port' => 'string', 940 'fulltext_sphinx_indexer_mem_limit' => 'int', 941 ); 942 943 $tpl = ' 944 <span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span> 945 <dl> 946 <dt><label for="fulltext_sphinx_data_path">' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '</span></dt> 947 <dd><input id="fulltext_sphinx_data_path" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_data_path]" value="' . $this->config['fulltext_sphinx_data_path'] . '" /></dd> 948 </dl> 949 <dl> 950 <dt><label for="fulltext_sphinx_host">' . $this->user->lang['FULLTEXT_SPHINX_HOST'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '</span></dt> 951 <dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd> 952 </dl> 953 <dl> 954 <dt><label for="fulltext_sphinx_port">' . $this->user->lang['FULLTEXT_SPHINX_PORT'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '</span></dt> 955 <dd><input id="fulltext_sphinx_port" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd> 956 </dl> 957 <dl> 958 <dt><label for="fulltext_sphinx_indexer_mem_limit">' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '</span></dt> 959 <dd><input id="fulltext_sphinx_indexer_mem_limit" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" /> ' . $this->user->lang['MIB'] . '</dd> 960 </dl> 961 <dl> 962 <dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</span></dt> 963 <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd> 964 <dl> 965 '; 966 967 // These are fields required in the config table 968 return array( 969 'tpl' => $tpl, 970 'config' => $config_vars 971 ); 972 } 973 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Jan 11 00:25:41 2018 | Cross-referenced by PHPXref 0.7.1 |