[ Index ]

PHP Cross Reference of phpBB-3.3.0-deutsch

title

Body

[close]

/phpbb/search/ -> fulltext_sphinx.php (source)

   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 daemon
  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\tools_interface
  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\tools object
 147          global $phpbb_container; // TODO inject into object
 148          $this->db_tools = $phpbb_container->get('dbal.tools');
 149  
 150          if (!$this->config['fulltext_sphinx_id'])
 151          {
 152              $this->config->set('fulltext_sphinx_id', unique_id());
 153          }
 154          $this->id = $this->config['fulltext_sphinx_id'];
 155          $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main';
 156  
 157          if (!class_exists('SphinxClient'))
 158          {
 159              require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext);
 160          }
 161  
 162          // Initialize sphinx client
 163          $this->sphinx = new \SphinxClient();
 164  
 165          $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));
 166  
 167          $error = false;
 168      }
 169  
 170      /**
 171      * Returns the name of this search backend to be displayed to administrators
 172      *
 173      * @return string Name
 174      */
 175  	public function get_name()
 176      {
 177          return 'Sphinx Fulltext';
 178      }
 179  
 180      /**
 181       * Returns the search_query
 182       *
 183       * @return string search query
 184       */
 185  	public function get_search_query()
 186      {
 187          return $this->search_query;
 188      }
 189  
 190      /**
 191       * Returns false as there is no word_len array
 192       *
 193       * @return false
 194       */
 195  	public function get_word_length()
 196      {
 197          return false;
 198      }
 199  
 200      /**
 201       * Returns an empty array as there are no common_words
 202       *
 203       * @return array common words that are ignored by search backend
 204       */
 205  	public function get_common_words()
 206      {
 207          return array();
 208      }
 209  
 210      /**
 211      * Checks permissions and paths, if everything is correct it generates the config file
 212      *
 213      * @return string|bool Language key of the error/incompatibility encountered, or false if successful
 214      */
 215  	public function init()
 216      {
 217          if ($this->db->get_sql_layer() != 'mysqli' && $this->db->get_sql_layer() != 'postgres')
 218          {
 219              return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE'];
 220          }
 221  
 222          // Move delta to main index each hour
 223          $this->config->set('search_gc', 3600);
 224  
 225          return false;
 226      }
 227  
 228      /**
 229       * Generates content of sphinx.conf
 230       *
 231       * @return bool True if sphinx.conf content is correctly generated, false otherwise
 232       */
 233  	protected function config_generate()
 234      {
 235          // Check if Database is supported by Sphinx
 236          if ($this->db->get_sql_layer() == 'mysqli')
 237          {
 238              $this->dbtype = 'mysql';
 239          }
 240          else if ($this->db->get_sql_layer() == 'postgres')
 241          {
 242              $this->dbtype = 'pgsql';
 243          }
 244          else
 245          {
 246              $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE');
 247              return false;
 248          }
 249  
 250          // Check if directory paths have been filled
 251          if (!$this->config['fulltext_sphinx_data_path'])
 252          {
 253              $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA');
 254              return false;
 255          }
 256  
 257          include($this->phpbb_root_path . 'config.' . $this->php_ext);
 258  
 259          /* Now that we're sure everything was entered correctly,
 260          generate a config for the index. We use a config value
 261          fulltext_sphinx_id for this, as it should be unique. */
 262          $config_object = new \phpbb\search\sphinx\config($this->config_file_data);
 263          $config_data = array(
 264              'source source_phpbb_' . $this->id . '_main' => array(
 265                  array('type',                        $this->dbtype . ' # mysql or pgsql'),
 266                  // This config value sql_host needs to be changed incase sphinx and sql are on different servers
 267                  array('sql_host',                    $dbhost . ' # SQL server host sphinx connects to'),
 268                  array('sql_user',                    '[dbuser]'),
 269                  array('sql_pass',                    '[dbpassword]'),
 270                  array('sql_db',                        $dbname),
 271                  array('sql_port',                    $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'),
 272                  array('sql_query_pre',                'SET NAMES \'utf8\''),
 273                  array('sql_query_pre',                'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'),
 274                  array('sql_query_range',            'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''),
 275                  array('sql_range_step',                '5000'),
 276                  array('sql_query',                    'SELECT
 277                          p.post_id AS id,
 278                          p.forum_id,
 279                          p.topic_id,
 280                          p.poster_id,
 281                          p.post_visibility,
 282                          CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
 283                          p.post_time,
 284                          p.post_subject,
 285                          p.post_subject as title,
 286                          p.post_text as data,
 287                          t.topic_last_post_time,
 288                          0 as deleted
 289                      FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t
 290                      WHERE
 291                          p.topic_id = t.topic_id
 292                          AND p.post_id >= $start AND p.post_id <= $end'),
 293                  array('sql_query_post',                ''),
 294                  array('sql_query_post_index',        'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'),
 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',                'SET NAMES \'utf8\''),
 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                  array('sql_query_post_index',        ''),
 327              ),
 328              'index index_phpbb_' . $this->id . '_main' => array(
 329                  array('path',                        $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'),
 330                  array('source',                        'source_phpbb_' . $this->id . '_main'),
 331                  array('docinfo',                    'extern'),
 332                  array('morphology',                    'none'),
 333                  array('stopwords',                    ''),
 334                  array('min_word_len',                '2'),
 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                  array('html_strip',                    '1'),
 339              ),
 340              'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array(
 341                  array('path',                        $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'),
 342                  array('source',                        'source_phpbb_' . $this->id . '_delta'),
 343              ),
 344              'indexer' => array(
 345                  array('mem_limit',                    $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'),
 346              ),
 347              'searchd' => array(
 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('binlog_path',                $this->config['fulltext_sphinx_data_path']),
 355              ),
 356          );
 357  
 358          $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true);
 359          $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true);
 360  
 361          /**
 362          * Allow adding/changing the Sphinx configuration data
 363          *
 364          * @event core.search_sphinx_modify_config_data
 365          * @var    array    config_data    Array with the Sphinx configuration data
 366          * @var    array    non_unique    Array with the Sphinx non-unique variables to delete
 367          * @var    array    delete        Array with the Sphinx variables to delete
 368          * @since 3.1.7-RC1
 369          */
 370          $vars = array(
 371              'config_data',
 372              'non_unique',
 373              'delete',
 374          );
 375          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars)));
 376  
 377          foreach ($config_data as $section_name => $section_data)
 378          {
 379              $section = $config_object->get_section_by_name($section_name);
 380              if (!$section)
 381              {
 382                  $section = $config_object->add_section($section_name);
 383              }
 384  
 385              foreach ($delete as $key => $void)
 386              {
 387                  $section->delete_variables_by_name($key);
 388              }
 389  
 390              foreach ($non_unique as $key => $void)
 391              {
 392                  $section->delete_variables_by_name($key);
 393              }
 394  
 395              foreach ($section_data as $entry)
 396              {
 397                  $key = $entry[0];
 398                  $value = $entry[1];
 399  
 400                  if (!isset($non_unique[$key]))
 401                  {
 402                      $variable = $section->get_variable_by_name($key);
 403                      if (!$variable)
 404                      {
 405                          $section->create_variable($key, $value);
 406                      }
 407                      else
 408                      {
 409                          $variable->set_value($value);
 410                      }
 411                  }
 412                  else
 413                  {
 414                      $section->create_variable($key, $value);
 415                  }
 416              }
 417          }
 418          $this->config_file_data = $config_object->get_data();
 419  
 420          return true;
 421      }
 422  
 423      /**
 424      * Splits keywords entered by a user into an array of words stored in $this->split_words
 425      * Stores the tidied search query in $this->search_query
 426      *
 427      * @param string $keywords Contains the keyword as entered by the user
 428      * @param string $terms is either 'all' or 'any'
 429      * @return false if no valid keywords were found and otherwise true
 430      */
 431  	public function split_keywords(&$keywords, $terms)
 432      {
 433          if ($terms == 'all')
 434          {
 435              $match        = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#');
 436              $replace    = array(' & ', ' | ', '  - ', ' +', ' -', ' |', '');
 437  
 438              $keywords = preg_replace($match, $replace, $keywords);
 439              $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED);
 440          }
 441          else
 442          {
 443              $this->sphinx->SetMatchMode(SPH_MATCH_ANY);
 444          }
 445  
 446          // Keep quotes and new lines
 447          $keywords = str_replace(array('&quot;', "\n"), array('"', ' '), trim($keywords));
 448  
 449          if (strlen($keywords) > 0)
 450          {
 451              $this->search_query = str_replace('"', '&quot;', $keywords);
 452              return true;
 453          }
 454  
 455          return false;
 456      }
 457  
 458      /**
 459      * Performs a search on keywords depending on display specific params. You have to run split_keywords() first
 460      *
 461      * @param    string        $type                contains either posts or topics depending on what should be searched for
 462      * @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)
 463      * @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)
 464      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 465      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 466      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 467      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 468      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 469      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
 470      * @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
 471      * @param    array        $author_ary            an array of author ids if the author should be ignored during the search the array is empty
 472      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 473      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 474      * @param    int            $start                indicates the first index of the page
 475      * @param    int            $per_page            number of ids each page is supposed to contain
 476      * @return    boolean|int                        total number of results
 477      */
 478  	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)
 479      {
 480          global $user, $phpbb_log;
 481  
 482          // No keywords? No posts.
 483          if (!strlen($this->search_query) && !count($author_ary))
 484          {
 485              return false;
 486          }
 487  
 488          $id_ary = array();
 489  
 490          // Sorting
 491  
 492          if ($type == 'topics')
 493          {
 494              switch ($sort_key)
 495              {
 496                  case 'a':
 497                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 498                  break;
 499  
 500                  case 'f':
 501                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 502                  break;
 503  
 504                  case 'i':
 505  
 506                  case 's':
 507                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 508                  break;
 509  
 510                  case 't':
 511  
 512                  default:
 513                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 514                  break;
 515              }
 516          }
 517          else
 518          {
 519              switch ($sort_key)
 520              {
 521                  case 'a':
 522                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id');
 523                  break;
 524  
 525                  case 'f':
 526                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id');
 527                  break;
 528  
 529                  case 'i':
 530  
 531                  case 's':
 532                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject');
 533                  break;
 534  
 535                  case 't':
 536  
 537                  default:
 538                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time');
 539                  break;
 540              }
 541          }
 542  
 543          // Most narrow filters first
 544          if ($topic_id)
 545          {
 546              $this->sphinx->SetFilter('topic_id', array($topic_id));
 547          }
 548  
 549          /**
 550          * Allow modifying the Sphinx search options
 551          *
 552          * @event core.search_sphinx_keywords_modify_options
 553          * @var    string    type                Searching type ('posts', 'topics')
 554          * @var    string    fields                Searching fields ('titleonly', 'msgonly', 'firstpost', 'all')
 555          * @var    string    terms                Searching terms ('all', 'any')
 556          * @var    int        sort_days            Time, in days, of the oldest possible post to list
 557          * @var    string    sort_key            The sort type used from the possible sort types
 558          * @var    int        topic_id            Limit the search to this topic_id only
 559          * @var    array    ex_fid_ary            Which forums not to search on
 560          * @var    string    post_visibility        Post visibility data
 561          * @var    array    author_ary            Array of user_id containing the users to filter the results to
 562          * @var    string    author_name            The username to search on
 563          * @var    object    sphinx                The Sphinx searchd client object
 564          * @since 3.1.7-RC1
 565          */
 566          $sphinx = $this->sphinx;
 567          $vars = array(
 568              'type',
 569              'fields',
 570              'terms',
 571              'sort_days',
 572              'sort_key',
 573              'topic_id',
 574              'ex_fid_ary',
 575              'post_visibility',
 576              'author_ary',
 577              'author_name',
 578              'sphinx',
 579          );
 580          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars)));
 581          $this->sphinx = $sphinx;
 582          unset($sphinx);
 583  
 584          $search_query_prefix = '';
 585  
 586          switch ($fields)
 587          {
 588              case 'titleonly':
 589                  // Only search the title
 590                  if ($terms == 'all')
 591                  {
 592                      $search_query_prefix = '@title ';
 593                  }
 594                  // Weight for the title
 595                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 596                  // 1 is first_post, 0 is not first post
 597                  $this->sphinx->SetFilter('topic_first_post', array(1));
 598              break;
 599  
 600              case 'msgonly':
 601                  // Only search the body
 602                  if ($terms == 'all')
 603                  {
 604                      $search_query_prefix = '@data ';
 605                  }
 606                  // Weight for the body
 607                  $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5));
 608              break;
 609  
 610              case 'firstpost':
 611                  // More relative weight for the title, also search the body
 612                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 613                  // 1 is first_post, 0 is not first post
 614                  $this->sphinx->SetFilter('topic_first_post', array(1));
 615              break;
 616  
 617              default:
 618                  // More relative weight for the title, also search the body
 619                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 620              break;
 621          }
 622  
 623          if (count($author_ary))
 624          {
 625              $this->sphinx->SetFilter('poster_id', $author_ary);
 626          }
 627  
 628          // As this is not simply possible at the moment, we limit the result to approved posts.
 629          // This will make it impossible for moderators to search unapproved and softdeleted posts,
 630          // but at least it will also cause the same for normal users.
 631          $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED));
 632  
 633          if (count($ex_fid_ary))
 634          {
 635              // All forums that a user is allowed to access
 636              $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true))));
 637              // All forums that the user wants to and can search in
 638              $search_forums = array_diff($fid_ary, $ex_fid_ary);
 639  
 640              if (count($search_forums))
 641              {
 642                  $this->sphinx->SetFilter('forum_id', $search_forums);
 643              }
 644          }
 645  
 646          $this->sphinx->SetFilter('deleted', array(0));
 647  
 648          $this->sphinx->SetLimits((int) $start, (int) $per_page, max(SPHINX_MAX_MATCHES, (int) $start + $per_page));
 649          $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 650  
 651          // Could be connection to localhost:9312 failed (errno=111,
 652          // msg=Connection refused) during rotate, retry if so
 653          $retries = SPHINX_CONNECT_RETRIES;
 654          while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
 655          {
 656              usleep(SPHINX_CONNECT_WAIT_TIME);
 657              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 658          }
 659  
 660          if ($this->sphinx->GetLastError())
 661          {
 662              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError()));
 663              if ($this->auth->acl_get('a_'))
 664              {
 665                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError()));
 666              }
 667              else
 668              {
 669                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG'));
 670              }
 671          }
 672  
 673          $result_count = $result['total_found'];
 674  
 675          if ($result_count && $start >= $result_count)
 676          {
 677              $start = floor(($result_count - 1) / $per_page) * $per_page;
 678  
 679              $this->sphinx->SetLimits((int) $start, (int) $per_page, max(SPHINX_MAX_MATCHES, (int) $start + $per_page));
 680              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 681  
 682              // Could be connection to localhost:9312 failed (errno=111,
 683              // msg=Connection refused) during rotate, retry if so
 684              $retries = SPHINX_CONNECT_RETRIES;
 685              while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
 686              {
 687                  usleep(SPHINX_CONNECT_WAIT_TIME);
 688                  $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 689              }
 690          }
 691  
 692          $id_ary = array();
 693          if (isset($result['matches']))
 694          {
 695              if ($type == 'posts')
 696              {
 697                  $id_ary = array_keys($result['matches']);
 698              }
 699              else
 700              {
 701                  foreach ($result['matches'] as $key => $value)
 702                  {
 703                      $id_ary[] = $value['attrs']['topic_id'];
 704                  }
 705              }
 706          }
 707          else
 708          {
 709              return false;
 710          }
 711  
 712          $id_ary = array_slice($id_ary, 0, (int) $per_page);
 713  
 714          return $result_count;
 715      }
 716  
 717      /**
 718      * Performs a search on an author's posts without caring about message contents. Depends on display specific params
 719      *
 720      * @param    string        $type                contains either posts or topics depending on what should be searched for
 721      * @param    boolean        $firstpost_only        if true, only topic starting posts will be considered
 722      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 723      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 724      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 725      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 726      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 727      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
 728      * @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
 729      * @param    array        $author_ary            an array of author ids
 730      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 731      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 732      * @param    int            $start                indicates the first index of the page
 733      * @param    int            $per_page            number of ids each page is supposed to contain
 734      * @return    boolean|int                        total number of results
 735      */
 736  	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)
 737      {
 738          $this->search_query = '';
 739  
 740          $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN);
 741          $fields = ($firstpost_only) ? 'firstpost' : 'all';
 742          $terms = 'all';
 743          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);
 744      }
 745  
 746      /**
 747       * Updates wordlist and wordmatch tables when a message is posted or changed
 748       *
 749       * @param    string    $mode    Contains the post mode: edit, post, reply, quote
 750       * @param    int    $post_id    The id of the post which is modified/created
 751       * @param    string    &$message    New or updated post content
 752       * @param    string    &$subject    New or updated post subject
 753       * @param    int    $poster_id    Post author's user id
 754       * @param    int    $forum_id    The id of the forum in which the post is located
 755       */
 756  	public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
 757      {
 758          /**
 759          * Event to modify method arguments before the Sphinx search index is updated
 760          *
 761          * @event core.search_sphinx_index_before
 762          * @var string    mode                Contains the post mode: edit, post, reply, quote
 763          * @var int        post_id                The id of the post which is modified/created
 764          * @var string    message                New or updated post content
 765          * @var string    subject                New or updated post subject
 766          * @var int        poster_id            Post author's user id
 767          * @var int        forum_id            The id of the forum in which the post is located
 768          * @since 3.2.3-RC1
 769          */
 770          $vars = array(
 771              'mode',
 772              'post_id',
 773              'message',
 774              'subject',
 775              'poster_id',
 776              'forum_id',
 777          );
 778          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_index_before', compact($vars)));
 779  
 780          if ($mode == 'edit')
 781          {
 782              $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id)));
 783          }
 784          else if ($mode != 'post' && $post_id)
 785          {
 786              // Update topic_last_post_time for full topic
 787              $sql_array = array(
 788                  'SELECT'    => 'p1.post_id',
 789                  'FROM'        => array(
 790                      POSTS_TABLE    => 'p1',
 791                  ),
 792                  'LEFT_JOIN'    => array(array(
 793                      'FROM'    => array(
 794                          POSTS_TABLE    => 'p2'
 795                      ),
 796                      'ON'    => 'p1.topic_id = p2.topic_id',
 797                  )),
 798                  'WHERE' => 'p2.post_id = ' . ((int) $post_id),
 799              );
 800  
 801              $sql = $this->db->sql_build_query('SELECT', $sql_array);
 802              $result = $this->db->sql_query($sql);
 803  
 804              $post_updates = array();
 805              $post_time = time();
 806              while ($row = $this->db->sql_fetchrow($result))
 807              {
 808                  $post_updates[(int) $row['post_id']] = array($post_time);
 809              }
 810              $this->db->sql_freeresult($result);
 811  
 812              if (count($post_updates))
 813              {
 814                  $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates);
 815              }
 816          }
 817      }
 818  
 819      /**
 820      * Delete a post from the index after it was deleted
 821      */
 822  	public function index_remove($post_ids, $author_ids, $forum_ids)
 823      {
 824          $values = array();
 825          foreach ($post_ids as $post_id)
 826          {
 827              $values[$post_id] = array(1);
 828          }
 829  
 830          $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values);
 831      }
 832  
 833      /**
 834      * Nothing needs to be destroyed
 835      */
 836  	public function tidy($create = false)
 837      {
 838          $this->config->set('search_last_gc', time(), false);
 839      }
 840  
 841      /**
 842      * Create sphinx table
 843      *
 844      * @return string|bool error string is returned incase of errors otherwise false
 845      */
 846  	public function create_index($acp_module, $u_action)
 847      {
 848          if (!$this->index_created())
 849          {
 850              $table_data = array(
 851                  'COLUMNS'    => array(
 852                      'counter_id'    => array('UINT', 0),
 853                      'max_doc_id'    => array('UINT', 0),
 854                  ),
 855                  'PRIMARY_KEY'    => 'counter_id',
 856              );
 857              $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data);
 858  
 859              $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE;
 860              $this->db->sql_query($sql);
 861  
 862              $data = array(
 863                  'counter_id'    => '1',
 864                  'max_doc_id'    => '0',
 865              );
 866              $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data);
 867              $this->db->sql_query($sql);
 868          }
 869  
 870          return false;
 871      }
 872  
 873      /**
 874      * Drop sphinx table
 875      *
 876      * @return string|bool error string is returned incase of errors otherwise false
 877      */
 878  	public function delete_index($acp_module, $u_action)
 879      {
 880          if (!$this->index_created())
 881          {
 882              return false;
 883          }
 884  
 885          $this->db_tools->sql_table_drop(SPHINX_TABLE);
 886  
 887          return false;
 888      }
 889  
 890      /**
 891      * Returns true if the sphinx table was created
 892      *
 893      * @return bool true if sphinx table was created
 894      */
 895  	public function index_created($allow_new_files = true)
 896      {
 897          $created = false;
 898  
 899          if ($this->db_tools->sql_table_exists(SPHINX_TABLE))
 900          {
 901              $created = true;
 902          }
 903  
 904          return $created;
 905      }
 906  
 907      /**
 908      * Returns an associative array containing information about the indexes
 909      *
 910      * @return string|bool Language string of error false otherwise
 911      */
 912  	public function index_stats()
 913      {
 914          if (empty($this->stats))
 915          {
 916              $this->get_stats();
 917          }
 918  
 919          return array(
 920              $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS']            => ($this->index_created()) ? $this->stats['main_posts'] : 0,
 921              $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0,
 922              $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] : 0,
 923          );
 924      }
 925  
 926      /**
 927      * Collects stats that can be displayed on the index maintenance page
 928      */
 929  	protected function get_stats()
 930      {
 931          if ($this->index_created())
 932          {
 933              $sql = 'SELECT COUNT(post_id) as total_posts
 934                  FROM ' . POSTS_TABLE;
 935              $result = $this->db->sql_query($sql);
 936              $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts');
 937              $this->db->sql_freeresult($result);
 938  
 939              $sql = 'SELECT COUNT(p.post_id) as main_posts
 940                  FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m
 941                  WHERE p.post_id <= m.max_doc_id
 942                      AND m.counter_id = 1';
 943              $result = $this->db->sql_query($sql);
 944              $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts');
 945              $this->db->sql_freeresult($result);
 946          }
 947      }
 948  
 949      /**
 950      * Returns a list of options for the ACP to display
 951      *
 952      * @return associative array containing template and config variables
 953      */
 954  	public function acp()
 955      {
 956          $config_vars = array(
 957              'fulltext_sphinx_data_path' => 'string',
 958              'fulltext_sphinx_host' => 'string',
 959              'fulltext_sphinx_port' => 'string',
 960              'fulltext_sphinx_indexer_mem_limit' => 'int',
 961          );
 962  
 963          $tpl = '
 964          <span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span>
 965          <dl>
 966              <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>
 967              <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>
 968          </dl>
 969          <dl>
 970              <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>
 971              <dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd>
 972          </dl>
 973          <dl>
 974              <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>
 975              <dd><input id="fulltext_sphinx_port" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd>
 976          </dl>
 977          <dl>
 978              <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>
 979              <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>
 980          </dl>
 981          <dl>
 982              <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>
 983              <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd>
 984          <dl>
 985          ';
 986  
 987          // These are fields required in the config table
 988          return array(
 989              'tpl'        => $tpl,
 990              'config'    => $config_vars
 991          );
 992      }
 993  }


Generated: Tue Apr 7 19:44:41 2020 Cross-referenced by PHPXref 0.7.1