[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/includes/search/ -> fulltext_mysql.php (source)

   1  <?php
   2  /**
   3  *
   4  * @package search
   5  * @version $Id$
   6  * @copyright (c) 2005 phpBB Group
   7  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
   8  *
   9  */
  10  
  11  /**
  12  * @ignore
  13  */
  14  if (!defined('IN_PHPBB'))
  15  {
  16      exit;
  17  }
  18  
  19  /**
  20  * @ignore
  21  */
  22  include_once($phpbb_root_path . 'includes/search/search.' . $phpEx);
  23  
  24  /**
  25  * fulltext_mysql
  26  * Fulltext search for MySQL
  27  * @package search
  28  */
  29  class fulltext_mysql extends search_backend
  30  {
  31      var $stats = array();
  32      var $word_length = array();
  33      var $split_words = array();
  34      var $search_query;
  35      var $common_words = array();
  36      var $pcre_properties = false;
  37      var $mbstring_regex = false;
  38  
  39  	function fulltext_mysql(&$error)
  40      {
  41          global $config;
  42  
  43          $this->word_length = array('min' => $config['fulltext_mysql_min_word_len'], 'max' => $config['fulltext_mysql_max_word_len']);
  44  
  45          if (version_compare(PHP_VERSION, '5.1.0', '>=') || (version_compare(PHP_VERSION, '5.0.0-dev', '<=') && version_compare(PHP_VERSION, '4.4.0', '>=')))
  46          {
  47              // While this is the proper range of PHP versions, PHP may not be linked with the bundled PCRE lib and instead with an older version
  48              if (@preg_match('/\p{L}/u', 'a') !== false)
  49              {
  50                  $this->pcre_properties = true;
  51              }
  52          }
  53  
  54          if (function_exists('mb_ereg'))
  55          {
  56              $this->mbstring_regex = true;
  57              mb_regex_encoding('UTF-8');
  58          }
  59  
  60          $error = false;
  61      }
  62  
  63      /**
  64      * Checks for correct MySQL version and stores min/max word length in the config
  65      */
  66  	function init()
  67      {
  68          global $db, $user;
  69  
  70          if ($db->sql_layer != 'mysql4' && $db->sql_layer != 'mysqli')
  71          {
  72              return $user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_VERSION'];
  73          }
  74  
  75          $result = $db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\'');
  76          $info = $db->sql_fetchrow($result);
  77          $db->sql_freeresult($result);
  78  
  79          $engine = '';
  80          if (isset($info['Engine']))
  81          {
  82              $engine = $info['Engine'];
  83          }
  84          else if (isset($info['Type']))
  85          {
  86              $engine = $info['Type'];
  87          }
  88  
  89          $fulltext_supported =
  90              $engine === 'MyISAM' ||
  91              // FULLTEXT is supported on InnoDB since MySQL 5.6.4 according to
  92              // http://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html
  93              $engine === 'InnoDB' &&
  94              phpbb_version_compare($db->sql_server_info(true), '5.6.4', '>=');
  95  
  96          if (!$fulltext_supported)
  97          {
  98              return $user->lang['FULLTEXT_MYSQL_NOT_SUPPORTED'];
  99          }
 100  
 101          $sql = 'SHOW VARIABLES
 102              LIKE \'ft\_%\'';
 103          $result = $db->sql_query($sql);
 104  
 105          $mysql_info = array();
 106          while ($row = $db->sql_fetchrow($result))
 107          {
 108              $mysql_info[$row['Variable_name']] = $row['Value'];
 109          }
 110          $db->sql_freeresult($result);
 111  
 112          set_config('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']);
 113          set_config('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']);
 114  
 115          return false;
 116      }
 117  
 118      /**
 119      * Splits keywords entered by a user into an array of words stored in $this->split_words
 120      * Stores the tidied search query in $this->search_query
 121      *
 122      * @param string &$keywords Contains the keyword as entered by the user
 123      * @param string $terms is either 'all' or 'any'
 124      * @return bool false if no valid keywords were found and otherwise true
 125      */
 126  	function split_keywords(&$keywords, $terms)
 127      {
 128          global $config, $user;
 129  
 130          if ($terms == 'all')
 131          {
 132              $match        = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#');
 133              $replace    = array(' +', ' |', ' -', ' +', ' -', ' |');
 134  
 135              $keywords = preg_replace($match, $replace, $keywords);
 136          }
 137  
 138          // Filter out as above
 139          $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords)));
 140  
 141          // Split words
 142          if ($this->pcre_properties)
 143          {
 144              $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords)));
 145          }
 146          else if ($this->mbstring_regex)
 147          {
 148              $split_keywords = mb_ereg_replace('([^\w\'*"()])', '\\1\\1', str_replace('\'\'', '\' \'', trim($split_keywords)));
 149          }
 150          else
 151          {
 152              $split_keywords = preg_replace('#([^\w\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords)));
 153          }
 154  
 155          if ($this->pcre_properties)
 156          {
 157              $matches = array();
 158              preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches);
 159              $this->split_words = $matches[1];
 160          }
 161          else if ($this->mbstring_regex)
 162          {
 163              mb_ereg_search_init($split_keywords, '(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)');
 164  
 165              while (($word = mb_ereg_search_regs()))
 166              {
 167                  $this->split_words[] = $word[1];
 168              }
 169          }
 170          else
 171          {
 172              $matches = array();
 173              preg_match_all('#(?:[^\w*"()]|^)([+\-|]?(?:[\w*"()]+\'?)*[\w*"()])(?:[^\w*"()]|$)#u', $split_keywords, $matches);
 174              $this->split_words = $matches[1];
 175          }
 176  
 177          // We limit the number of allowed keywords to minimize load on the database
 178          if ($config['max_num_search_keywords'] && sizeof($this->split_words) > $config['max_num_search_keywords'])
 179          {
 180              trigger_error($user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $config['max_num_search_keywords'], sizeof($this->split_words)));
 181          }
 182  
 183          // to allow phrase search, we need to concatenate quoted words
 184          $tmp_split_words = array();
 185          $phrase = '';
 186          foreach ($this->split_words as $word)
 187          {
 188              if ($phrase)
 189              {
 190                  $phrase .= ' ' . $word;
 191                  if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1)
 192                  {
 193                      $tmp_split_words[] = $phrase;
 194                      $phrase = '';
 195                  }
 196              }
 197              else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1)
 198              {
 199                  $phrase = $word;
 200              }
 201              else
 202              {
 203                  $tmp_split_words[] = $word . ' ';
 204              }
 205          }
 206          if ($phrase)
 207          {
 208              $tmp_split_words[] = $phrase;
 209          }
 210  
 211          $this->split_words = $tmp_split_words;
 212  
 213          unset($tmp_split_words);
 214          unset($phrase);
 215  
 216          foreach ($this->split_words as $i => $word)
 217          {
 218              $clean_word = preg_replace('#^[+\-|"]#', '', $word);
 219  
 220              // check word length
 221              $clean_len = utf8_strlen(str_replace('*', '', $clean_word));
 222              if (($clean_len < $config['fulltext_mysql_min_word_len']) || ($clean_len > $config['fulltext_mysql_max_word_len']))
 223              {
 224                  $this->common_words[] = $word;
 225                  unset($this->split_words[$i]);
 226              }
 227          }
 228  
 229          if ($terms == 'any')
 230          {
 231              $this->search_query = '';
 232              foreach ($this->split_words as $word)
 233              {
 234                  if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0))
 235                  {
 236                      $word = substr($word, 1);
 237                  }
 238                  $this->search_query .= $word . ' ';
 239              }
 240          }
 241          else
 242          {
 243              $this->search_query = '';
 244              foreach ($this->split_words as $word)
 245              {
 246                  if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0))
 247                  {
 248                      $this->search_query .= $word . ' ';
 249                  }
 250                  else if (strpos($word, '|') === 0)
 251                  {
 252                      $this->search_query .= substr($word, 1) . ' ';
 253                  }
 254                  else
 255                  {
 256                      $this->search_query .= '+' . $word . ' ';
 257                  }
 258              }
 259          }
 260  
 261          $this->search_query = utf8_htmlspecialchars($this->search_query);
 262  
 263          if ($this->search_query)
 264          {
 265              $this->split_words = array_values($this->split_words);
 266              sort($this->split_words);
 267              return true;
 268          }
 269          return false;
 270      }
 271  
 272      /**
 273      * Turns text into an array of words
 274      */
 275  	function split_message($text)
 276      {
 277          global $config;
 278  
 279          // Split words
 280          if ($this->pcre_properties)
 281          {
 282              $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text)));
 283          }
 284          else if ($this->mbstring_regex)
 285          {
 286              $text = mb_ereg_replace('([^\w\'*])', '\\1\\1', str_replace('\'\'', '\' \'', trim($text)));
 287          }
 288          else
 289          {
 290              $text = preg_replace('#([^\w\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text)));
 291          }
 292  
 293          if ($this->pcre_properties)
 294          {
 295              $matches = array();
 296              preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches);
 297              $text = $matches[1];
 298          }
 299          else if ($this->mbstring_regex)
 300          {
 301              mb_ereg_search_init($text, '(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)');
 302  
 303              $text = array();
 304              while (($word = mb_ereg_search_regs()))
 305              {
 306                  $text[] = $word[1];
 307              }
 308          }
 309          else
 310          {
 311              $matches = array();
 312              preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#u', $text, $matches);
 313              $text = $matches[1];
 314          }
 315  
 316          // remove too short or too long words
 317          $text = array_values($text);
 318          for ($i = 0, $n = sizeof($text); $i < $n; $i++)
 319          {
 320              $text[$i] = trim($text[$i]);
 321              if (utf8_strlen($text[$i]) < $config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $config['fulltext_mysql_max_word_len'])
 322              {
 323                  unset($text[$i]);
 324              }
 325          }
 326  
 327          return array_values($text);
 328      }
 329  
 330      /**
 331      * Performs a search on keywords depending on display specific params. You have to run split_keywords() first.
 332      *
 333      * @param    string        $type                contains either posts or topics depending on what should be searched for
 334      * @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)
 335      * @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)
 336      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 337      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 338      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 339      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 340      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 341      * @param    array        $m_approve_fid_ary    specifies an array of forum ids in which the searcher is allowed to view unapproved posts
 342      * @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
 343      * @param    array        $author_ary            an array of author ids if the author should be ignored during the search the array is empty
 344      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 345      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 346      * @param    int            $start                indicates the first index of the page
 347      * @param    int            $per_page            number of ids each page is supposed to contain
 348      * @return    boolean|int                        total number of results
 349      *
 350      * @access    public
 351      */
 352  	function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
 353      {
 354          global $config, $db;
 355  
 356          // No keywords? No posts.
 357          if (!$this->search_query)
 358          {
 359              return false;
 360          }
 361  
 362          // generate a search_key from all the options to identify the results
 363          $search_key = md5(implode('#', array(
 364              implode(', ', $this->split_words),
 365              $type,
 366              $fields,
 367              $terms,
 368              $sort_days,
 369              $sort_key,
 370              $topic_id,
 371              implode(',', $ex_fid_ary),
 372              implode(',', $m_approve_fid_ary),
 373              implode(',', $author_ary)
 374          )));
 375  
 376          // try reading the results from cache
 377          $result_count = 0;
 378          if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
 379          {
 380              return $result_count;
 381          }
 382  
 383          $id_ary = array();
 384  
 385          $join_topic = ($type == 'posts') ? false : true;
 386  
 387          // Build sql strings for sorting
 388          $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC');
 389          $sql_sort_table = $sql_sort_join = '';
 390  
 391          switch ($sql_sort[0])
 392          {
 393              case 'u':
 394                  $sql_sort_table    = USERS_TABLE . ' u, ';
 395                  $sql_sort_join    = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster ';
 396              break;
 397  
 398              case 't':
 399                  $join_topic = true;
 400              break;
 401  
 402              case 'f':
 403                  $sql_sort_table    = FORUMS_TABLE . ' f, ';
 404                  $sql_sort_join    = ' AND f.forum_id = p.forum_id ';
 405              break;
 406          }
 407  
 408          // Build some display specific sql strings
 409          switch ($fields)
 410          {
 411              case 'titleonly':
 412                  $sql_match = 'p.post_subject';
 413                  $sql_match_where = ' AND p.post_id = t.topic_first_post_id';
 414                  $join_topic = true;
 415              break;
 416  
 417              case 'msgonly':
 418                  $sql_match = 'p.post_text';
 419                  $sql_match_where = '';
 420              break;
 421  
 422              case 'firstpost':
 423                  $sql_match = 'p.post_subject, p.post_text';
 424                  $sql_match_where = ' AND p.post_id = t.topic_first_post_id';
 425                  $join_topic = true;
 426              break;
 427  
 428              default:
 429                  $sql_match = 'p.post_subject, p.post_text';
 430                  $sql_match_where = '';
 431              break;
 432          }
 433  
 434          if (!sizeof($m_approve_fid_ary))
 435          {
 436              $m_approve_fid_sql = ' AND p.post_approved = 1';
 437          }
 438          else if ($m_approve_fid_ary === array(-1))
 439          {
 440              $m_approve_fid_sql = '';
 441          }
 442          else
 443          {
 444              $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';
 445          }
 446  
 447          $sql_select            = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : '';
 448          $sql_select            = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id';
 449          $sql_from            = ($join_topic) ? TOPICS_TABLE . ' t, ' : '';
 450          $field                = ($type == 'posts') ? 'post_id' : 'topic_id';
 451          if (sizeof($author_ary) && $author_name)
 452          {
 453              // first one matches post of registered users, second one guests and deleted users
 454              $sql_author = ' AND (' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')';
 455          }
 456          else if (sizeof($author_ary))
 457          {
 458              $sql_author = ' AND ' . $db->sql_in_set('p.poster_id', $author_ary);
 459          }
 460          else
 461          {
 462              $sql_author = '';
 463          }
 464  
 465          $sql_where_options = $sql_sort_join;
 466          $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : '';
 467          $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : '';
 468          $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '';
 469          $sql_where_options .= $m_approve_fid_sql;
 470          $sql_where_options .= $sql_author;
 471          $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';
 472          $sql_where_options .= $sql_match_where;
 473  
 474          $sql = "SELECT $sql_select
 475              FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p
 476              WHERE MATCH ($sql_match) AGAINST ('" . $db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE)
 477                  $sql_where_options
 478              ORDER BY $sql_sort";
 479          $result = $db->sql_query_limit($sql, $config['search_block_size'], $start);
 480  
 481          while ($row = $db->sql_fetchrow($result))
 482          {
 483              $id_ary[] = (int) $row[$field];
 484          }
 485          $db->sql_freeresult($result);
 486  
 487          $id_ary = array_unique($id_ary);
 488  
 489          if (!sizeof($id_ary))
 490          {
 491              return false;
 492          }
 493  
 494          // if the total result count is not cached yet, retrieve it from the db
 495          if (!$result_count)
 496          {
 497              $sql = 'SELECT FOUND_ROWS() as result_count';
 498              $result = $db->sql_query($sql);
 499              $result_count = (int) $db->sql_fetchfield('result_count');
 500              $db->sql_freeresult($result);
 501  
 502              if (!$result_count)
 503              {
 504                  return false;
 505              }
 506          }
 507  
 508          // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page
 509          $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir);
 510          $id_ary = array_slice($id_ary, 0, (int) $per_page);
 511  
 512          return $result_count;
 513      }
 514  
 515      /**
 516      * Performs a search on an author's posts without caring about message contents. Depends on display specific params
 517      *
 518      * @param    string        $type                contains either posts or topics depending on what should be searched for
 519      * @param    boolean        $firstpost_only        if true, only topic starting posts will be considered
 520      * @param    array        $sort_by_sql        contains SQL code for the ORDER BY part of a query
 521      * @param    string        $sort_key            is the key of $sort_by_sql for the selected sorting
 522      * @param    string        $sort_dir            is either a or d representing ASC and DESC
 523      * @param    string        $sort_days            specifies the maximum amount of days a post may be old
 524      * @param    array        $ex_fid_ary            specifies an array of forum ids which should not be searched
 525      * @param    array        $m_approve_fid_ary    specifies an array of forum ids in which the searcher is allowed to view unapproved posts
 526      * @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
 527      * @param    array        $author_ary            an array of author ids
 528      * @param    string        $author_name        specifies the author match, when ANONYMOUS is also a search-match
 529      * @param    array        &$id_ary            passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
 530      * @param    int            $start                indicates the first index of the page
 531      * @param    int            $per_page            number of ids each page is supposed to contain
 532      * @return    boolean|int                        total number of results
 533      *
 534      * @access    public
 535      */
 536  	function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $m_approve_fid_ary, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
 537      {
 538          global $config, $db;
 539  
 540          // No author? No posts.
 541          if (!sizeof($author_ary))
 542          {
 543              return 0;
 544          }
 545  
 546          // generate a search_key from all the options to identify the results
 547          $search_key = md5(implode('#', array(
 548              '',
 549              $type,
 550              ($firstpost_only) ? 'firstpost' : '',
 551              '',
 552              '',
 553              $sort_days,
 554              $sort_key,
 555              $topic_id,
 556              implode(',', $ex_fid_ary),
 557              implode(',', $m_approve_fid_ary),
 558              implode(',', $author_ary),
 559              $author_name,
 560          )));
 561  
 562          // try reading the results from cache
 563          $result_count = 0;
 564          if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE)
 565          {
 566              return $result_count;
 567          }
 568  
 569          $id_ary = array();
 570  
 571          // Create some display specific sql strings
 572          if ($author_name)
 573          {
 574              // first one matches post of registered users, second one guests and deleted users
 575              $sql_author = '(' . $db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')';
 576          }
 577          else
 578          {
 579              $sql_author = $db->sql_in_set('p.poster_id', $author_ary);
 580          }
 581          $sql_fora        = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : '';
 582          $sql_topic_id    = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : '';
 583          $sql_time        = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : '';
 584          $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : '';
 585  
 586          // Build sql strings for sorting
 587          $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC');
 588          $sql_sort_table = $sql_sort_join = '';
 589          switch ($sql_sort[0])
 590          {
 591              case 'u':
 592                  $sql_sort_table    = USERS_TABLE . ' u, ';
 593                  $sql_sort_join    = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster ';
 594              break;
 595  
 596              case 't':
 597                  $sql_sort_table    = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : '';
 598                  $sql_sort_join    = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : '';
 599              break;
 600  
 601              case 'f':
 602                  $sql_sort_table    = FORUMS_TABLE . ' f, ';
 603                  $sql_sort_join    = ' AND f.forum_id = p.forum_id ';
 604              break;
 605          }
 606  
 607          if (!sizeof($m_approve_fid_ary))
 608          {
 609              $m_approve_fid_sql = ' AND p.post_approved = 1';
 610          }
 611          else if ($m_approve_fid_ary == array(-1))
 612          {
 613              $m_approve_fid_sql = '';
 614          }
 615          else
 616          {
 617              $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')';
 618          }
 619  
 620          // If the cache was completely empty count the results
 621          $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS ';
 622  
 623          // Build the query for really selecting the post_ids
 624          if ($type == 'posts')
 625          {
 626              $sql = "SELECT {$calc_results}p.post_id
 627                  FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . "
 628                  WHERE $sql_author
 629                      $sql_topic_id
 630                      $sql_firstpost
 631                      $m_approve_fid_sql
 632                      $sql_fora
 633                      $sql_sort_join
 634                      $sql_time
 635                  ORDER BY $sql_sort";
 636              $field = 'post_id';
 637          }
 638          else
 639          {
 640              $sql = "SELECT {$calc_results}t.topic_id
 641                  FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p
 642                  WHERE $sql_author
 643                      $sql_topic_id
 644                      $sql_firstpost
 645                      $m_approve_fid_sql
 646                      $sql_fora
 647                      AND t.topic_id = p.topic_id
 648                      $sql_sort_join
 649                      $sql_time
 650                  GROUP BY t.topic_id
 651                  ORDER BY $sql_sort";
 652              $field = 'topic_id';
 653          }
 654  
 655          // Only read one block of posts from the db and then cache it
 656          $result = $db->sql_query_limit($sql, $config['search_block_size'], $start);
 657  
 658          while ($row = $db->sql_fetchrow($result))
 659          {
 660              $id_ary[] = (int) $row[$field];
 661          }
 662          $db->sql_freeresult($result);
 663  
 664          // retrieve the total result count if needed
 665          if (!$result_count)
 666          {
 667              $sql = 'SELECT FOUND_ROWS() as result_count';
 668              $result = $db->sql_query($sql);
 669              $result_count = (int) $db->sql_fetchfield('result_count');
 670              $db->sql_freeresult($result);
 671  
 672              if (!$result_count)
 673              {
 674                  return false;
 675              }
 676          }
 677  
 678          if (sizeof($id_ary))
 679          {
 680              $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir);
 681              $id_ary = array_slice($id_ary, 0, $per_page);
 682  
 683              return $result_count;
 684          }
 685          return false;
 686      }
 687  
 688      /**
 689      * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated.
 690      *
 691      * @param string $mode contains the post mode: edit, post, reply, quote ...
 692      */
 693  	function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
 694      {
 695          global $db;
 696  
 697          // Split old and new post/subject to obtain array of words
 698          $split_text = $this->split_message($message);
 699          $split_title = ($subject) ? $this->split_message($subject) : array();
 700  
 701          $words = array_unique(array_merge($split_text, $split_title));
 702  
 703          unset($split_text);
 704          unset($split_title);
 705  
 706          // destroy cached search results containing any of the words removed or added
 707          $this->destroy_cache($words, array($poster_id));
 708  
 709          unset($words);
 710      }
 711  
 712      /**
 713      * Destroy cached results, that might be outdated after deleting a post
 714      */
 715  	function index_remove($post_ids, $author_ids, $forum_ids)
 716      {
 717          $this->destroy_cache(array(), array_unique($author_ids));
 718      }
 719  
 720      /**
 721      * Destroy old cache entries
 722      */
 723  	function tidy()
 724      {
 725          global $db, $config;
 726  
 727          // destroy too old cached search results
 728          $this->destroy_cache(array());
 729  
 730          set_config('search_last_gc', time(), true);
 731      }
 732  
 733      /**
 734      * Create fulltext index
 735      */
 736  	function create_index($acp_module, $u_action)
 737      {
 738          global $db;
 739  
 740          // Make sure we can actually use MySQL with fulltext indexes
 741          if ($error = $this->init())
 742          {
 743              return $error;
 744          }
 745  
 746          if (empty($this->stats))
 747          {
 748              $this->get_stats();
 749          }
 750  
 751          $alter = array();
 752  
 753          if (!isset($this->stats['post_subject']))
 754          {
 755              if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>='))
 756              {
 757                  $alter[] = 'MODIFY post_subject varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL';
 758              }
 759              else
 760              {
 761                  $alter[] = 'MODIFY post_subject text NOT NULL';
 762              }
 763              $alter[] = 'ADD FULLTEXT (post_subject)';
 764          }
 765  
 766          if (!isset($this->stats['post_text']))
 767          {
 768              if ($db->sql_layer == 'mysqli' || version_compare($db->sql_server_info(true), '4.1.3', '>='))
 769              {
 770                  $alter[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL';
 771              }
 772              else
 773              {
 774                  $alter[] = 'MODIFY post_text mediumtext NOT NULL';
 775              }
 776              $alter[] = 'ADD FULLTEXT (post_text)';
 777          }
 778  
 779          if (!isset($this->stats['post_content']))
 780          {
 781              $alter[] = 'ADD FULLTEXT post_content (post_subject, post_text)';
 782          }
 783  
 784          if (sizeof($alter))
 785          {
 786              $db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));
 787          }
 788  
 789          $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
 790  
 791          return false;
 792      }
 793  
 794      /**
 795      * Drop fulltext index
 796      */
 797  	function delete_index($acp_module, $u_action)
 798      {
 799          global $db;
 800  
 801          // Make sure we can actually use MySQL with fulltext indexes
 802          if ($error = $this->init())
 803          {
 804              return $error;
 805          }
 806  
 807          if (empty($this->stats))
 808          {
 809              $this->get_stats();
 810          }
 811  
 812          $alter = array();
 813  
 814          if (isset($this->stats['post_subject']))
 815          {
 816              $alter[] = 'DROP INDEX post_subject';
 817          }
 818  
 819          if (isset($this->stats['post_text']))
 820          {
 821              $alter[] = 'DROP INDEX post_text';
 822          }
 823  
 824          if (isset($this->stats['post_content']))
 825          {
 826              $alter[] = 'DROP INDEX post_content';
 827          }
 828  
 829          if (sizeof($alter))
 830          {
 831              $db->sql_query('ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter));
 832          }
 833  
 834          $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE);
 835  
 836          return false;
 837      }
 838  
 839      /**
 840      * Returns true if both FULLTEXT indexes exist
 841      */
 842  	function index_created()
 843      {
 844          if (empty($this->stats))
 845          {
 846              $this->get_stats();
 847          }
 848  
 849          return (isset($this->stats['post_text']) && isset($this->stats['post_subject']) && isset($this->stats['post_content'])) ? true : false;
 850      }
 851  
 852      /**
 853      * Returns an associative array containing information about the indexes
 854      */
 855  	function index_stats()
 856      {
 857          global $user;
 858  
 859          if (empty($this->stats))
 860          {
 861              $this->get_stats();
 862          }
 863  
 864          return array(
 865              $user->lang['FULLTEXT_MYSQL_TOTAL_POSTS']            => ($this->index_created()) ? $this->stats['total_posts'] : 0,
 866          );
 867      }
 868  
 869  	function get_stats()
 870      {
 871          global $db;
 872  
 873          if (strpos($db->sql_layer, 'mysql') === false)
 874          {
 875              $this->stats = array();
 876              return;
 877          }
 878  
 879          $sql = 'SHOW INDEX
 880              FROM ' . POSTS_TABLE;
 881          $result = $db->sql_query($sql);
 882  
 883          while ($row = $db->sql_fetchrow($result))
 884          {
 885              // deal with older MySQL versions which didn't use Index_type
 886              $index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment'];
 887  
 888              if ($index_type == 'FULLTEXT')
 889              {
 890                  if ($row['Key_name'] == 'post_text')
 891                  {
 892                      $this->stats['post_text'] = $row;
 893                  }
 894                  else if ($row['Key_name'] == 'post_subject')
 895                  {
 896                      $this->stats['post_subject'] = $row;
 897                  }
 898                  else if ($row['Key_name'] == 'post_content')
 899                  {
 900                      $this->stats['post_content'] = $row;
 901                  }
 902              }
 903          }
 904          $db->sql_freeresult($result);
 905  
 906          $this->stats['total_posts'] = empty($this->stats) ? 0 : $db->get_estimated_row_count(POSTS_TABLE);
 907      }
 908  
 909      /**
 910      * Display a note, that UTF-8 support is not available with certain versions of PHP
 911      */
 912  	function acp()
 913      {
 914          global $user, $config;
 915  
 916          $tpl = '
 917          <dl>
 918              <dt><label>' . $user->lang['FULLTEXT_MYSQL_PCRE'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_PCRE_EXPLAIN'] . '</span></dt>
 919              <dd>' . (($this->pcre_properties) ? $user->lang['YES'] : $user->lang['NO']) . ' (PHP ' . PHP_VERSION . ')</dd>
 920          </dl>
 921          <dl>
 922              <dt><label>' . $user->lang['FULLTEXT_MYSQL_MBSTRING'] . '</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MBSTRING_EXPLAIN'] . '</span></dt>
 923              <dd>' . (($this->mbstring_regex) ? $user->lang['YES'] : $user->lang['NO']). '</dd>
 924          </dl>
 925          <dl>
 926              <dt><label>' . $user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt>
 927              <dd>' . $config['fulltext_mysql_min_word_len'] . '</dd>
 928          </dl>
 929          <dl>
 930              <dt><label>' . $user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt>
 931              <dd>' . $config['fulltext_mysql_max_word_len'] . '</dd>
 932          </dl>
 933          ';
 934  
 935          // These are fields required in the config table
 936          return array(
 937              'tpl'        => $tpl,
 938              'config'    => array()
 939          );
 940      }
 941  }
 942  
 943  ?>


Generated: Wed Oct 2 15:03:47 2013 Cross-referenced by PHPXref 0.7.1