Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

fulltext_sphinx.php

Zuletzt modifiziert: 01.05.2026, 11:25 - Dateigröße: 38.55 KiB


0001  <?php
0002  /**
0003  *
0004  * This file is part of the phpBB Forum Software package.
0005  *
0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
0007  * @license GNU General Public License, version 2 (GPL-2.0)
0008  *
0009  * For full copyright and license information, please see
0010  * the docs/CREDITS.txt file.
0011  *
0012  */
0013   
0014  namespace phpbb\search;
0015   
0016  define('SPHINX_MAX_MATCHES', 20000);
0017  define('SPHINX_CONNECT_RETRIES', 3);
0018  define('SPHINX_CONNECT_WAIT_TIME', 300);
0019   
0020  /**
0021  * Fulltext search based on the sphinx search daemon
0022  */
0023  class fulltext_sphinx
0024  {
0025      /**
0026       * Associative array holding index stats
0027       * @var array
0028       */
0029      protected $stats = array();
0030   
0031      /**
0032       * Holds the words entered by user, obtained by splitting the entered query on whitespace
0033       * @var array
0034       */
0035      protected $split_words = array();
0036   
0037      /**
0038       * Holds unique sphinx id
0039       * @var string
0040       */
0041      protected $id;
0042   
0043      /**
0044       * Stores the names of both main and delta sphinx indexes
0045       * separated by a semicolon
0046       * @var string
0047       */
0048      protected $indexes;
0049   
0050      /**
0051       * Sphinx searchd client object
0052       * @var SphinxClient
0053       */
0054      protected $sphinx;
0055   
0056      /**
0057       * Relative path to board root
0058       * @var string
0059       */
0060      protected $phpbb_root_path;
0061   
0062      /**
0063       * PHP Extension
0064       * @var string
0065       */
0066      protected $php_ext;
0067   
0068      /**
0069       * Auth object
0070       * @var \phpbb\auth\auth
0071       */
0072      protected $auth;
0073   
0074      /**
0075       * Config object
0076       * @var \phpbb\config\config
0077       */
0078      protected $config;
0079   
0080      /**
0081       * Database connection
0082       * @var \phpbb\db\driver\driver_interface
0083       */
0084      protected $db;
0085   
0086      /**
0087       * Database Tools object
0088       * @var \phpbb\db\tools\tools_interface
0089       */
0090      protected $db_tools;
0091   
0092      /**
0093       * Stores the database type if supported by sphinx
0094       * @var string
0095       */
0096      protected $dbtype;
0097   
0098      /**
0099       * phpBB event dispatcher object
0100       * @var \phpbb\event\dispatcher_interface
0101       */
0102      protected $phpbb_dispatcher;
0103   
0104      /**
0105       * User object
0106       * @var \phpbb\user
0107       */
0108      protected $user;
0109   
0110      /**
0111       * Stores the generated content of the sphinx config file
0112       * @var string
0113       */
0114      protected $config_file_data = '';
0115   
0116      /**
0117       * Contains tidied search query.
0118       * Operators are prefixed in search query and common words excluded
0119       * @var string
0120       */
0121      protected $search_query;
0122   
0123      /**
0124       * Constructor
0125       * Creates a new \phpbb\search\fulltext_postgres, which is used as a search backend
0126       *
0127       * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false
0128       * @param string $phpbb_root_path Relative path to phpBB root
0129       * @param string $phpEx PHP file extension
0130       * @param \phpbb\auth\auth $auth Auth object
0131       * @param \phpbb\config\config $config Config object
0132       * @param \phpbb\db\driver\driver_interface $db Database object
0133       * @param \phpbb\user $user User object
0134       * @param \phpbb\event\dispatcher_interface    $phpbb_dispatcher    Event dispatcher object
0135       */
0136      public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher)
0137      {
0138          $this->phpbb_root_path = $phpbb_root_path;
0139          $this->php_ext = $phpEx;
0140          $this->config = $config;
0141          $this->phpbb_dispatcher = $phpbb_dispatcher;
0142          $this->user = $user;
0143          $this->db = $db;
0144          $this->auth = $auth;
0145   
0146          // Initialize \phpbb\db\tools\tools object
0147          global $phpbb_container; // TODO inject into object
0148          $this->db_tools = $phpbb_container->get('dbal.tools');
0149   
0150          if (!$this->config['fulltext_sphinx_id'])
0151          {
0152              $this->config->set('fulltext_sphinx_id', unique_id());
0153          }
0154          $this->id = $this->config['fulltext_sphinx_id'];
0155          $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main';
0156   
0157          if (!class_exists('SphinxClient'))
0158          {
0159              require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext);
0160          }
0161   
0162          // Initialize sphinx client
0163          $this->sphinx = new \SphinxClient();
0164   
0165          $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));
0166   
0167          $error = false;
0168      }
0169   
0170      /**
0171      * Returns the name of this search backend to be displayed to administrators
0172      *
0173      * @return string Name
0174      */
0175      public function get_name()
0176      {
0177          return 'Sphinx Fulltext';
0178      }
0179   
0180      /**
0181       * Returns the search_query
0182       *
0183       * @return string search query
0184       */
0185      public function get_search_query()
0186      {
0187          return $this->search_query;
0188      }
0189   
0190      /**
0191       * Returns false as there is no word_len array
0192       *
0193       * @return false
0194       */
0195      public function get_word_length()
0196      {
0197          return false;
0198      }
0199   
0200      /**
0201       * Returns an empty array as there are no common_words
0202       *
0203       * @return array common words that are ignored by search backend
0204       */
0205      public function get_common_words()
0206      {
0207          return array();
0208      }
0209   
0210      /**
0211      * Checks permissions and paths, if everything is correct it generates the config file
0212      *
0213      * @return string|bool Language key of the error/incompatibility encountered, or false if successful
0214      */
0215      public function init()
0216      {
0217          if ($this->db->get_sql_layer() != 'mysqli' && $this->db->get_sql_layer() != 'postgres')
0218          {
0219              return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE'];
0220          }
0221   
0222          // Move delta to main index each hour
0223          $this->config->set('search_gc', 3600);
0224   
0225          return false;
0226      }
0227   
0228      /**
0229       * Generates content of sphinx.conf
0230       *
0231       * @return bool True if sphinx.conf content is correctly generated, false otherwise
0232       */
0233      protected function config_generate()
0234      {
0235          // Check if Database is supported by Sphinx
0236          if ($this->db->get_sql_layer() == 'mysqli')
0237          {
0238              $this->dbtype = 'mysql';
0239          }
0240          else if ($this->db->get_sql_layer() == 'postgres')
0241          {
0242              $this->dbtype = 'pgsql';
0243          }
0244          else
0245          {
0246              $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE');
0247              return false;
0248          }
0249   
0250          // Check if directory paths have been filled
0251          if (!$this->config['fulltext_sphinx_data_path'])
0252          {
0253              $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA');
0254              return false;
0255          }
0256   
0257          include($this->phpbb_root_path . 'config.' . $this->php_ext);
0258   
0259          /* Now that we're sure everything was entered correctly,
0260          generate a config for the index. We use a config value
0261          fulltext_sphinx_id for this, as it should be unique. */
0262          $config_object = new \phpbb\search\sphinx\config($this->config_file_data);
0263          $config_data = array(
0264              'source source_phpbb_' . $this->id . '_main' => array(
0265                  array('type',                        $this->dbtype . ' # mysql or pgsql'),
0266                  // This config value sql_host needs to be changed incase sphinx and sql are on different servers
0267                  array('sql_host',                    $dbhost . ' # SQL server host sphinx connects to'),
0268                  array('sql_user',                    '[dbuser]'),
0269                  array('sql_pass',                    '[dbpassword]'),
0270                  array('sql_db',                        $dbname),
0271                  array('sql_port',                    $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'),
0272                  array('sql_query_pre',                'SET NAMES \'utf8\''),
0273                  array('sql_query_pre',                'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'),
0274                  array('sql_query_range',            'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''),
0275                  array('sql_range_step',                '5000'),
0276                  array('sql_query',                    'SELECT
0277                          p.post_id AS id,
0278                          p.forum_id,
0279                          p.topic_id,
0280                          p.poster_id,
0281                          p.post_visibility,
0282                          CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
0283                          p.post_time,
0284                          p.post_subject,
0285                          p.post_subject as title,
0286                          p.post_text as data,
0287                          t.topic_last_post_time,
0288                          0 as deleted
0289                      FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t
0290                      WHERE
0291                          p.topic_id = t.topic_id
0292                          AND p.post_id >= $start AND p.post_id <= $end'),
0293                  array('sql_query_post',                ''),
0294                  array('sql_query_post_index',        'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'),
0295                  array('sql_attr_uint',                'forum_id'),
0296                  array('sql_attr_uint',                'topic_id'),
0297                  array('sql_attr_uint',                'poster_id'),
0298                  array('sql_attr_uint',                'post_visibility'),
0299                  array('sql_attr_bool',                'topic_first_post'),
0300                  array('sql_attr_bool',                'deleted'),
0301                  array('sql_attr_timestamp',            'post_time'),
0302                  array('sql_attr_timestamp',            'topic_last_post_time'),
0303                  array('sql_attr_string',            'post_subject'),
0304              ),
0305              'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array(
0306                  array('sql_query_pre',                'SET NAMES \'utf8\''),
0307                  array('sql_query_range',            ''),
0308                  array('sql_range_step',                ''),
0309                  array('sql_query',                    'SELECT
0310                          p.post_id AS id,
0311                          p.forum_id,
0312                          p.topic_id,
0313                          p.poster_id,
0314                          p.post_visibility,
0315                          CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
0316                          p.post_time,
0317                          p.post_subject,
0318                          p.post_subject as title,
0319                          p.post_text as data,
0320                          t.topic_last_post_time,
0321                          0 as deleted
0322                      FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t
0323                      WHERE
0324                          p.topic_id = t.topic_id
0325                          AND p.post_id >=  ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'),
0326                  array('sql_query_post_index',        ''),
0327              ),
0328              'index index_phpbb_' . $this->id . '_main' => array(
0329                  array('path',                        $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'),
0330                  array('source',                        'source_phpbb_' . $this->id . '_main'),
0331                  array('docinfo',                    'extern'),
0332                  array('morphology',                    'none'),
0333                  array('stopwords',                    ''),
0334                  array('wordforms',                    '  # optional, specify path to wordforms file. See ./docs/sphinx_wordforms.txt for example'),
0335                  array('exceptions',                    '  # optional, specify path to exceptions file. See ./docs/sphinx_exceptions.txt for example'),
0336                  array('min_word_len',                '2'),
0337                  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'),
0338                  array('ignore_chars',                 'U+0027, U+002C'),
0339                  array('min_prefix_len',                '3 # Minimum number of characters for wildcard searches by prefix (min 1). Default is 3. If specified, set min_infix_len to 0'),
0340                  array('min_infix_len',                '0 # Minimum number of characters for wildcard searches by infix (min 2). If specified, set min_prefix_len to 0'),
0341                  array('html_strip',                    '1'),
0342                  array('index_exact_words',            '0 # Set to 1 to enable exact search operator. Requires wordforms or morphology'),
0343                  array('blend_chars',                 'U+23, U+24, U+25, U+26, U+40'),
0344              ),
0345              'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array(
0346                  array('path',                        $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'),
0347                  array('source',                        'source_phpbb_' . $this->id . '_delta'),
0348              ),
0349              'indexer' => array(
0350                  array('mem_limit',                    $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'),
0351              ),
0352              'searchd' => array(
0353                  array('listen'    ,                    ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')),
0354                  array('log',                        $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'),
0355                  array('query_log',                    $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'),
0356                  array('read_timeout',                '5'),
0357                  array('max_children',                '30'),
0358                  array('pid_file',                    $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'),
0359                  array('binlog_path',                rtrim($this->config['fulltext_sphinx_data_path'], '/\\')), // Trim trailing slash
0360              ),
0361          );
0362   
0363          $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true);
0364          $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true);
0365   
0366          /**
0367          * Allow adding/changing the Sphinx configuration data
0368          *
0369          * @event core.search_sphinx_modify_config_data
0370          * @var    array    config_data    Array with the Sphinx configuration data
0371          * @var    array    non_unique    Array with the Sphinx non-unique variables to delete
0372          * @var    array    delete        Array with the Sphinx variables to delete
0373          * @since 3.1.7-RC1
0374          */
0375          $vars = array(
0376              'config_data',
0377              'non_unique',
0378              'delete',
0379          );
0380          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars)));
0381   
0382          foreach ($config_data as $section_name => $section_data)
0383          {
0384              $section = $config_object->get_section_by_name($section_name);
0385              if (!$section)
0386              {
0387                  $section = $config_object->add_section($section_name);
0388              }
0389   
0390              foreach ($delete as $key => $void)
0391              {
0392                  $section->delete_variables_by_name($key);
0393              }
0394   
0395              foreach ($non_unique as $key => $void)
0396              {
0397                  $section->delete_variables_by_name($key);
0398              }
0399   
0400              foreach ($section_data as $entry)
0401              {
0402                  $key = $entry[0];
0403                  $value = $entry[1];
0404   
0405                  if (!isset($non_unique[$key]))
0406                  {
0407                      $variable = $section->get_variable_by_name($key);
0408                      if (!$variable)
0409                      {
0410                          $section->create_variable($key, $value);
0411                      }
0412                      else
0413                      {
0414                          $variable->set_value($value);
0415                      }
0416                  }
0417                  else
0418                  {
0419                      $section->create_variable($key, $value);
0420                  }
0421              }
0422          }
0423          $this->config_file_data = $config_object->get_data();
0424   
0425          return true;
0426      }
0427   
0428      /**
0429      * Splits keywords entered by a user into an array of words stored in $this->split_words
0430      * Stores the tidied search query in $this->search_query
0431      *
0432      * @param string $keywords Contains the keyword as entered by the user
0433      * @param string $terms is either 'all' or 'any'
0434      * @return false if no valid keywords were found and otherwise true
0435      */
0436      public function split_keywords(&$keywords, $terms)
0437      {
0438          // Keep quotes and new lines
0439          $keywords = str_replace(['&quot;', "\n"], ['"', ' '], trim($keywords));
0440   
0441          if ($terms == 'all')
0442          {
0443              // Replaces verbal operators OR and NOT with special characters | and -, unless appearing within quotation marks
0444              $match        = ['#\sor\s(?=([^"]*"[^"]*")*[^"]*$)#i', '#\snot\s(?=([^"]*"[^"]*")*[^"]*$)#i'];
0445              $replace    = [' | ', ' -'];
0446   
0447              $keywords = preg_replace($match, $replace, $keywords);
0448              $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED);
0449          }
0450          else
0451          {
0452              $match = ['\\', '(',')', '|', '!', '@', '~', '/', '^', '$', '=', '&amp;', '&lt;', '&gt;'];
0453   
0454              $keywords = str_replace($match, ' ', $keywords);
0455              $this->sphinx->SetMatchMode(SPH_MATCH_ANY);
0456          }
0457   
0458          // Split words
0459          $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($keywords)));
0460          $matches = array();
0461          preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches);
0462          $this->split_words = $matches[1];
0463   
0464          if ($terms == 'any')
0465          {
0466              $this->search_query = '';
0467              foreach ($this->split_words as $word)
0468              {
0469                  if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0))
0470                  {
0471                      $word = substr($word, 1);
0472                  }
0473                  $this->search_query .= $word . ' ';
0474              }
0475          }
0476          else
0477          {
0478              $this->search_query = '';
0479              foreach ($this->split_words as $word)
0480              {
0481                  if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0))
0482                  {
0483                      $this->search_query .= $word . ' ';
0484                  }
0485                  else if (strpos($word, '|') === 0)
0486                  {
0487                      $this->search_query .= substr($word, 1) . ' ';
0488                  }
0489                  else
0490                  {
0491                      $this->search_query .= '+' . $word . ' ';
0492                  }
0493              }
0494          }
0495   
0496          if ($this->search_query)
0497          {
0498              $this->search_query = str_replace('"', '&quot;', $this->search_query);
0499              return true;
0500          }
0501   
0502          return false;
0503      }
0504   
0505      /**
0506      * Cleans search query passed into Sphinx search engine, as follows:
0507      * 1. Hyphenated words are replaced with keyword search for either the exact phrase with spaces
0508      *    or as a single word without spaces eg search for "know-it-all" becomes ("know it all"|"knowitall*")
0509      * 2. Words with apostrophes are contracted eg "it's" becomes "its"
0510      * 3. <, >, " and & are decoded from HTML entities.
0511      * 4. Following special characters used as search operators in Sphinx are preserved when used with correct syntax:
0512      *    (a) quorum matching: "the world is a wonderful place"/3
0513      *        Finds 3 of the words within the phrase. Number must be between 1 and 9.
0514      *    (b) proximity search: "hello world"~10
0515      *        Finds hello and world within 10 words of each other. Number can be between 1 and 99.
0516      *    (c) strict word order: aaa << bbb << ccc
0517      *        Finds "aaa" only where it appears before "bbb" and only where "bbb" appears before "ccc".
0518      *    (d) exact match operator: if lemmatizer or stemming enabled,
0519      *        search will find exact match only and ignore other grammatical forms of the same word stem.
0520      *        eg. raining =cats and =dogs
0521      *            will not return "raining cat and dog"
0522      *        eg. ="search this exact phrase"
0523      *            will not return "searched this exact phrase", "searching these exact phrases".
0524      * 5. Special characters /, ~, << and = not complying with the correct syntax
0525      *    and other reserved operators are escaped and searched literally.
0526      *    Special characters not explicitly listed in charset_table or blend_chars in sphinx.conf
0527      *    will not be indexed and keywords containing them will be ignored by Sphinx.
0528      *    By default, only $, %, & and @ characters are indexed and searchable.
0529      *    String transformation is in backend only and not visible to the end user
0530      *    nor reflected in the results page URL or keyword highlighting.
0531      *
0532      * @param string    $search_string
0533      * @return string
0534      */
0535      public function sphinx_clean_search_string($search_string)
0536      {
0537          $from = ['@', '^', '$', '!', '&lt;', '&gt;', '&quot;', '&amp;', '\''];
0538          $to = ['\@', '\^', '\$', '\!', '<', '>', '"', '&', ''];
0539   
0540          $search_string = str_replace($from, $to, $search_string);
0541   
0542          $search_string = strrev($search_string);
0543          $search_string = preg_replace(['#\/(?!"[^"]+")#', '#~(?!"[^"]+")#'], ['/\\', '~\\'], $search_string);
0544          $search_string = strrev($search_string);
0545   
0546          $match = ['#(/|\\\\/)(?![1-9](\s|$))#', '#(~|\\\\~)(?!\d{1,2}(\s|$))#', '#((?:\p{L}|\p{N})+)-((?:\p{L}|\p{N})+)(?:-((?:\p{L}|\p{N})+))?(?:-((?:\p{L}|\p{N})+))?#i', '#<<\s*$#', '#(\S\K=|=(?=\s)|=$)#'];
0547          $replace = ['\/', '\~', '("$1 $2 $3 $4"|$1$2$3$4*)', '\<\<', '\='];
0548   
0549          $search_string = preg_replace($match, $replace, $search_string);
0550          $search_string = preg_replace('#\s+"\|#', '"|', $search_string);
0551   
0552          /**
0553          * OPTIONAL: Thousands separator stripped from numbers, eg search for '90,000' is queried as '90000'.
0554          * By default commas are stripped from search index so that '90,000' is indexed as '90000'
0555          */
0556          // $search_string = preg_replace('#[0-9]{1,3}\K,(?=[0-9]{3})#', '', $search_string);
0557   
0558          return $search_string;
0559      }
0560   
0561      /**
0562      * Performs a search on keywords depending on display specific params. You have to run split_keywords() first
0563      *
0564      * @param    string        $type                contains either posts or topics depending on what should be searched for
0565      * @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)
0566      * @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)
0567      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
0568      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
0569      * @param    string        $sort_dir            is either a or d representing ASC and DESC
0570      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
0571      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
0572      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
0573      * @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
0574      * @param    array        $author_ary            an array of author ids if the author should be ignored during the search the array is empty
0575      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
0576      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
0577      * @param    int            $start                indicates the first index of the page
0578      * @param    int            $per_page            number of ids each page is supposed to contain
0579      * @return    boolean|int                        total number of results
0580      */
0581      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)
0582      {
0583          global $user, $phpbb_log;
0584   
0585          // No keywords? No posts.
0586          if (!strlen($this->search_query) && !count($author_ary))
0587          {
0588              return false;
0589          }
0590   
0591          $id_ary = array();
0592   
0593          // Sorting
0594   
0595          if ($type == 'topics')
0596          {
0597              switch ($sort_key)
0598              {
0599                  case 'a':
0600                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
0601                  break;
0602   
0603                  case 'f':
0604                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
0605                  break;
0606   
0607                  case 'i':
0608   
0609                  case 's':
0610                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
0611                  break;
0612   
0613                  case 't':
0614   
0615                  default:
0616                      $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
0617                  break;
0618              }
0619          }
0620          else
0621          {
0622              switch ($sort_key)
0623              {
0624                  case 'a':
0625                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id');
0626                  break;
0627   
0628                  case 'f':
0629                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id');
0630                  break;
0631   
0632                  case 'i':
0633   
0634                  case 's':
0635                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject');
0636                  break;
0637   
0638                  case 't':
0639   
0640                  default:
0641                      $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time');
0642                  break;
0643              }
0644          }
0645   
0646          // Most narrow filters first
0647          if ($topic_id)
0648          {
0649              $this->sphinx->SetFilter('topic_id', array($topic_id));
0650          }
0651   
0652          /**
0653          * Allow modifying the Sphinx search options
0654          *
0655          * @event core.search_sphinx_keywords_modify_options
0656          * @var    string    type                Searching type ('posts', 'topics')
0657          * @var    string    fields                Searching fields ('titleonly', 'msgonly', 'firstpost', 'all')
0658          * @var    string    terms                Searching terms ('all', 'any')
0659          * @var    int        sort_days            Time, in days, of the oldest possible post to list
0660          * @var    string    sort_key            The sort type used from the possible sort types
0661          * @var    int        topic_id            Limit the search to this topic_id only
0662          * @var    array    ex_fid_ary            Which forums not to search on
0663          * @var    string    post_visibility        Post visibility data
0664          * @var    array    author_ary            Array of user_id containing the users to filter the results to
0665          * @var    string    author_name            The username to search on
0666          * @var    object    sphinx                The Sphinx searchd client object
0667          * @since 3.1.7-RC1
0668          */
0669          $sphinx = $this->sphinx;
0670          $vars = array(
0671              'type',
0672              'fields',
0673              'terms',
0674              'sort_days',
0675              'sort_key',
0676              'topic_id',
0677              'ex_fid_ary',
0678              'post_visibility',
0679              'author_ary',
0680              'author_name',
0681              'sphinx',
0682          );
0683          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars)));
0684          $this->sphinx = $sphinx;
0685          unset($sphinx);
0686   
0687          $search_query_prefix = '';
0688   
0689          switch ($fields)
0690          {
0691              case 'titleonly':
0692                  // Only search the title
0693                  if ($terms == 'all')
0694                  {
0695                      $search_query_prefix = '@title ';
0696                  }
0697                  // Weight for the title
0698                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
0699                  // 1 is first_post, 0 is not first post
0700                  $this->sphinx->SetFilter('topic_first_post', array(1));
0701              break;
0702   
0703              case 'msgonly':
0704                  // Only search the body
0705                  if ($terms == 'all')
0706                  {
0707                      $search_query_prefix = '@data ';
0708                  }
0709                  // Weight for the body
0710                  $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5));
0711              break;
0712   
0713              case 'firstpost':
0714                  // More relative weight for the title, also search the body
0715                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
0716                  // 1 is first_post, 0 is not first post
0717                  $this->sphinx->SetFilter('topic_first_post', array(1));
0718              break;
0719   
0720              default:
0721                  // More relative weight for the title, also search the body
0722                  $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
0723              break;
0724          }
0725   
0726          if (count($author_ary))
0727          {
0728              $this->sphinx->SetFilter('poster_id', $author_ary);
0729          }
0730   
0731          // As this is not simply possible at the moment, we limit the result to approved posts.
0732          // This will make it impossible for moderators to search unapproved and softdeleted posts,
0733          // but at least it will also cause the same for normal users.
0734          $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED));
0735   
0736          if (count($ex_fid_ary))
0737          {
0738              // All forums that a user is allowed to access
0739              $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true))));
0740              // All forums that the user wants to and can search in
0741              $search_forums = array_diff($fid_ary, $ex_fid_ary);
0742   
0743              if (count($search_forums))
0744              {
0745                  $this->sphinx->SetFilter('forum_id', $search_forums);
0746              }
0747          }
0748   
0749          $this->sphinx->SetFilter('deleted', array(0));
0750   
0751          $this->sphinx->SetLimits((int) $start, (int) $per_page, max(SPHINX_MAX_MATCHES, (int) $start + $per_page));
0752          $result = $this->sphinx->Query($search_query_prefix . $this->sphinx_clean_search_string(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
0753   
0754          // Could be connection to localhost:9312 failed (errno=111,
0755          // msg=Connection refused) during rotate, retry if so
0756          $retries = SPHINX_CONNECT_RETRIES;
0757          while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
0758          {
0759              usleep(SPHINX_CONNECT_WAIT_TIME);
0760              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx_clean_search_string(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
0761          }
0762   
0763          if ($this->sphinx->GetLastError())
0764          {
0765              $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError()));
0766              if ($this->auth->acl_get('a_'))
0767              {
0768                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError()));
0769              }
0770              else
0771              {
0772                  trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG'));
0773              }
0774          }
0775   
0776          $result_count = $result['total_found'];
0777   
0778          if ($result_count && $start >= $result_count)
0779          {
0780              $start = floor(($result_count - 1) / $per_page) * $per_page;
0781   
0782              $this->sphinx->SetLimits((int) $start, (int) $per_page, max(SPHINX_MAX_MATCHES, (int) $start + $per_page));
0783              $result = $this->sphinx->Query($search_query_prefix . $this->sphinx_clean_search_string(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
0784   
0785              // Could be connection to localhost:9312 failed (errno=111,
0786              // msg=Connection refused) during rotate, retry if so
0787              $retries = SPHINX_CONNECT_RETRIES;
0788              while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
0789              {
0790                  usleep(SPHINX_CONNECT_WAIT_TIME);
0791                  $result = $this->sphinx->Query($search_query_prefix . $this->sphinx_clean_search_string(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
0792              }
0793          }
0794   
0795          $id_ary = array();
0796          if (isset($result['matches']))
0797          {
0798              if ($type == 'posts')
0799              {
0800                  $id_ary = array_keys($result['matches']);
0801              }
0802              else
0803              {
0804                  foreach ($result['matches'] as $key => $value)
0805                  {
0806                      $id_ary[] = $value['attrs']['topic_id'];
0807                  }
0808              }
0809          }
0810          else
0811          {
0812              return false;
0813          }
0814   
0815          $id_ary = array_slice($id_ary, 0, (int) $per_page);
0816   
0817          return $result_count;
0818      }
0819   
0820      /**
0821      * Performs a search on an author's posts without caring about message contents. Depends on display specific params
0822      *
0823      * @param    string        $type                contains either posts or topics depending on what should be searched for
0824      * @param    boolean        $firstpost_only        if true, only topic starting posts will be considered
0825      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
0826      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
0827      * @param    string        $sort_dir            is either a or d representing ASC and DESC
0828      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
0829      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
0830      * @param    string        $post_visibility    specifies which types of posts the user can view in which forums
0831      * @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
0832      * @param    array        $author_ary            an array of author ids
0833      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
0834      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
0835      * @param    int            $start                indicates the first index of the page
0836      * @param    int            $per_page            number of ids each page is supposed to contain
0837      * @return    boolean|int                        total number of results
0838      */
0839      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)
0840      {
0841          $this->search_query = '';
0842   
0843          $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN);
0844          $fields = ($firstpost_only) ? 'firstpost' : 'all';
0845          $terms = 'all';
0846          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);
0847      }
0848   
0849      /**
0850       * Updates wordlist and wordmatch tables when a message is posted or changed
0851       *
0852       * @param    string    $mode    Contains the post mode: edit, post, reply, quote
0853       * @param    int    $post_id    The id of the post which is modified/created
0854       * @param    string    &$message    New or updated post content
0855       * @param    string    &$subject    New or updated post subject
0856       * @param    int    $poster_id    Post author's user id
0857       * @param    int    $forum_id    The id of the forum in which the post is located
0858       */
0859      public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
0860      {
0861          /**
0862          * Event to modify method arguments before the Sphinx search index is updated
0863          *
0864          * @event core.search_sphinx_index_before
0865          * @var string    mode                Contains the post mode: edit, post, reply, quote
0866          * @var int        post_id                The id of the post which is modified/created
0867          * @var string    message                New or updated post content
0868          * @var string    subject                New or updated post subject
0869          * @var int        poster_id            Post author's user id
0870          * @var int        forum_id            The id of the forum in which the post is located
0871          * @since 3.2.3-RC1
0872          */
0873          $vars = array(
0874              'mode',
0875              'post_id',
0876              'message',
0877              'subject',
0878              'poster_id',
0879              'forum_id',
0880          );
0881          extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_index_before', compact($vars)));
0882   
0883          if ($mode == 'edit')
0884          {
0885              $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id)));
0886          }
0887          else if ($mode != 'post' && $post_id)
0888          {
0889              // Update topic_last_post_time for full topic
0890              $sql_array = array(
0891                  'SELECT'    => 'p1.post_id',
0892                  'FROM'        => array(
0893                      POSTS_TABLE    => 'p1',
0894                  ),
0895                  'LEFT_JOIN'    => array(array(
0896                      'FROM'    => array(
0897                          POSTS_TABLE    => 'p2'
0898                      ),
0899                      'ON'    => 'p1.topic_id = p2.topic_id',
0900                  )),
0901                  'WHERE' => 'p2.post_id = ' . ((int) $post_id),
0902              );
0903   
0904              $sql = $this->db->sql_build_query('SELECT', $sql_array);
0905              $result = $this->db->sql_query($sql);
0906   
0907              $post_updates = array();
0908              $post_time = time();
0909              while ($row = $this->db->sql_fetchrow($result))
0910              {
0911                  $post_updates[(int) $row['post_id']] = array($post_time);
0912              }
0913              $this->db->sql_freeresult($result);
0914   
0915              if (count($post_updates))
0916              {
0917                  $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates);
0918              }
0919          }
0920      }
0921   
0922      /**
0923      * Delete a post from the index after it was deleted
0924      */
0925      public function index_remove($post_ids, $author_ids, $forum_ids)
0926      {
0927          $values = array();
0928          foreach ($post_ids as $post_id)
0929          {
0930              $values[$post_id] = array(1);
0931          }
0932   
0933          $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values);
0934      }
0935   
0936      /**
0937      * Nothing needs to be destroyed
0938      */
0939      public function tidy($create = false)
0940      {
0941          $this->config->set('search_last_gc', time(), false);
0942      }
0943   
0944      /**
0945      * Create sphinx table
0946      *
0947      * @return string|bool error string is returned incase of errors otherwise false
0948      */
0949      public function create_index($acp_module, $u_action)
0950      {
0951          if (!$this->index_created())
0952          {
0953              $table_data = array(
0954                  'COLUMNS'    => array(
0955                      'counter_id'    => array('UINT', 0),
0956                      'max_doc_id'    => array('UINT', 0),
0957                  ),
0958                  'PRIMARY_KEY'    => 'counter_id',
0959              );
0960              $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data);
0961   
0962              $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE;
0963              $this->db->sql_query($sql);
0964   
0965              $data = array(
0966                  'counter_id'    => '1',
0967                  'max_doc_id'    => '0',
0968              );
0969              $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data);
0970              $this->db->sql_query($sql);
0971          }
0972   
0973          return false;
0974      }
0975   
0976      /**
0977      * Drop sphinx table
0978      *
0979      * @return string|bool error string is returned incase of errors otherwise false
0980      */
0981      public function delete_index($acp_module, $u_action)
0982      {
0983          if (!$this->index_created())
0984          {
0985              return false;
0986          }
0987   
0988          $this->db_tools->sql_table_drop(SPHINX_TABLE);
0989   
0990          return false;
0991      }
0992   
0993      /**
0994      * Returns true if the sphinx table was created
0995      *
0996      * @return bool true if sphinx table was created
0997      */
0998      public function index_created($allow_new_files = true)
0999      {
1000          $created = false;
1001   
1002          if ($this->db_tools->sql_table_exists(SPHINX_TABLE))
1003          {
1004              $created = true;
1005          }
1006   
1007          return $created;
1008      }
1009   
1010      /**
1011      * Returns an associative array containing information about the indexes
1012      *
1013      * @return string|bool Language string of error false otherwise
1014      */
1015      public function index_stats()
1016      {
1017          if (empty($this->stats))
1018          {
1019              $this->get_stats();
1020          }
1021   
1022          return array(
1023              $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS']            => ($this->index_created()) ? $this->stats['main_posts'] : 0,
1024              $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0,
1025              $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] : 0,
1026          );
1027      }
1028   
1029      /**
1030      * Collects stats that can be displayed on the index maintenance page
1031      */
1032      protected function get_stats()
1033      {
1034          if ($this->index_created())
1035          {
1036              $sql = 'SELECT COUNT(post_id) as total_posts
1037                  FROM ' . POSTS_TABLE;
1038              $result = $this->db->sql_query($sql);
1039              $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts');
1040              $this->db->sql_freeresult($result);
1041   
1042              $sql = 'SELECT COUNT(p.post_id) as main_posts
1043                  FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m
1044                  WHERE p.post_id <= m.max_doc_id
1045                      AND m.counter_id = 1';
1046              $result = $this->db->sql_query($sql);
1047              $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts');
1048              $this->db->sql_freeresult($result);
1049          }
1050      }
1051   
1052      /**
1053      * Returns a list of options for the ACP to display
1054      *
1055      * @return associative array containing template and config variables
1056      */
1057      public function acp()
1058      {
1059          $config_vars = array(
1060              'fulltext_sphinx_data_path' => 'string',
1061              'fulltext_sphinx_host' => 'string',
1062              'fulltext_sphinx_port' => 'string',
1063              'fulltext_sphinx_indexer_mem_limit' => 'int',
1064          );
1065   
1066          $tpl = '
1067          <span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span>
1068          <dl>
1069              <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>
1070              <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>
1071          </dl>
1072          <dl>
1073              <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>
1074              <dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd>
1075          </dl>
1076          <dl>
1077              <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>
1078              <dd><input id="fulltext_sphinx_port" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd>
1079          </dl>
1080          <dl>
1081              <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>
1082              <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>
1083          </dl>
1084          <dl>
1085              <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>
1086              <dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data, ENT_COMPAT) . '</textarea>' : $this->config_file_data) . '</dd>
1087          <dl>
1088          ';
1089   
1090          // These are fields required in the config table
1091          return array(
1092              'tpl'        => $tpl,
1093              'config'    => $config_vars
1094          );
1095      }
1096  }
1097