diff --git a/phpBB/includes/search/fulltext_mysql.php b/phpBB/includes/search/fulltext_mysql.php index 6f0b1b0c09..7ad156a60d 100644 --- a/phpBB/includes/search/fulltext_mysql.php +++ b/phpBB/includes/search/fulltext_mysql.php @@ -8,198 +8,530 @@ * */ +/** +* @ignore +*/ +include($phpbb_root_path . 'includes/search/search.' . $phpEx); + /** * @package search * fulltext_mysql -* Search indexing for MySQL +* Fulltext search for MySQL */ -class fulltext_mysql +class fulltext_mysql extends search_backend { - var $version = 4; - var $split_words = array(); - var $common_words = array(); - var $old_split_words = array(); - function fulltext_mysql(&$error) { global $db; - $result = $db->sql_query('SELECT VERSION() AS mysql_version'); + /** + * @todo Move some of this to ACP + * @todo Add SET SESSION ft_stopword_file = '' to ACP? + */ + + $result = $db->sql_query('SELECT VERSION() AS mysql_version', 7200); $version = ($row = $db->sql_fetchrow($result)) ? $row['mysql_version'] : ''; $db->sql_freeresult($result); - // Need to check for fulltext indexes ... maybe all of thise is best left in acp? + $sql = 'SHOW VARIABLES + LIKE \'ft\_%\''; + $result = $db->sql_query($sql, 7200); + + while ($row = $db->sql_fetchrow($result)) + { + $this->mysql_info[$row['Variable_name']] = $row['Value']; + } + $db->sql_freeresult($result); $error = (!preg_match('#^4|5|6#s', $version)) ? true : false; } - function search($type, &$fields, &$terms, &$fid_ary, &$keywords, &$author, &$pid_ary, $sort_days) + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * + * @param string $keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return false if no valid keywords were found and otherwise true + */ + function split_keywords(&$keywords, $terms) { - global $phpbb_root_path, $phpEx, $config, $db, $user, $SID; + global $config; - // Are we looking for words - if ($keywords) + $this->get_ignore_words(); + $this->get_synonyms(); + + if ($terms == 'all') { - $author = ($author) ? ' AND ' . $author : ''; - - $this->split_words = $this->common_words = array(); - $drop_char_match = array('^', '$', ';', '#', '&', '(', ')', '<', '>', '`', '\'', '"', '|', ',', '@', '_', '?', '%', '~', '.', '[', ']', '{', '}', ':', '\\', '/', '=', '\'', '!', '*'); - $drop_char_replace = array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', '', ' ', ' ', ' ', ' ', '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' , ' ', ' ', ' ', ' ', ' '); - - if ($fp = @fopen($user->lang_path . '/search_stopwords.txt', 'rb')) - { - $stopwords = explode("\n", str_replace("\r\n", "\n", fread($fp, filesize($user->lang_path . '/search_stopwords.txt')))); - } - fclose($fp); - - if ($fp = @fopen($user->lang_path . '/search_synonyms.txt', 'rb')) - { - preg_match_all('#^(.*?) (.*?)$#ms', fread($fp, filesize($user->lang_path . '/search_synonyms.txt')), $match); - $replace_synonym = &$match[1]; - $match_synonym = &$match[2]; - } - fclose($fp); - $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#'); $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); $keywords = preg_replace($match, $replace, $keywords); - - $match = array(); - // Comments for hardcoded bbcode elements (urls, smilies, html) - $match[] = '#(.*?)#is'; - // New lines, carriage returns - $match[] = "#[\n\r]+#"; - // NCRs like   etc. - $match[] = '#(&|&)[\#a-z0-9]+?;#i'; - // BBcode - $match[] = '#\[\/?[a-z\*\+\-]+(=.*)?(\:?[0-9a-z]{5,})\]#'; - - // Filter out as above - $keywords = preg_replace($match, ' ', strtolower(trim($keywords))); - $keywords = str_replace($drop_char_match, $drop_char_replace, $keywords); - - // Split words - $this->split_words = explode(' ', preg_replace('#\s+#', ' ', $keywords)); - - if (sizeof($stopwords)) - { - $this->common_words = array_intersect($this->split_words, $stopwords); - $this->split_words = array_diff($this->split_words, $stopwords); - } - - if (sizeof($replace_synonym)) - { - $this->split_words = str_replace($replace_synonym, $match_synonym, $this->split_words); - } } - if ($this->old_split_words && sizeof($this->old_split_words)) + $match = array(); + // New lines, carriage returns + $match[] = "#[\n\r]+#"; + // NCRs like   etc. + $match[] = '#(&|&)[\#a-z0-9]+?;#i'; + + // Filter out as above + $keywords = preg_replace($match, ' ', trim($keywords)); + + // Split words + $split_keywords = preg_replace('#([^\w\'*])#', '$1$1', str_replace('\'\'', '\' \'', trim($keywords))); + $matches = array(); + preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#', $split_keywords, $matches); + $this->split_words = $matches[1]; + + if (sizeof($this->ignore_words)) { - $this->split_words = (sizeof($this->split_words)) ? array_diff($this->split_words, $this->old_split_words) : $this->old_split_words; + $this->common_words = array_intersect($this->split_words, $this->ignore_words); + $this->split_words = array_diff($this->split_words, $this->ignore_words); + } + + if (sizeof($this->replace_synonym)) + { + $this->split_words = str_replace($this->replace_synonym, $this->match_synonym, $this->split_words); + } + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|]#', '', $word); + + // check word length + $clean_len = strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $this->mysql_info['ft_min_word_len']) || ($clean_len > $this->mysql_info['ft_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } } if (sizeof($this->split_words)) { - // Build some display specific variable strings - $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id'; - $sql_from = ($type == 'posts') ? '' : TOPICS_TABLE . ' t, '; - $sql_fora = (sizeof($fid_ary)) ? ' AND p.forum_id IN (' . implode(',', $fid_ary) . ')' : ''; - $sql_author = ($author) ? 'AND p.poster_id = ' . $author : ''; - $sql_time = ($sort_days) ? 'AND p.post_time >= ' . ($current_time - ($sort_days * 86400)) : ''; - $sql_topic = ($type == 'posts') ? '' : 'AND t.topic_id = p.topic_id'; + $this->split_words = array_values($this->split_words); + return true; + } + return false; + } - switch ($fields) + /** + * Turns text into an array of words that can be stored in the word list table + */ + function split_message($text) + { + global $config; + + $this->get_ignore_words(); + $this->get_synonyms(); + + // Split words + $text = preg_replace('#([^\w\'*])#', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\w*]|^)([+\-|]?(?:[\w*]+\'?)*[\w*])(?:[^\w*]|$)#', $text, $matches); + $text = $matches[1]; + + if (sizeof($this->ignore_words)) + { + $stopped_words = array_intersect($text, $this->ignore_words); + $text = array_diff($text, $this->ignore_words); + } + + if (sizeof($this->replace_synonym)) + { + $text = str_replace($this->replace_synonym, $this->match_synonym, $text); + } + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = sizeof($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (strlen($text[$i]) < $this->mysql_info['ft_min_word_len'] || strlen($text[$i]) > $this->mysql_info['ft_max_word_len']) { - case 'titleonly': - $sql_match = 'p.post_subject'; - break; - case 'msgonly': - $sql_match = 'p.post_text'; - break; - default: - $sql_match = 'p.post_text, p.post_subject'; + unset($text[$i]); } + } - // Are we searching within an existing search set? Yes, then include the old ids - $sql_find_in = (sizeof($pid_ary)) ? 'AND ' . (($type == 'topics') ? 't.topic_id' : 'p.post_id') . ' IN (' . implode(', ', $pid_ary) . ')' : ''; + return array_values($text); + } - $words = array(); - if ($terms == 'all') + /** + * Performs a search on keywords depending on display specific params. + * + * @param array $id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return total number of results + */ + function keyword_search($type, &$fields, &$terms, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No keywords? No posts. + if (!sizeof($this->split_words)) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + implode(',', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $author_ary) + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + } + + $sql_select = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : ''; + $sql_select = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; + $sql_author = (sizeof($author_ary) == 1) ? ' = ' . $author_ary[0] : 'IN (' . implode(',', $author_ary) . ')'; + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (sizeof($ex_fid_ary)) ? ' AND p.forum_id NOT IN (' . implode(',', $ex_fid_ary) . ')' : ''; + $sql_where_options .= (sizeof($author_ary)) ? ' AND p.poster_id ' . $sql_author : ''; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + // split the words into words that have to be included or must not be included and optional words + $words = $any_words = array(); + if ($terms == 'all') + { + foreach ($this->split_words as $id => $word) { - foreach ($this->split_words as $id => $word) + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0)) { - $words[] = (strpos($word, '+') === 0) ? $word : '+' . $word; + $words[] = $word; + } + else if (strpos($word, '|') === 0) + { + $any_words[] = substr($word, 1); + } + else + { + $words[] = '+' . $word; } } - else - { - $words = $this->split_words; - } - - $sql = "SELECT $sql_select - FROM $sql_from" . POSTS_TABLE . " p - WHERE MATCH ($sql_match) AGAINST ('" . implode(' ', $words) . "' IN BOOLEAN MODE) - $sql_topic - $sql_find_in - $sql_fora - $sql_author - $sql_time"; - $result = $db->sql_query_limit($sql, 1000); - - if ($db->sql_numrows() > 999) - { - trigger_error($user->lang['TOO_MANY_SEARCH_RESULTS']); - } - - $pid_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - $pid_ary[] = ($type == 'topics') ? $row['topic_id'] : $row['post_id']; - } - $db->sql_freeresult($result); - - $pid_ary = array_unique($pid_ary); - - if (!sizeof($pid_ary)) - { - trigger_error($user->lang['NO_SEARCH_RESULTS']); - } } - else if ($author) + else { - $sql_author = ($author) ? 'p.poster_id = ' . $author : ''; - $sql_fora = (sizeof($fid_ary)) ? ' AND p.forum_id IN (' . implode(',', $fid_ary) . ')' : ''; - - if ($type == 'posts') + foreach ($this->split_words as $id => $word) { - $sql = 'SELECT p.post_id - FROM ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_fora"; - $field = 'post_id'; + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $words[] = substr($word, 1); + } + else + { + $words[] = $word; + } } - else - { - $sql = 'SELECT t.topic_id - FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p - WHERE $sql_author - $sql_fora - AND t.topic_id = p.topic_id - GROUP BY t.topic_id"; - $field = 'topic_id'; - } - $result = $db->sql_query_limit($sql, 1000); + } - while ($row = $db->sql_fetchrow($result)) + // Get the ids for the current result block + $any_words = (sizeof($any_words)) ? ' +(' . implode(' ', $any_words) . ')' : ''; + $sql = "SELECT $sql_select + FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p + WHERE MATCH ($sql_match) AGAINST ('" . implode(' ', $words) . $any_words . "' IN BOOLEAN MODE) + $sql_where_options + ORDER BY $sql_sort"; + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = ($type == 'topics') ? $row['topic_id'] : $row['post_id']; + } + $db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + if (!sizeof($id_ary)) + { + return false; + } + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $sql = 'SELECT FOUND_ROWS() as result_count'; + $result = $db->sql_query($sql); + + if (!($result_count = (int) $db->sql_fetchfield('result_count', 0, $result))) { - $pid_ary[] = $row[$field]; + return false; } $db->sql_freeresult($result); } + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param array $id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return total number of results + */ + function author_search($type, &$sort_by_sql, &$sort_key, &$sort_dir, &$sort_days, &$ex_fid_ary, &$topic_id, &$author_ary, &$id_ary, $start, $per_page) + { + global $config, $db; + + // No author? No posts. + if (!sizeof($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key = md5(implode('#', array( + '', + $type, + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + implode(',', $author_ary) + ))); + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + $sql_author = 'p.poster_id ' . ((sizeof($author_ary) > 1) ? 'IN (' . implode(',', $author_ary) . ')' : '= ' . $author_ary[0]); + $sql_fora = (sizeof($ex_fid_ary)) ? ' AND p.forum_id NOT IN (' . implode(',', $ex_fid_ary) . ')' : ''; + $sql_topic_id = (sizeof($ex_fid_ary)) ? ' AND p.forum_id NOT IN (' . implode(',', $ex_fid_ary) . ')' : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts') ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts') ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // If the cache was completely empty count the results + $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS '; + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT {$calc_results}p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . " p + WHERE $sql_author + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT {$calc_results}t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + $sql = 'SELECT FOUND_ROWS() as result_count'; + $result = $db->sql_query($sql); + + if (!($result_count = (int) $db->sql_fetchfield('result_count', 0, $result))) + { + return false; + } + $db->sql_freeresult($result); + } + + if (sizeof($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } return false; } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated. + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + */ + function index($mode, $post_id, &$message, &$subject, $poster_id) + { + global $db; + + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array(); + if ($mode == 'edit') + { + $old_text = array(); + $old_title = array(); + + $sql = 'SELECT post_text, post_subject + FROM ' . POSTS_TABLE . " + WHERE post_id = $post_id"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $old_text = $this->split_message($row['post_text']); + $old_title = $this->split_message($row['post_subject']); + } + $db->sql_freeresult($result); + + $words = array_unique(array_merge( + array_diff($split_text, $old_text), + array_diff($split_title, $old_title), + array_diff($old_text, $split_text), + array_diff($old_title, $split_title) + )); + unset($old_title); + unset($old_text); + } + else + { + $words = array_unique(array_merge($split_text, $split_title)); + } + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + function index_remove($post_ids, $author_ids) + { + $this->destroy_cache(array(), $author_ids); + } + + /** + * Destroy old cache entries + */ + function tidy() + { + global $db, $config; + + // destroy too old cached search results + $this->destroy_cache(array()); + } } ?> \ No newline at end of file diff --git a/phpBB/includes/search/fulltext_phpbb.php b/phpBB/includes/search/fulltext_phpbb.php index 3dadc08466..d9a61d4d6b 100644 --- a/phpBB/includes/search/fulltext_phpbb.php +++ b/phpBB/includes/search/fulltext_phpbb.php @@ -36,8 +36,8 @@ class fulltext_phpbb extends search_backend { global $db, $config; - $drop_char_match = array('^', '$', ';', '#', '&', '(', ')', '<', '>', '`', '\'', '"', ',', '@', '_', '?', '%', '~', '.', '[', ']', '{', '}', ':', '\\', '/', '=', '\'', '!'); - $drop_char_replace = array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', '', ' ', ' ', ' ', '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' , ' ', ' ', ' ', ' '); + $drop_char_match = array('^', '$', ';', '#', '&', '(', ')', '<', '>', '`', '\'', '"', ',', '@', '_', '?', '%', '~', '.', '[', ']', '{', '}', ':', '\\', '/', '=', '!'); + $drop_char_replace = array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '', '', ' ', ' ', ' ', '', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '' , ' ', ' ', ' '); $this->get_ignore_words(); $this->get_synonyms(); @@ -206,7 +206,7 @@ class fulltext_phpbb extends search_backend * Performs a search on keywords depending on display specific params. * * @param array $id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicated the first index of the page + * @param int $start indicates the first index of the page * @param int $per_page number of ids each page is supposed to contain * @return total number of results */ @@ -252,14 +252,17 @@ class fulltext_phpbb extends search_backend case 'u': $sql_sort_table = USERS_TABLE . ' u, '; $sql_sort_join = ' AND u.user_id = p.poster_id '; - break; + break; + case 't': $join_topic = true; - break; + break; + case 'f': $sql_sort_table = FORUMS_TABLE . ' f, '; $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; + break; + } // Build some display specific sql strings @@ -268,14 +271,17 @@ class fulltext_phpbb extends search_backend case 'titleonly': $sql_match = ' AND m.title_match = 1 AND p.post_id = t.topic_first_post_id'; $join_topic = true; - break; + break; + case 'msgonly': $sql_match = ' AND m.title_match = 0'; - break; + break; + case 'firstpost': $sql_match = ' AND p.post_id = t.topic_first_post_id'; $join_topic = true; - break; + break; + default: $sql_match = ''; } @@ -522,7 +528,7 @@ class fulltext_phpbb extends search_backend * Performs a search on an author's posts without caring about message contents. Depends on display specific params * * @param array $id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered - * @param int $start indicated the first index of the page + * @param int $start indicates the first index of the page * @param int $per_page number of ids each page is supposed to contain * @return total number of results */ @@ -572,15 +578,17 @@ class fulltext_phpbb extends search_backend case 'u': $sql_sort_table = USERS_TABLE . ' u, '; $sql_sort_join = ' AND u.user_id = p.poster_id '; - break; + break; + case 't': $sql_sort_table = ($type == 'posts') ? TOPICS_TABLE . ' t, ' : ''; $sql_sort_join = ($type == 'posts') ? ' AND t.topic_id = p.topic_id ' : ''; - break; + break; + case 'f': $sql_sort_table = FORUMS_TABLE . ' f, '; $sql_sort_join = ' AND f.forum_id = p.forum_id '; - break; + break; } // If the cache was completely empty count the results @@ -745,7 +753,7 @@ class fulltext_phpbb extends search_backend $sql = 'INSERT INTO ' . SEARCH_WORD_TABLE . ' (word_text) VALUES ' . implode(', ', preg_replace('#^(.*)$#', '(\'$1\')', $new_words)); $db->sql_query($sql); - break; + break; case 'mysql4': case 'mysqli': @@ -754,7 +762,7 @@ class fulltext_phpbb extends search_backend case 'sqlite': $sql = 'INSERT INTO ' . SEARCH_WORD_TABLE . ' (word_text) ' . implode(' UNION ALL ', preg_replace('#^(.*)$#', "SELECT '\$1'", $new_words)); $db->sql_query($sql); - break; + break; default: foreach ($new_words as $word) @@ -763,7 +771,6 @@ class fulltext_phpbb extends search_backend VALUES ('$word')"; $db->sql_query($sql); } - break; } } unset($new_words); diff --git a/phpBB/search.php b/phpBB/search.php index 25e70e96ba..13f38e6c28 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -99,7 +99,7 @@ if ($keywords || $author || $search_id) { if ($row['forum_password'] && ($row['user_id'] != $user->data['user_id'])) { - $ex_fid_ary[] = $row['forum_id']; + $ex_fid_ary[] = (int) $row['forum_id']; continue; } @@ -109,7 +109,7 @@ if ($keywords || $author || $search_id) { if (in_array($row['forum_id'], $search_forum) && $row['right_id'] > $right_id) { - $right_id = $row['right_id']; + $right_id = (int) $row['right_id']; } else if ($row['right_id'] < $right_id) { @@ -119,7 +119,7 @@ if ($keywords || $author || $search_id) if (!in_array($row['forum_id'], $search_forum)) { - $ex_fid_ary[] = $row['forum_id']; + $ex_fid_ary[] = (int) $row['forum_id']; $reset_search_forum = false; } } @@ -210,9 +210,15 @@ if ($keywords || $author || $search_id) } } + if (!$keywords && sizeof($author_id_ary)) + { + // default to showing results as posts when performing an author search + $show_results = ($topic_id) ? 'posts' : request_var('sr', 'posts'); + } + // define some variables needed for retrieving post_id/topic_id information $per_page = ($show_results == 'posts') ? $config['posts_per_page'] : $config['topics_per_page']; - $sort_by_sql = array('a' => (($show_results == 'posts') ? 'u.username' : 't.topic_poster'), 't' => (($show_results == 'posts') ? 'p.post_time' : 't.topic_last_post_time'), 'f' => 'f.forum_id', 'i' => 't.topic_title', 's' => (($show_results == 'posts') ? 'p.post_subject' : 't.topic_title')); + $sort_by_sql = array('a' => 'u.username', 't' => (($show_results == 'posts') ? 'p.post_time' : 't.topic_last_post_time'), 'f' => 'f.forum_id', 'i' => 't.topic_title', 's' => (($show_results == 'posts') ? 'p.post_subject' : 't.topic_title')); // pre-made searches $sql = $field = ''; @@ -246,7 +252,7 @@ if ($keywords || $author || $search_id) " . ((sizeof($ex_fid_ary)) ? ' AND p.forum_id NOT IN (' . implode(',', $ex_fid_ary) . ')' : '') . ' ORDER BY t.topic_last_post_time DESC'; $field = 'topic_id'; - break; + break; case 'unanswered': $sort_join = ($sort_key == 'f') ? FORUMS_TABLE . ' f, ' : ''; @@ -276,7 +282,7 @@ if ($keywords || $author || $search_id) $sql_sort"; $field = 'topic_id'; } - break; + break; case 'newposts': $sort_join = ($sort_key == 'f') ? FORUMS_TABLE . ' f, ' : ''; @@ -311,7 +317,7 @@ if ($keywords || $author || $search_id) $sql_sort"; $field = 'topic_id'; } - break; + break; } if ($sql) @@ -340,9 +346,6 @@ if ($keywords || $author || $search_id) } else if (sizeof($author_id_ary)) { - // default to showing results as posts when performing an author search - $show_results = ($topic_id) ? 'posts' : request_var('sr', 'posts'); - $total_match_count = $search->author_search($show_results, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $topic_id, $author_id_ary, $id_ary, $start, $per_page); } @@ -417,7 +420,7 @@ if ($keywords || $author || $search_id) 'UNAPPROVED_IMG' => $user->img('icon_unapproved', 'TOPIC_UNAPPROVED'), 'GOTO_PAGE_IMG' => $user->img('icon_post', 'GOTO_PAGE'), - 'U_SEARCH_WORDS' => "{$phpbb_root_path}search.$phpEx$SID$u_show_results&keywords=$u_hilit") + 'U_SEARCH_WORDS' => "{$phpbb_root_path}search.$phpEx$SID$u_show_results&keywords=$u_hilit" . (($author) ? '&author=' . urlencode($author) : '')) ); if ($sql_where) @@ -448,29 +451,111 @@ if ($keywords || $author || $search_id) } else { - $sql = 'SELECT t.*, f.forum_id, f.forum_name - FROM ' . TOPICS_TABLE . ' t - LEFT JOIN ' . FORUMS_TABLE . " f ON (f.forum_id = t.forum_id) - WHERE $sql_where"; + $sql_from = TOPICS_TABLE . ' t' . (($sort_key == 'a') ? ', ' . USERS_TABLE . ' u' : ''); + $sql_select = 't.*, f.forum_id, f.forum_name'; + if ($user->data['is_registered']) + { + if ($config['load_db_track']) + { + $sql_from .= ' LEFT JOIN ' . TOPICS_POSTED_TABLE . ' tp ON (tp.user_id = ' . $user->data['user_id'] . ' + AND t.topic_id = tp.topic_id)'; + $sql_select .= ', tp.topic_posted'; + } + if ($config['load_db_lastread']) + { + $sql_from .= ' LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.user_id = ' . $user->data['user_id'] . ' + AND t.topic_id = tt.topic_id) + LEFT JOIN ' . FORUMS_TRACK_TABLE . ' ft ON (ft.user_id = ' . $user->data['user_id'] . ' + AND ft.forum_id = f.forum_id)'; + $sql_select .= ', tt.mark_time, ft.mark_time as f_mark_time'; + } + } + + if ((!$user->data['is_registered']) || (!$config['load_db_lastread'])) + { + $tracking_topics = (isset($_COOKIE[$config['cookie_name'] . '_track'])) ? unserialize(stripslashes($_COOKIE[$config['cookie_name'] . '_track'])) : array(); + } + + $sql = "SELECT $sql_select + FROM ($sql_from) + LEFT JOIN " . FORUMS_TABLE . " f ON (f.forum_id = t.forum_id) + WHERE $sql_where" . (($sort_key == 'a') ? ' AND u.user_id = t.topic_poster' : ''); } $sql .= ' ORDER BY ' . $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); $result = $db->sql_query($sql); $result_topic_id = 0; - while ($row = $db->sql_fetchrow($result)) + if ($show_results = 'topics') + { + $forums = $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['topic_id']] = $row; + + if ((!isset($forums[$row['forum_id']])) && ($user->data['is_registered']) && ($config['load_db_lastread'])) + { + $forums[$row['forum_id']]['mark_time'] = $row['f_mark_time']; + } + $forums[$row['forum_id']]['topic_list'][] = $row['topic_id']; + $forums[$row['forum_id']]['rowset'][$row['topic_id']] = &$rowset[$row['topic_id']]; + } + $db->sql_freeresult($result); + + foreach ($forums as $forum_id => $forum) + { + if (($user->data['is_registered']) && ($config['load_db_lastread'])) + { + $topic_tracking_info[$forum_id] = get_topic_tracking($forum_id, $forum['topic_list'], $forum['rowset'], array($forum_id => $forum['mark_time']), ($forum_id) ? false : $forum['topic_list']); + } + else + { + $topic_tracking_info[$forum_id] = get_complete_topic_tracking($forum_id, $forum['topic_list'], ($forum_id) ? false : $forum['topic_list']); + + if (!$user->data['is_registered']) + { + $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0; + } + } + } + unset($forums); + } + else + { + while ($row = $db->sql_fetchrow($result)) + { + $rowset[] = $row; + } + $db->sql_freeresult($result); + } + + foreach ($rowset as $row) { $forum_id = $row['forum_id']; $result_topic_id = $row['topic_id']; $topic_title = censor_text($row['topic_title']); - $view_topic_url = "{$phpbb_root_path}viewtopic.$phpEx$SID&f=$forum_id&t=$result_topic_id&hilit=$u_hilit"; + if (!$forum_id) + { + if (!isset($g_forum_id)) + { + $availible_forums = array_values(array_diff(array_keys($auth->acl_getf('f_read', true)), $ex_fid_ary)); + $g_forum_id = $availible_forums[0]; + } + $u_forum_id = $g_forum_id; + } + else + { + $u_forum_id = $forum_id; + } + + $view_topic_url = "{$phpbb_root_path}viewtopic.$phpEx$SID&f=$u_forum_id&t=$result_topic_id&hilit=$u_hilit"; if ($show_results == 'topics') { $replies = ($auth->acl_get('m_approve', $forum_id)) ? $row['topic_replies_real'] : $row['topic_replies']; $folder_img = $folder_alt = $topic_type = ''; - topic_status($row, $replies, false, $folder_img, $folder_alt, $topic_type); + topic_status($row, $replies, (isset($topic_tracking_info[$forum_id][$row['topic_id']]) && $row['topic_last_post_time'] > $topic_tracking_info[$forum_id][$row['topic_id']]) ? true : false, $folder_img, $folder_alt, $topic_type); $tpl_ary = array( 'TOPIC_AUTHOR' => topic_topic_author($row), @@ -562,7 +647,6 @@ if ($keywords || $author || $search_id) 'U_VIEW_POST' => (!empty($row['post_id'])) ? "viewtopic.$phpEx$SID&f=$forum_id&t=" . $row['topic_id'] . '&p=' . $row['post_id'] . '&hilit=' . $u_hilit . '#' . $row['post_id'] : '') )); } - $db->sql_freeresult($result); if ($topic_id && ($topic_id == $result_topic_id)) { @@ -572,6 +656,7 @@ if ($keywords || $author || $search_id) )); } } + unset($rowset); page_header($user->lang['SEARCH']);