[ Index ]

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


Generated: Thu Jan 11 00:25:41 2018 Cross-referenced by PHPXref 0.7.1