| <?php |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (!defined('IN_PHPBB')) |
| { |
| exit; |
| } |
| |
| |
| |
| |
| include_once($phpbb_root_path . 'includes/search/search.' . $phpEx); |
| |
| |
| |
| |
| |
| |
| class fulltext_native extends search_backend |
| { |
| var $stats = array(); |
| var $word_length = array(); |
| var $search_query; |
| var $common_words = array(); |
| |
| var $must_contain_ids = array(); |
| var $must_not_contain_ids = array(); |
| var $must_exclude_one_ids = array(); |
| |
| |
| |
| |
| |
| |
| |
| |
| function fulltext_native(&$error) |
| { |
| global $phpbb_root_path, $phpEx, $config; |
| |
| $this->word_length = array('min' => $config['fulltext_native_min_chars'], 'max' => $config['fulltext_native_max_chars']); |
| |
| |
| |
| |
| if (!class_exists('utf_normalizer')) |
| { |
| include($phpbb_root_path . 'includes/utf/utf_normalizer.' . $phpEx); |
| } |
| |
| |
| $error = false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function split_keywords($keywords, $terms) |
| { |
| global $db, $user; |
| |
| $keywords = trim($this->cleanup($keywords, '+-|()*')); |
| |
| |
| if ((strpos($keywords, ' ') === false) && (strpos($keywords, '|') !== false) && (strpos($keywords, '(') === false)) |
| { |
| $keywords = '(' . $keywords . ')'; |
| } |
| |
| $open_bracket = $space = false; |
| for ($i = 0, $n = strlen($keywords); $i < $n; $i++) |
| { |
| if ($open_bracket !== false) |
| { |
| switch ($keywords[$i]) |
| { |
| case ')': |
| if ($open_bracket + 1 == $i) |
| { |
| $keywords[$i - 1] = '|'; |
| $keywords[$i] = '|'; |
| } |
| $open_bracket = false; |
| break; |
| case '(': |
| $keywords[$i] = '|'; |
| break; |
| case '+': |
| case '-': |
| case ' ': |
| $keywords[$i] = '|'; |
| break; |
| } |
| } |
| else |
| { |
| switch ($keywords[$i]) |
| { |
| case ')': |
| $keywords[$i] = ' '; |
| break; |
| case '(': |
| $open_bracket = $i; |
| $space = false; |
| break; |
| case '|': |
| $keywords[$i] = ' '; |
| break; |
| case '-': |
| case '+': |
| $space = $keywords[$i]; |
| break; |
| case ' ': |
| if ($space !== false) |
| { |
| $keywords[$i] = $space; |
| } |
| break; |
| default: |
| $space = false; |
| } |
| } |
| } |
| |
| if ($open_bracket) |
| { |
| $keywords .= ')'; |
| } |
| |
| $match = array( |
| '# +#', |
| '#\|\|+#', |
| '#(\+|\-)(?:\+|\-)+#', |
| '#\(\|#', |
| '#\|\)#', |
| ); |
| $replace = array( |
| ' ', |
| '|', |
| '$1', |
| '(', |
| ')', |
| ); |
| |
| $keywords = preg_replace($match, $replace, $keywords); |
| |
| |
| |
| |
| if ($terms == 'any') |
| { |
| $words = array(); |
| |
| preg_match_all('#([^\\s+\\-|()]+)(?:$|[\\s+\\-|()])#u', $keywords, $words); |
| if (sizeof($words[1])) |
| { |
| $keywords = '(' . implode('|', $words[1]) . ')'; |
| } |
| } |
| |
| |
| $this->search_query = $keywords; |
| |
| $exact_words = array(); |
| preg_match_all('#([^\\s+\\-|*()]+)(?:$|[\\s+\\-|()])#u', $keywords, $exact_words); |
| $exact_words = $exact_words[1]; |
| |
| $common_ids = $words = array(); |
| |
| if (sizeof($exact_words)) |
| { |
| $sql = 'SELECT word_id, word_text, word_common |
| FROM ' . SEARCH_WORDLIST_TABLE . ' |
| WHERE ' . $db->sql_in_set('word_text', $exact_words); |
| $result = $db->sql_query($sql); |
| |
| |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| if ($row['word_common']) |
| { |
| $this->common_words[] = $row['word_text']; |
| $common_ids[$row['word_text']] = (int) $row['word_id']; |
| continue; |
| } |
| |
| $words[$row['word_text']] = (int) $row['word_id']; |
| } |
| $db->sql_freeresult($result); |
| } |
| unset($exact_words); |
| |
| |
| $query = explode(' ', $keywords); |
| |
| $this->must_contain_ids = array(); |
| $this->must_not_contain_ids = array(); |
| $this->must_exclude_one_ids = array(); |
| |
| $mode = ''; |
| $ignore_no_id = true; |
| |
| foreach ($query as $word) |
| { |
| if (empty($word)) |
| { |
| continue; |
| } |
| |
| |
| if ($word[0] == '-') |
| { |
| $word = substr($word, 1); |
| |
| |
| if ($word[0] == '(') |
| { |
| $word = array_unique(explode('|', substr($word, 1, -1))); |
| $mode = 'must_exclude_one'; |
| } |
| |
| else |
| { |
| $mode = 'must_not_contain'; |
| } |
| $ignore_no_id = true; |
| } |
| |
| else |
| { |
| |
| if ($word[0] == '+') |
| { |
| $word = substr($word, 1); |
| } |
| |
| |
| if ($word[0] == '(') |
| { |
| $word = array_unique(explode('|', substr($word, 1, -1))); |
| } |
| $ignore_no_id = false; |
| $mode = 'must_contain'; |
| } |
| |
| if (empty($word)) |
| { |
| continue; |
| } |
| |
| |
| if (is_array($word)) |
| { |
| $non_common_words = array(); |
| $id_words = array(); |
| foreach ($word as $i => $word_part) |
| { |
| if (strpos($word_part, '*') !== false) |
| { |
| $id_words[] = '\'' . $db->sql_escape(str_replace('*', '%', $word_part)) . '\''; |
| $non_common_words[] = $word_part; |
| } |
| else if (isset($words[$word_part])) |
| { |
| $id_words[] = $words[$word_part]; |
| $non_common_words[] = $word_part; |
| } |
| else |
| { |
| $len = utf8_strlen($word_part); |
| if ($len < $this->word_length['min'] || $len > $this->word_length['max']) |
| { |
| $this->common_words[] = $word_part; |
| } |
| } |
| } |
| if (sizeof($id_words)) |
| { |
| sort($id_words); |
| if (sizeof($id_words) > 1) |
| { |
| $this->{$mode . '_ids'}[] = $id_words; |
| } |
| else |
| { |
| $mode = ($mode == 'must_exclude_one') ? 'must_not_contain' : $mode; |
| $this->{$mode . '_ids'}[] = $id_words[0]; |
| } |
| } |
| |
| else if (!$ignore_no_id && sizeof($non_common_words)) |
| { |
| trigger_error(sprintf($user->lang['WORDS_IN_NO_POST'], implode(', ', $non_common_words))); |
| } |
| unset($non_common_words); |
| } |
| |
| else if (($wildcard = strpos($word, '*') !== false) || isset($words[$word])) |
| { |
| if ($wildcard) |
| { |
| $len = utf8_strlen(str_replace('*', '', $word)); |
| if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) |
| { |
| $this->{$mode . '_ids'}[] = '\'' . $db->sql_escape(str_replace('*', '%', $word)) . '\''; |
| } |
| else |
| { |
| $this->common_words[] = $word; |
| } |
| } |
| else |
| { |
| $this->{$mode . '_ids'}[] = $words[$word]; |
| } |
| } |
| |
| else if (!$ignore_no_id) |
| { |
| if (!isset($common_ids[$word])) |
| { |
| $len = utf8_strlen($word); |
| if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) |
| { |
| trigger_error(sprintf($user->lang['WORD_IN_NO_POST'], $word)); |
| } |
| else |
| { |
| $this->common_words[] = $word; |
| } |
| } |
| } |
| else |
| { |
| $len = utf8_strlen($word); |
| if ($len < $this->word_length['min'] || $len > $this->word_length['max']) |
| { |
| $this->common_words[] = $word; |
| } |
| } |
| } |
| |
| |
| if (!sizeof($this->must_contain_ids)) |
| { |
| return false; |
| } |
| |
| sort($this->must_contain_ids); |
| sort($this->must_not_contain_ids); |
| sort($this->must_exclude_one_ids); |
| |
| if (!empty($this->search_query)) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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, &$id_ary, $start, $per_page) |
| { |
| global $config, $db; |
| |
| |
| if (empty($this->search_query)) |
| { |
| return false; |
| } |
| |
| |
| $search_key = md5(implode('#', array( |
| serialize($this->must_contain_ids), |
| serialize($this->must_not_contain_ids), |
| serialize($this->must_exclude_one_ids), |
| $type, |
| $fields, |
| $terms, |
| $sort_days, |
| $sort_key, |
| $topic_id, |
| implode(',', $ex_fid_ary), |
| implode(',', $m_approve_fid_ary), |
| implode(',', $author_ary) |
| ))); |
| |
| |
| $total_results = 0; |
| if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) |
| { |
| return $total_results; |
| } |
| |
| $id_ary = array(); |
| |
| $sql_where = array(); |
| $group_by = false; |
| $m_num = 0; |
| $w_num = 0; |
| |
| $sql_array = array( |
| 'SELECT' => ($type == 'posts') ? 'p.post_id' : 'p.topic_id', |
| 'FROM' => array( |
| SEARCH_WORDMATCH_TABLE => array(), |
| SEARCH_WORDLIST_TABLE => array(), |
| ), |
| 'LEFT_JOIN' => array(array( |
| 'FROM' => array(POSTS_TABLE => 'p'), |
| 'ON' => 'm0.post_id = p.post_id', |
| )), |
| ); |
| |
| $title_match = ''; |
| $left_join_topics = false; |
| $group_by = true; |
| |
| switch ($fields) |
| { |
| case 'titleonly': |
| $title_match = 'title_match = 1'; |
| $group_by = false; |
| |
| case 'firstpost': |
| $left_join_topics = true; |
| $sql_where[] = 'p.post_id = t.topic_first_post_id'; |
| break; |
| |
| case 'msgonly': |
| $title_match = 'title_match = 0'; |
| $group_by = false; |
| break; |
| } |
| |
| if ($type == 'topics') |
| { |
| $left_join_topics = true; |
| $group_by = true; |
| } |
| |
| |
| |
| |
| |
| foreach ($this->must_contain_ids as $subquery) |
| { |
| if (is_array($subquery)) |
| { |
| $group_by = true; |
| |
| $word_id_sql = array(); |
| $word_ids = array(); |
| foreach ($subquery as $id) |
| { |
| if (is_string($id)) |
| { |
| $sql_array['LEFT_JOIN'][] = array( |
| 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), |
| 'ON' => "w$w_num.word_text LIKE $id" |
| ); |
| $word_ids[] = "w$w_num.word_id"; |
| |
| $w_num++; |
| } |
| else |
| { |
| $word_ids[] = $id; |
| } |
| } |
| |
| $sql_where[] = $db->sql_in_set("m$m_num.word_id", $word_ids); |
| |
| unset($word_id_sql); |
| unset($word_ids); |
| } |
| else if (is_string($subquery)) |
| { |
| $sql_array['FROM'][SEARCH_WORDLIST_TABLE][] = 'w' . $w_num; |
| |
| $sql_where[] = "w$w_num.word_text LIKE $subquery"; |
| $sql_where[] = "m$m_num.word_id = w$w_num.word_id"; |
| |
| $group_by = true; |
| $w_num++; |
| } |
| else |
| { |
| $sql_where[] = "m$m_num.word_id = $subquery"; |
| } |
| |
| $sql_array['FROM'][SEARCH_WORDMATCH_TABLE][] = 'm' . $m_num; |
| |
| if ($title_match) |
| { |
| $sql_where[] = "m$m_num.$title_match"; |
| } |
| |
| if ($m_num != 0) |
| { |
| $sql_where[] = "m$m_num.post_id = m0.post_id"; |
| } |
| $m_num++; |
| } |
| |
| foreach ($this->must_not_contain_ids as $key => $subquery) |
| { |
| if (is_string($subquery)) |
| { |
| $sql_array['LEFT_JOIN'][] = array( |
| 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), |
| 'ON' => "w$w_num.word_text LIKE $subquery" |
| ); |
| |
| $this->must_not_contain_ids[$key] = "w$w_num.word_id"; |
| |
| $group_by = true; |
| $w_num++; |
| } |
| } |
| |
| if (sizeof($this->must_not_contain_ids)) |
| { |
| $sql_array['LEFT_JOIN'][] = array( |
| 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), |
| 'ON' => $db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id" |
| ); |
| |
| $sql_where[] = "m$m_num.word_id IS NULL"; |
| $m_num++; |
| } |
| |
| foreach ($this->must_exclude_one_ids as $ids) |
| { |
| $is_null_joins = array(); |
| foreach ($ids as $id) |
| { |
| if (is_string($id)) |
| { |
| $sql_array['LEFT_JOIN'][] = array( |
| 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), |
| 'ON' => "w$w_num.word_text LIKE $id" |
| ); |
| $id = "w$w_num.word_id"; |
| |
| $group_by = true; |
| $w_num++; |
| } |
| |
| $sql_array['LEFT_JOIN'][] = array( |
| 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), |
| 'ON' => "m$m_num.word_id = $id AND m$m_num.post_id = m0.post_id" . (($title_match) ? " AND m$m_num.$title_match" : '') |
| ); |
| $is_null_joins[] = "m$m_num.word_id IS NULL"; |
| |
| $m_num++; |
| } |
| $sql_where[] = '(' . implode(' OR ', $is_null_joins) . ')'; |
| } |
| |
| if (!sizeof($m_approve_fid_ary)) |
| { |
| $sql_where[] = 'p.post_approved = 1'; |
| } |
| else if ($m_approve_fid_ary !== array(-1)) |
| { |
| $sql_where[] = '(p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; |
| } |
| |
| if ($topic_id) |
| { |
| $sql_where[] = 'p.topic_id = ' . $topic_id; |
| } |
| |
| if (sizeof($author_ary)) |
| { |
| $sql_where[] = $db->sql_in_set('p.poster_id', $author_ary); |
| } |
| |
| if (sizeof($ex_fid_ary)) |
| { |
| $sql_where[] = $db->sql_in_set('p.forum_id', $ex_fid_ary, true); |
| } |
| |
| if ($sort_days) |
| { |
| $sql_where[] = 'p.post_time >= ' . (time() - ($sort_days * 86400)); |
| } |
| |
| $sql_array['WHERE'] = implode(' AND ', $sql_where); |
| |
| $is_mysql = false; |
| |
| if (!$total_results) |
| { |
| $sql = ''; |
| $sql_array_count = $sql_array; |
| |
| switch ($db->sql_layer) |
| { |
| case 'mysql4': |
| case 'mysqli': |
| |
| |
| $sql_array['SELECT'] = 'SQL_CALC_FOUND_ROWS ' . $sql_array['SELECT']; |
| $is_mysql = true; |
| |
| break; |
| |
| case 'sqlite': |
| $sql_array_count['SELECT'] = ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id'; |
| $sql = 'SELECT COUNT(' . (($type == 'posts') ? 'post_id' : 'topic_id') . ') as total_results |
| FROM (' . $db->sql_build_query('SELECT', $sql_array_count) . ')'; |
| |
| |
| |
| default: |
| $sql_array_count['SELECT'] = ($type == 'posts') ? 'COUNT(DISTINCT p.post_id) AS total_results' : 'COUNT(DISTINCT p.topic_id) AS total_results'; |
| $sql = (!$sql) ? $db->sql_build_query('SELECT', $sql_array_count) : $sql; |
| |
| $result = $db->sql_query($sql); |
| $total_results = (int) $db->sql_fetchfield('total_results'); |
| $db->sql_freeresult($result); |
| |
| if (!$total_results) |
| { |
| return false; |
| } |
| break; |
| } |
| |
| unset($sql_array_count, $sql); |
| } |
| |
| |
| $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); |
| |
| switch ($sql_sort[0]) |
| { |
| case 'u': |
| $sql_array['FROM'][USERS_TABLE] = 'u'; |
| $sql_where[] = 'u.user_id = p.poster_id '; |
| break; |
| |
| case 't': |
| $left_join_topics = true; |
| break; |
| |
| case 'f': |
| $sql_array['FROM'][FORUMS_TABLE] = 'f'; |
| $sql_where[] = 'f.forum_id = p.forum_id'; |
| break; |
| } |
| |
| if ($left_join_topics) |
| { |
| $sql_array['LEFT_JOIN'][$left_join_topics] = array( |
| 'FROM' => array(TOPICS_TABLE => 't'), |
| 'ON' => 'p.topic_id = t.topic_id' |
| ); |
| } |
| |
| $sql_array['WHERE'] = implode(' AND ', $sql_where); |
| $sql_array['GROUP_BY'] = ($group_by) ? (($type == 'posts') ? 'p.post_id' : 'p.topic_id') . ', ' . $sort_by_sql[$sort_key] : ''; |
| $sql_array['ORDER_BY'] = $sql_sort; |
| |
| unset($sql_where, $sql_sort, $group_by); |
| |
| $sql = $db->sql_build_query('SELECT', $sql_array); |
| $result = $db->sql_query_limit($sql, $config['search_block_size'], $start); |
| |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| $id_ary[] = $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; |
| } |
| $db->sql_freeresult($result); |
| |
| if (!sizeof($id_ary)) |
| { |
| return false; |
| } |
| |
| |
| if (!$total_results && $is_mysql) |
| { |
| $sql = 'SELECT FOUND_ROWS() as total_results'; |
| $result = $db->sql_query($sql); |
| $total_results = (int) $db->sql_fetchfield('total_results'); |
| $db->sql_freeresult($result); |
| |
| if (!$total_results) |
| { |
| return false; |
| } |
| } |
| |
| |
| $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); |
| $id_ary = array_slice($id_ary, 0, (int) $per_page); |
| |
| return $total_results; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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, &$id_ary, $start, $per_page) |
| { |
| global $config, $db; |
| |
| |
| if (!sizeof($author_ary)) |
| { |
| return 0; |
| } |
| |
| |
| $search_key = md5(implode('#', array( |
| '', |
| $type, |
| ($firstpost_only) ? 'firstpost' : '', |
| '', |
| '', |
| $sort_days, |
| $sort_key, |
| $topic_id, |
| implode(',', $ex_fid_ary), |
| implode(',', $m_approve_fid_ary), |
| implode(',', $author_ary) |
| ))); |
| |
| |
| $total_results = 0; |
| if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) |
| { |
| return $total_results; |
| } |
| |
| $id_ary = array(); |
| |
| |
| $sql_author = $db->sql_in_set('p.poster_id', $author_ary); |
| $sql_fora = (sizeof($ex_fid_ary)) ? ' AND ' . $db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; |
| $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; |
| $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; |
| $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; |
| |
| |
| $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 = ' AND u.user_id = p.poster_id '; |
| break; |
| |
| case 't': |
| $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; |
| $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' 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 (!sizeof($m_approve_fid_ary)) |
| { |
| $m_approve_fid_sql = ' AND p.post_approved = 1'; |
| } |
| else if ($m_approve_fid_ary == array(-1)) |
| { |
| $m_approve_fid_sql = ''; |
| } |
| else |
| { |
| $m_approve_fid_sql = ' AND (p.post_approved = 1 OR ' . $db->sql_in_set('p.forum_id', $m_approve_fid_ary, true) . ')'; |
| } |
| |
| $select = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; |
| $is_mysql = false; |
| |
| |
| if (!$total_results) |
| { |
| switch ($db->sql_layer) |
| { |
| case 'mysql4': |
| case 'mysqli': |
| $select = 'SQL_CALC_FOUND_ROWS ' . $select; |
| $is_mysql = true; |
| break; |
| |
| default: |
| if ($type == 'posts') |
| { |
| $sql = 'SELECT COUNT(p.post_id) as total_results |
| FROM ' . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " |
| WHERE $sql_author |
| $sql_topic_id |
| $sql_firstpost |
| $m_approve_fid_sql |
| $sql_fora |
| $sql_time"; |
| } |
| else |
| { |
| if ($db->sql_layer == 'sqlite') |
| { |
| $sql = 'SELECT COUNT(topic_id) as total_results |
| FROM (SELECT DISTINCT t.topic_id'; |
| } |
| else |
| { |
| $sql = 'SELECT COUNT(DISTINCT t.topic_id) as total_results'; |
| } |
| |
| $sql .= ' FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p |
| WHERE $sql_author |
| $sql_topic_id |
| $sql_firstpost |
| $m_approve_fid_sql |
| $sql_fora |
| AND t.topic_id = p.topic_id |
| $sql_time" . (($db->sql_layer == 'sqlite') ? ')' : ''); |
| } |
| $result = $db->sql_query($sql); |
| |
| $total_results = (int) $db->sql_fetchfield('total_results'); |
| $db->sql_freeresult($result); |
| |
| if (!$total_results) |
| { |
| return false; |
| } |
| break; |
| } |
| } |
| |
| |
| if ($type == 'posts') |
| { |
| $sql = "SELECT $select |
| FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t' : '') . " |
| WHERE $sql_author |
| $sql_topic_id |
| $sql_firstpost |
| $m_approve_fid_sql |
| $sql_fora |
| $sql_sort_join |
| $sql_time |
| ORDER BY $sql_sort"; |
| $field = 'post_id'; |
| } |
| else |
| { |
| $sql = "SELECT $select |
| FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p |
| WHERE $sql_author |
| $sql_topic_id |
| $sql_firstpost |
| $m_approve_fid_sql |
| $sql_fora |
| AND t.topic_id = p.topic_id |
| $sql_sort_join |
| $sql_time |
| GROUP BY t.topic_id, " . $sort_by_sql[$sort_key] . ' |
| ORDER BY ' . $sql_sort; |
| $field = 'topic_id'; |
| } |
| |
| |
| $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); |
| |
| if (!$total_results && $is_mysql) |
| { |
| $sql = 'SELECT FOUND_ROWS() as total_results'; |
| $result = $db->sql_query($sql); |
| $total_results = (int) $db->sql_fetchfield('total_results'); |
| $db->sql_freeresult($result); |
| |
| if (!$total_results) |
| { |
| return false; |
| } |
| } |
| |
| if (sizeof($id_ary)) |
| { |
| $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); |
| $id_ary = array_slice($id_ary, 0, $per_page); |
| |
| return $total_results; |
| } |
| return false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function split_message($text) |
| { |
| global $phpbb_root_path, $phpEx, $user; |
| |
| $match = $words = array(); |
| |
| |
| |
| |
| |
| $match[] = '#\[code(?:=.*?)?(\:?[0-9a-z]{5,})\].*?\[\/code(\:?[0-9a-z]{5,})\]#is'; |
| |
| $match[] = '#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?[0-9a-z]{5,})\]#'; |
| |
| $min = $this->word_length['min']; |
| $max = $this->word_length['max']; |
| |
| $isset_min = $min - 1; |
| |
| |
| |
| |
| $word = strtok($this->cleanup(preg_replace($match, ' ', strip_tags($text)), -1), ' '); |
| |
| while (strlen($word)) |
| { |
| if (strlen($word) > 255 || strlen($word) <= $isset_min) |
| { |
| |
| |
| |
| |
| |
| |
| $word = strtok(' '); |
| continue; |
| } |
| |
| $len = utf8_strlen($word); |
| |
| |
| |
| |
| |
| |
| if ($len < $min) |
| { |
| |
| |
| |
| |
| if ((strncmp($word, UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, UTF8_HANGUL_LAST, 3) > 0) |
| && (strncmp($word, UTF8_CJK_FIRST, 3) < 0 || strncmp($word, UTF8_CJK_LAST, 3) > 0) |
| && (strncmp($word, UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, UTF8_CJK_B_LAST, 4) > 0)) |
| { |
| $word = strtok(' '); |
| continue; |
| } |
| } |
| |
| $words[] = $word; |
| $word = strtok(' '); |
| } |
| |
| return $words; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) |
| { |
| global $config, $db, $user; |
| |
| if (!$config['fulltext_native_load_upd']) |
| { |
| |
| |
| |
| return; |
| } |
| |
| |
| $split_text = $this->split_message($message); |
| $split_title = $this->split_message($subject); |
| |
| $cur_words = array('post' => array(), 'title' => array()); |
| |
| $words = array(); |
| if ($mode == 'edit') |
| { |
| $words['add']['post'] = array(); |
| $words['add']['title'] = array(); |
| $words['del']['post'] = array(); |
| $words['del']['title'] = array(); |
| |
| $sql = 'SELECT w.word_id, w.word_text, m.title_match |
| FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m |
| WHERE m.post_id = $post_id |
| AND w.word_id = m.word_id"; |
| $result = $db->sql_query($sql); |
| |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| $which = ($row['title_match']) ? 'title' : 'post'; |
| $cur_words[$which][$row['word_text']] = $row['word_id']; |
| } |
| $db->sql_freeresult($result); |
| |
| $words['add']['post'] = array_diff($split_text, array_keys($cur_words['post'])); |
| $words['add']['title'] = array_diff($split_title, array_keys($cur_words['title'])); |
| $words['del']['post'] = array_diff(array_keys($cur_words['post']), $split_text); |
| $words['del']['title'] = array_diff(array_keys($cur_words['title']), $split_title); |
| } |
| else |
| { |
| $words['add']['post'] = $split_text; |
| $words['add']['title'] = $split_title; |
| $words['del']['post'] = array(); |
| $words['del']['title'] = array(); |
| } |
| unset($split_text); |
| unset($split_title); |
| |
| |
| $unique_add_words = array_unique(array_merge($words['add']['post'], $words['add']['title'])); |
| |
| |
| |
| |
| |
| if (sizeof($unique_add_words)) |
| { |
| $sql = 'SELECT word_id, word_text |
| FROM ' . SEARCH_WORDLIST_TABLE . ' |
| WHERE ' . $db->sql_in_set('word_text', $unique_add_words); |
| $result = $db->sql_query($sql); |
| |
| $word_ids = array(); |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| $word_ids[$row['word_text']] = $row['word_id']; |
| } |
| $db->sql_freeresult($result); |
| $new_words = array_diff($unique_add_words, array_keys($word_ids)); |
| |
| $db->sql_transaction('begin'); |
| if (sizeof($new_words)) |
| { |
| $sql_ary = array(); |
| |
| foreach ($new_words as $word) |
| { |
| $sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0); |
| } |
| $db->sql_return_on_error(true); |
| $db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); |
| $db->sql_return_on_error(false); |
| } |
| unset($new_words, $sql_ary); |
| } |
| else |
| { |
| $db->sql_transaction('begin'); |
| } |
| |
| |
| foreach ($words['del'] as $word_in => $word_ary) |
| { |
| $title_match = ($word_in == 'title') ? 1 : 0; |
| |
| if (sizeof($word_ary)) |
| { |
| $sql_in = array(); |
| foreach ($word_ary as $word) |
| { |
| $sql_in[] = $cur_words[$word_in][$word]; |
| } |
| |
| $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' |
| WHERE ' . $db->sql_in_set('word_id', $sql_in) . ' |
| AND post_id = ' . intval($post_id) . " |
| AND title_match = $title_match"; |
| $db->sql_query($sql); |
| |
| $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' |
| SET word_count = word_count - 1 |
| WHERE ' . $db->sql_in_set('word_id', $sql_in) . ' |
| AND word_count > 0'; |
| $db->sql_query($sql); |
| |
| unset($sql_in); |
| } |
| } |
| |
| $db->sql_return_on_error(true); |
| foreach ($words['add'] as $word_in => $word_ary) |
| { |
| $title_match = ($word_in == 'title') ? 1 : 0; |
| |
| if (sizeof($word_ary)) |
| { |
| $sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match) |
| SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . ' |
| FROM ' . SEARCH_WORDLIST_TABLE . ' |
| WHERE ' . $db->sql_in_set('word_text', $word_ary); |
| $db->sql_query($sql); |
| |
| $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' |
| SET word_count = word_count + 1 |
| WHERE ' . $db->sql_in_set('word_text', $word_ary); |
| $db->sql_query($sql); |
| } |
| } |
| $db->sql_return_on_error(false); |
| |
| $db->sql_transaction('commit'); |
| |
| |
| $this->destroy_cache(array_unique(array_merge($words['add']['post'], $words['add']['title'], $words['del']['post'], $words['del']['title'])), array($poster_id)); |
| |
| unset($unique_add_words); |
| unset($words); |
| unset($cur_words); |
| } |
| |
| |
| |
| |
| function index_remove($post_ids, $author_ids, $forum_ids) |
| { |
| global $db; |
| |
| if (sizeof($post_ids)) |
| { |
| $sql = 'SELECT w.word_id, w.word_text, m.title_match |
| FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w |
| WHERE ' . $db->sql_in_set('m.post_id', $post_ids) . ' |
| AND w.word_id = m.word_id'; |
| $result = $db->sql_query($sql); |
| |
| $message_word_ids = $title_word_ids = $word_texts = array(); |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| if ($row['title_match']) |
| { |
| $title_word_ids[] = $row['word_id']; |
| } |
| else |
| { |
| $message_word_ids[] = $row['word_id']; |
| } |
| $word_texts[] = $row['word_text']; |
| } |
| $db->sql_freeresult($result); |
| |
| if (sizeof($title_word_ids)) |
| { |
| $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' |
| SET word_count = word_count - 1 |
| WHERE ' . $db->sql_in_set('word_id', $title_word_ids) . ' |
| AND word_count > 0'; |
| $db->sql_query($sql); |
| } |
| |
| if (sizeof($message_word_ids)) |
| { |
| $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' |
| SET word_count = word_count - 1 |
| WHERE ' . $db->sql_in_set('word_id', $message_word_ids) . ' |
| AND word_count > 0'; |
| $db->sql_query($sql); |
| } |
| |
| unset($title_word_ids); |
| unset($message_word_ids); |
| |
| $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' |
| WHERE ' . $db->sql_in_set('post_id', $post_ids); |
| $db->sql_query($sql); |
| } |
| |
| $this->destroy_cache(array_unique($word_texts), $author_ids); |
| } |
| |
| |
| |
| |
| |
| function tidy() |
| { |
| global $db, $config; |
| |
| |
| |
| if (!$config['fulltext_native_load_upd']) |
| { |
| set_config('search_last_gc', time(), true); |
| return; |
| } |
| |
| $destroy_cache_words = array(); |
| |
| |
| if ($config['num_posts'] >= 100 && $config['fulltext_native_common_thres']) |
| { |
| $common_threshold = ((double) $config['fulltext_native_common_thres']) / 100.0; |
| |
| $sql = 'SELECT word_id, word_text |
| FROM ' . SEARCH_WORDLIST_TABLE . ' |
| WHERE word_count > ' . floor($config['num_posts'] * $common_threshold) . ' |
| OR word_common = 1'; |
| $result = $db->sql_query($sql); |
| |
| $sql_in = array(); |
| while ($row = $db->sql_fetchrow($result)) |
| { |
| $sql_in[] = $row['word_id']; |
| $destroy_cache_words[] = $row['word_text']; |
| } |
| $db->sql_freeresult($result); |
| |
| if (sizeof($sql_in)) |
| { |
| |
| $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' |
| SET word_common = 1 |
| WHERE ' . $db->sql_in_set('word_id', $sql_in); |
| $db->sql_query($sql); |
| |
| |
| |
| set_config('search_last_gc', time(), true); |
| |
| |
| $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' |
| WHERE ' . $db->sql_in_set('word_id', $sql_in); |
| $db->sql_query($sql); |
| } |
| unset($sql_in); |
| } |
| |
| if (sizeof($destroy_cache_words)) |
| { |
| |
| $this->destroy_cache(array_unique($destroy_cache_words)); |
| } |
| |
| set_config('search_last_gc', time(), true); |
| } |
| |
| |
| |
| |
| function delete_index($acp_module, $u_action) |
| { |
| global $db; |
| |
| switch ($db->sql_layer) |
| { |
| case 'sqlite': |
| case 'firebird': |
| $db->sql_query('DELETE FROM ' . SEARCH_WORDLIST_TABLE); |
| $db->sql_query('DELETE FROM ' . SEARCH_WORDMATCH_TABLE); |
| $db->sql_query('DELETE FROM ' . SEARCH_RESULTS_TABLE); |
| break; |
| |
| default: |
| $db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE); |
| $db->sql_query('TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE); |
| $db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); |
| break; |
| } |
| } |
| |
| |
| |
| |
| function index_created() |
| { |
| if (!sizeof($this->stats)) |
| { |
| $this->get_stats(); |
| } |
| |
| return ($this->stats['total_words'] && $this->stats['total_matches']) ? true : false; |
| } |
| |
| |
| |
| |
| function index_stats() |
| { |
| global $user; |
| |
| if (!sizeof($this->stats)) |
| { |
| $this->get_stats(); |
| } |
| |
| return array( |
| $user->lang['TOTAL_WORDS'] => $this->stats['total_words'], |
| $user->lang['TOTAL_MATCHES'] => $this->stats['total_matches']); |
| } |
| |
| function get_stats() |
| { |
| global $db; |
| |
| $sql = 'SELECT COUNT(*) as total_words |
| FROM ' . SEARCH_WORDLIST_TABLE; |
| $result = $db->sql_query($sql); |
| $this->stats['total_words'] = (int) $db->sql_fetchfield('total_words'); |
| $db->sql_freeresult($result); |
| |
| $sql = 'SELECT COUNT(*) as total_matches |
| FROM ' . SEARCH_WORDMATCH_TABLE; |
| $result = $db->sql_query($sql); |
| $this->stats['total_matches'] = (int) $db->sql_fetchfield('total_matches'); |
| $db->sql_freeresult($result); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function cleanup($text, $allowed_chars = null, $encoding = 'utf-8') |
| { |
| global $phpbb_root_path, $phpEx; |
| static $conv = array(), $conv_loaded = array(); |
| $words = $allow = array(); |
| |
| |
| $encoding = strtolower($encoding); |
| if ($encoding != 'utf-8') |
| { |
| $text = utf8_recode($text, $encoding); |
| } |
| |
| $utf_len_mask = array( |
| "\xC0" => 2, |
| "\xD0" => 2, |
| "\xE0" => 3, |
| "\xF0" => 4 |
| ); |
| |
| |
| |
| |
| $text = htmlspecialchars_decode(utf8_decode_ncr($text), ENT_QUOTES); |
| |
| |
| |
| |
| |
| |
| |
| utf_normalizer::nfc($text); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| $sb_match = "ISTCPAMELRDOJBNHFGVWUQKYXZ\r\n\t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\xC0\xC1\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; |
| $sb_replace = 'istcpamelrdojbnhfgvwuqkyxz '; |
| |
| |
| |
| |
| |
| $legal_ascii = ' eaisntroludcpmghbfvq10xy2j9kw354867z'; |
| |
| |
| |
| |
| if (isset($allowed_chars[0])) |
| { |
| $pos = 0; |
| $len = strlen($allowed_chars); |
| do |
| { |
| $c = $allowed_chars[$pos]; |
| |
| if ($c < "\x80") |
| { |
| |
| |
| |
| $sb_pos = strpos($sb_match, $c); |
| if (is_int($sb_pos)) |
| { |
| |
| |
| |
| |
| $sb_match = substr($sb_match, 0, $sb_pos) . substr($sb_match, $sb_pos + 1); |
| $sb_replace = substr($sb_replace, 0, $sb_pos) . substr($sb_replace, $sb_pos + 1); |
| $legal_ascii .= $c; |
| } |
| |
| ++$pos; |
| } |
| else |
| { |
| |
| |
| |
| $utf_len = $utf_len_mask[$c & "\xF0"]; |
| $allow[substr($allowed_chars, $pos, $utf_len)] = 1; |
| $pos += $utf_len; |
| } |
| } |
| while ($pos < $len); |
| } |
| |
| $text = strtr($text, $sb_match, $sb_replace); |
| $ret = ''; |
| |
| $pos = 0; |
| $len = strlen($text); |
| |
| do |
| { |
| |
| |
| |
| if ($spn = strspn($text, $legal_ascii, $pos)) |
| { |
| $ret .= substr($text, $pos, $spn); |
| $pos += $spn; |
| } |
| |
| if ($pos >= $len) |
| { |
| return $ret; |
| } |
| |
| |
| |
| |
| $utf_len = $utf_len_mask[$text[$pos] & "\xF0"]; |
| $utf_char = substr($text, $pos, $utf_len); |
| $pos += $utf_len; |
| |
| if (($utf_char >= UTF8_HANGUL_FIRST && $utf_char <= UTF8_HANGUL_LAST) |
| || ($utf_char >= UTF8_CJK_FIRST && $utf_char <= UTF8_CJK_LAST) |
| || ($utf_char >= UTF8_CJK_B_FIRST && $utf_char <= UTF8_CJK_B_LAST)) |
| { |
| |
| |
| |
| |
| |
| |
| $ret .= ' ' . $utf_char . ' '; |
| continue; |
| } |
| |
| if (isset($allow[$utf_char])) |
| { |
| |
| |
| |
| $ret .= $utf_char; |
| continue; |
| } |
| |
| if (isset($conv[$utf_char])) |
| { |
| |
| |
| |
| $ret .= $conv[$utf_char]; |
| continue; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (isset($utf_char[2])) |
| { |
| if (isset($utf_char[3])) |
| { |
| |
| |
| |
| |
| $idx = ((ord($utf_char[0]) & 0x07) << 7) | ((ord($utf_char[1]) & 0x3F) << 1) | ((ord($utf_char[2]) & 0x20) >> 5); |
| } |
| else |
| { |
| |
| |
| |
| |
| $idx = ((ord($utf_char[0]) & 0x07) << 1) | ((ord($utf_char[1]) & 0x20) >> 5); |
| } |
| } |
| else |
| { |
| |
| |
| |
| |
| $idx = 0; |
| } |
| |
| |
| |
| |
| if (!isset($conv_loaded[$idx])) |
| { |
| $conv_loaded[$idx] = 1; |
| $file = $phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $phpEx; |
| |
| if (file_exists($file)) |
| { |
| $conv += include($file); |
| } |
| } |
| |
| if (isset($conv[$utf_char])) |
| { |
| $ret .= $conv[$utf_char]; |
| } |
| else |
| { |
| |
| |
| |
| |
| |
| $conv[$utf_char] = ' '; |
| $ret .= ' '; |
| } |
| } |
| while (1); |
| |
| return $ret; |
| } |
| |
| |
| |
| |
| function acp() |
| { |
| global $user, $config; |
| |
| |
| |
| |
| |
| |
| $tpl = ' |
| <dl> |
| <dt><label for="fulltext_native_load_upd">' . $user->lang['YES_SEARCH_UPDATE'] . ':</label><br /><span>' . $user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '</span></dt> |
| <dd><label><input type="radio" id="fulltext_native_load_upd" name="config[fulltext_native_load_upd]" value="1"' . (($config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $user->lang['YES'] . '</label><label><input type="radio" name="config[fulltext_native_load_upd]" value="0"' . ((!$config['fulltext_native_load_upd']) ? ' checked="checked"' : '') . ' class="radio" /> ' . $user->lang['NO'] . '</label></dd> |
| </dl> |
| <dl> |
| <dt><label for="fulltext_native_min_chars">' . $user->lang['MIN_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '</span></dt> |
| <dd><input id="fulltext_native_min_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_min_chars]" value="' . (int) $config['fulltext_native_min_chars'] . '" /></dd> |
| </dl> |
| <dl> |
| <dt><label for="fulltext_native_max_chars">' . $user->lang['MAX_SEARCH_CHARS'] . ':</label><br /><span>' . $user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '</span></dt> |
| <dd><input id="fulltext_native_max_chars" type="text" size="3" maxlength="3" name="config[fulltext_native_max_chars]" value="' . (int) $config['fulltext_native_max_chars'] . '" /></dd> |
| </dl> |
| <dl> |
| <dt><label for="fulltext_native_common_thres">' . $user->lang['COMMON_WORD_THRESHOLD'] . ':</label><br /><span>' . $user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '</span></dt> |
| <dd><input id="fulltext_native_common_thres" type="text" size="3" maxlength="3" name="config[fulltext_native_common_thres]" value="' . (double) $config['fulltext_native_common_thres'] . '" /> %</dd> |
| </dl> |
| '; |
| |
| |
| return array( |
| 'tpl' => $tpl, |
| 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'double:0:100') |
| ); |
| } |
| } |
| |
| ?> |