[ Index ]

PHP Cross Reference of phpBB-3.2.8-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 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\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/incompatiblity encountered, or false if successful
 214      */
 215  	public function init()
 216      {
 217          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')
 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() =='mysql' || $this->db->get_sql_layer() == 'mysql4' || $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              ),
 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('listen'    ,                    ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')),
 348                  array('log',                        $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'),
 349                  array('query_log',                    $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'),
 350                  array('read_timeout',                '5'),
 351                  array('max_children',                '30'),
 352                  array('pid_file',                    $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'),
 353                  array('binlog_path',                $this->config['fulltext_sphinx_data_path']),
 354              ),
 355          );
 356  
 357          $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true);
 358          $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true);
 359  
 360          /**
 361          * Allow adding/changing the Sphinx configuration data
 362          *
 363          * @event core.search_sphinx_modify_config_data
 364          * @var    array    config_data    Array with the Sphinx configuration data
 365          * @var    array    non_unique    Array with the Sphinx non-unique variables to delete
 366          * @var    array    delete        Array with the Sphinx variables to delete
 367          * @since 3.1.7-RC1
 368          */
 369          $vars = array(
 370              'config_data',
 371              'non_unique',
 372              'delete',
 373          );
 374          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars)));
 375  
 376          foreach ($config_data as $section_name => $section_data)
 377          {
 378              $section = $config_object->get_section_by_name($section_name);
 379              if (!$section)
 380              {
 381                  $section = $config_object->add_section($section_name);
 382              }
 383  
 384              foreach ($delete as $key => $void)
 385              {
 386                  $section->delete_variables_by_name($key);
 387              }
 388  
 389              foreach ($non_unique as $key => $void)
 390              {
 391                  $section->delete_variables_by_name($key);
 392              }
 393  
 394              foreach ($section_data as $entry)
 395              {
 396                  $key = $entry[0];
 397                  $value = $entry[1];
 398  
 399                  if (!isset($non_unique[$key]))
 400                  {
 401                      $variable = $section->get_variable_by_name($key);
 402                      if (!$variable)
 403                      {
 404                          $section->create_variable($key, $value);
 405                      }
 406                      else
 407                      {
 408                          $variable->set_value($value);
 409                      }
 410                  }
 411                  else
 412                  {
 413                      $section->create_variable($key, $value);
 414                  }
 415              }
 416          }
 417          $this->config_file_data = $config_object->get_data();
 418  
 419          return true;
 420      }
 421  
 422      /**
 423      * Splits keywords entered by a user into an array of words stored in $this->split_words
 424      * Stores the tidied search query in $this->search_query
 425      *
 426      * @param string $keywords Contains the keyword as entered by the user
 427      * @param string $terms is either 'all' or 'any'
 428      * @return false if no valid keywords were found and otherwise true
 429      */
 430  	public function split_keywords(&$keywords, $terms)
 431      {
 432          if ($terms == 'all')
 433          {
 434              $match        = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#');
 435              $replace    = array(' & ', ' | ', '  - ', ' +', ' -', ' |', '');
 436  
 437              $keywords = preg_replace($match, $replace, $keywords);
 438              $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED);
 439          }
 440          else
 441          {
 442              $this->sphinx->SetMatchMode(SPH_MATCH_ANY);
 443          }
 444  
 445          // Keep quotes and new lines
 446          $keywords = str_replace(array('&quot;', "\n"), array('"', ' '), trim($keywords));
 447  
 448          if (strlen($keywords) > 0)
 449          {
 450              $this->search_query = str_replace('"', '&quot;', $keywords);
 451              return true;
 452          }
 453  
 454          return false;
 455      }
 456  
 457      /**
 458      * Performs a search on keywords depending on display specific params. You have to run split_keywords() first
 459      *
 460      * @param    string        $type                contains either posts or topics depending on what should be searched for
 461      * @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)
 462      * @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)
 463      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 464      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 465      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 466      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 467      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 468      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
 469      * @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
 470      * @param    array        $author_ary            an array of author ids if the author should be ignored during the search the array is empty
 471      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 472      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 473      * @param    int            $start                indicates the first index of the page
 474      * @param    int            $per_page            number of ids each page is supposed to contain
 475      * @return    boolean|int                        total number of results
 476      */
 477  	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)
 478      {
 479          global $user, $phpbb_log;
 480  
 481          // No keywords? No posts.
 482          if (!strlen($this->search_query) && !count($author_ary))
 483          {
 484              return false;
 485          }
 486  
 487          $id_ary = array();
 488  
 489          // Sorting
 490  
 491          if ($type == 'topics')
 492          {
 493              switch ($sort_key)
 494              {
 495                  case 'a':
 496                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 497                  break;
 498  
 499                  case 'f':
 500                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 501                  break;
 502  
 503                  case 'i':
 504  
 505                  case 's':
 506                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 507                  break;
 508  
 509                  case 't':
 510  
 511                  default:
 512                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
 513                  break;
 514              }
 515          }
 516          else
 517          {
 518              switch ($sort_key)
 519              {
 520                  case 'a':
 521                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id');
 522                  break;
 523  
 524                  case 'f':
 525                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id');
 526                  break;
 527  
 528                  case 'i':
 529  
 530                  case 's':
 531                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject');
 532                  break;
 533  
 534                  case 't':
 535  
 536                  default:
 537                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time');
 538                  break;
 539              }
 540          }
 541  
 542          // Most narrow filters first
 543          if ($topic_id)
 544          {
 545              $this->sphinx->SetFilter('topic_id', array($topic_id));
 546          }
 547  
 548          /**
 549          * Allow modifying the Sphinx search options
 550          *
 551          * @event core.search_sphinx_keywords_modify_options
 552          * @var    string    type                Searching type ('posts', 'topics')
 553          * @var    string    fields                Searching fields ('titleonly', 'msgonly', 'firstpost', 'all')
 554          * @var    string    terms                Searching terms ('all', 'any')
 555          * @var    int        sort_days            Time, in days, of the oldest possible post to list
 556          * @var    string    sort_key            The sort type used from the possible sort types
 557          * @var    int        topic_id            Limit the search to this topic_id only
 558          * @var    array    ex_fid_ary            Which forums not to search on
 559          * @var    string    post_visibility        Post visibility data
 560          * @var    array    author_ary            Array of user_id containing the users to filter the results to
 561          * @var    string    author_name            The username to search on
 562          * @var    object    sphinx                The Sphinx searchd client object
 563          * @since 3.1.7-RC1
 564          */
 565          $sphinx = $this->sphinx;
 566          $vars = array(
 567              'type',
 568              'fields',
 569              'terms',
 570              'sort_days',
 571              'sort_key',
 572              'topic_id',
 573              'ex_fid_ary',
 574              'post_visibility',
 575              'author_ary',
 576              'author_name',
 577              'sphinx',
 578          );
 579          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars)));
 580          $this->sphinx = $sphinx;
 581          unset($sphinx);
 582  
 583          $search_query_prefix = '';
 584  
 585          switch ($fields)
 586          {
 587              case 'titleonly':
 588                  // Only search the title
 589                  if ($terms == 'all')
 590                  {
 591                      $search_query_prefix = '@title ';
 592                  }
 593                  // Weight for the title
 594                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 595                  // 1 is first_post, 0 is not first post
 596                  $this->sphinx->SetFilter('topic_first_post', array(1));
 597              break;
 598  
 599              case 'msgonly':
 600                  // Only search the body
 601                  if ($terms == 'all')
 602                  {
 603                      $search_query_prefix = '@data ';
 604                  }
 605                  // Weight for the body
 606                  $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5));
 607              break;
 608  
 609              case 'firstpost':
 610                  // More relative weight for the title, also search the body
 611                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 612                  // 1 is first_post, 0 is not first post
 613                  $this->sphinx->SetFilter('topic_first_post', array(1));
 614              break;
 615  
 616              default:
 617                  // More relative weight for the title, also search the body
 618                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
 619              break;
 620          }
 621  
 622          if (count($author_ary))
 623          {
 624              $this->sphinx->SetFilter('poster_id', $author_ary);
 625          }
 626  
 627          // As this is not simply possible at the moment, we limit the result to approved posts.
 628          // This will make it impossible for moderators to search unapproved and softdeleted posts,
 629          // but at least it will also cause the same for normal users.
 630          $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED));
 631  
 632          if (count($ex_fid_ary))
 633          {
 634              // All forums that a user is allowed to access
 635              $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true))));
 636              // All forums that the user wants to and can search in
 637              $search_forums = array_diff($fid_ary, $ex_fid_ary);
 638  
 639              if (count($search_forums))
 640              {
 641                  $this->sphinx->SetFilter('forum_id', $search_forums);
 642              }
 643          }
 644  
 645          $this->sphinx->SetFilter('deleted', array(0));
 646  
 647          $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES);
 648          $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 649  
 650          // Could be connection to localhost:9312 failed (errno=111,
 651          // msg=Connection refused) during rotate, retry if so
 652          $retries = SPHINX_CONNECT_RETRIES;
 653          while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
 654          {
 655              usleep(SPHINX_CONNECT_WAIT_TIME);
 656              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 657          }
 658  
 659          if ($this->sphinx->GetLastError())
 660          {
 661              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError()));
 662              if ($this->auth->acl_get('a_'))
 663              {
 664                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError()));
 665              }
 666              else
 667              {
 668                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG'));
 669              }
 670          }
 671  
 672          $result_count = $result['total_found'];
 673  
 674          if ($result_count && $start >= $result_count)
 675          {
 676              $start = floor(($result_count - 1) / $per_page) * $per_page;
 677  
 678              $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES);
 679              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 680  
 681              // Could be connection to localhost:9312 failed (errno=111,
 682              // msg=Connection refused) during rotate, retry if so
 683              $retries = SPHINX_CONNECT_RETRIES;
 684              while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
 685              {
 686                  usleep(SPHINX_CONNECT_WAIT_TIME);
 687                  $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
 688              }
 689          }
 690  
 691          $id_ary = array();
 692          if (isset($result['matches']))
 693          {
 694              if ($type == 'posts')
 695              {
 696                  $id_ary = array_keys($result['matches']);
 697              }
 698              else
 699              {
 700                  foreach ($result['matches'] as $key => $value)
 701                  {
 702                      $id_ary[] = $value['attrs']['topic_id'];
 703                  }
 704              }
 705          }
 706          else
 707          {
 708              return false;
 709          }
 710  
 711          $id_ary = array_slice($id_ary, 0, (int) $per_page);
 712  
 713          return $result_count;
 714      }
 715  
 716      /**
 717      * Performs a search on an author's posts without caring about message contents. Depends on display specific params
 718      *
 719      * @param    string        $type                contains either posts or topics depending on what should be searched for
 720      * @param    boolean        $firstpost_only        if true, only topic starting posts will be considered
 721      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 722      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 723      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 724      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 725      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 726      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
 727      * @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
 728      * @param    array        $author_ary            an array of author ids
 729      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 730      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 731      * @param    int            $start                indicates the first index of the page
 732      * @param    int            $per_page            number of ids each page is supposed to contain
 733      * @return    boolean|int                        total number of results
 734      */
 735  	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)
 736      {
 737          $this->search_query = '';
 738  
 739          $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN);
 740          $fields = ($firstpost_only) ? 'firstpost' : 'all';
 741          $terms = 'all';
 742          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);
 743      }
 744  
 745      /**
 746       * Updates wordlist and wordmatch tables when a message is posted or changed
 747       *
 748       * @param    string    $mode    Contains the post mode: edit, post, reply, quote
 749       * @param    int    $post_id    The id of the post which is modified/created
 750       * @param    string    &$message    New or updated post content
 751       * @param    string    &$subject    New or updated post subject
 752       * @param    int    $poster_id    Post author's user id
 753       * @param    int    $forum_id    The id of the forum in which the post is located
 754       */
 755  	public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
 756      {
 757          /**
 758          * Event to modify method arguments before the Sphinx search index is updated
 759          *
 760          * @event core.search_sphinx_index_before
 761          * @var string    mode                Contains the post mode: edit, post, reply, quote
 762          * @var int        post_id                The id of the post which is modified/created
 763          * @var string    message                New or updated post content
 764          * @var string    subject                New or updated post subject
 765          * @var int        poster_id            Post author's user id
 766          * @var int        forum_id            The id of the forum in which the post is located
 767          * @since 3.2.3-RC1
 768          */
 769          $vars = array(
 770              'mode',
 771              'post_id',
 772              'message',
 773              'subject',
 774              'poster_id',
 775              'forum_id',
 776          );
 777          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_index_before', compact($vars)));
 778  
 779          if ($mode == 'edit')
 780          {
 781              $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id)));
 782          }
 783          else if ($mode != 'post' && $post_id)
 784          {
 785              // Update topic_last_post_time for full topic
 786              $sql_array = array(
 787                  'SELECT'    => 'p1.post_id',
 788                  'FROM'        => array(
 789                      POSTS_TABLE    => 'p1',
 790                  ),
 791                  'LEFT_JOIN'    => array(array(
 792                      'FROM'    => array(
 793                          POSTS_TABLE    => 'p2'
 794                      ),
 795                      'ON'    => 'p1.topic_id = p2.topic_id',
 796                  )),
 797                  'WHERE' => 'p2.post_id = ' . ((int) $post_id),
 798              );
 799  
 800              $sql = $this->db->sql_build_query('SELECT', $sql_array);
 801              $result = $this->db->sql_query($sql);
 802  
 803              $post_updates = array();
 804              $post_time = time();
 805              while ($row = $this->db->sql_fetchrow($result))
 806              {
 807                  $post_updates[(int) $row['post_id']] = array($post_time);
 808              }
 809              $this->db->sql_freeresult($result);
 810  
 811              if (count($post_updates))
 812              {
 813                  $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates);
 814              }
 815          }
 816      }
 817  
 818      /**
 819      * Delete a post from the index after it was deleted
 820      */
 821  	public function index_remove($post_ids, $author_ids, $forum_ids)
 822      {
 823          $values = array();
 824          foreach ($post_ids as $post_id)
 825          {
 826              $values[$post_id] = array(1);
 827          }
 828  
 829          $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values);
 830      }
 831  
 832      /**
 833      * Nothing needs to be destroyed
 834      */
 835  	public function tidy($create = false)
 836      {
 837          $this->config->set('search_last_gc', time(), false);
 838      }
 839  
 840      /**
 841      * Create sphinx table
 842      *
 843      * @return string|bool error string is returned incase of errors otherwise false
 844      */
 845  	public function create_index($acp_module, $u_action)
 846      {
 847          if (!$this->index_created())
 848          {
 849              $table_data = array(
 850                  'COLUMNS'    => array(
 851                      'counter_id'    => array('UINT', 0),
 852                      'max_doc_id'    => array('UINT', 0),
 853                  ),
 854                  'PRIMARY_KEY'    => 'counter_id',
 855              );
 856              $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data);
 857  
 858              $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE;
 859              $this->db->sql_query($sql);
 860  
 861              $data = array(
 862                  'counter_id'    => '1',
 863                  'max_doc_id'    => '0',
 864              );
 865              $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data);
 866              $this->db->sql_query($sql);
 867          }
 868  
 869          return false;
 870      }
 871  
 872      /**
 873      * Drop sphinx table
 874      *
 875      * @return string|bool error string is returned incase of errors otherwise false
 876      */
 877  	public function delete_index($acp_module, $u_action)
 878      {
 879          if (!$this->index_created())
 880          {
 881              return false;
 882          }
 883  
 884          $this->db_tools->sql_table_drop(SPHINX_TABLE);
 885  
 886          return false;
 887      }
 888  
 889      /**
 890      * Returns true if the sphinx table was created
 891      *
 892      * @return bool true if sphinx table was created
 893      */
 894  	public function index_created($allow_new_files = true)
 895      {
 896          $created = false;
 897  
 898          if ($this->db_tools->sql_table_exists(SPHINX_TABLE))
 899          {
 900              $created = true;
 901          }
 902  
 903          return $created;
 904      }
 905  
 906      /**
 907      * Returns an associative array containing information about the indexes
 908      *
 909      * @return string|bool Language string of error false otherwise
 910      */
 911  	public function index_stats()
 912      {
 913          if (empty($this->stats))
 914          {
 915              $this->get_stats();
 916          }
 917  
 918          return array(
 919              $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS']            => ($this->index_created()) ? $this->stats['main_posts'] : 0,
 920              $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0,
 921              $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] : 0,
 922          );
 923      }
 924  
 925      /**
 926      * Collects stats that can be displayed on the index maintenance page
 927      */
 928  	protected function get_stats()
 929      {
 930          if ($this->index_created())
 931          {
 932              $sql = 'SELECT COUNT(post_id) as total_posts
 933                  FROM ' . POSTS_TABLE;
 934              $result = $this->db->sql_query($sql);
 935              $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts');
 936              $this->db->sql_freeresult($result);
 937  
 938              $sql = 'SELECT COUNT(p.post_id) as main_posts
 939                  FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m
 940                  WHERE p.post_id <= m.max_doc_id
 941                      AND m.counter_id = 1';
 942              $result = $this->db->sql_query($sql);
 943              $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts');
 944              $this->db->sql_freeresult($result);
 945          }
 946      }
 947  
 948      /**
 949      * Returns a list of options for the ACP to display
 950      *
 951      * @return associative array containing template and config variables
 952      */
 953  	public function acp()
 954      {
 955          $config_vars = array(
 956              'fulltext_sphinx_data_path' => 'string',
 957              'fulltext_sphinx_host' => 'string',
 958              'fulltext_sphinx_port' => 'string',
 959              'fulltext_sphinx_indexer_mem_limit' => 'int',
 960          );
 961  
 962          $tpl = '
 963          <span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span>
 964          <dl>
 965              <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>
 966              <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>
 967          </dl>
 968          <dl>
 969              <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>
 970              <dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd>
 971          </dl>
 972          <dl>
 973              <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>
 974              <dd><input id="fulltext_sphinx_port" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd>
 975          </dl>
 976          <dl>
 977              <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>
 978              <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>
 979          </dl>
 980          <dl>
 981              <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>
 982              <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd>
 983          <dl>
 984          ';
 985  
 986          // These are fields required in the config table
 987          return array(
 988              'tpl'        => $tpl,
 989              'config'    => $config_vars
 990          );
 991      }
 992  }


Generated: Tue Apr 7 19:42:26 2020 Cross-referenced by PHPXref 0.7.1