/** * The actualiser of a search. * * @param ID_TEXT Codename for what's being searched (blank: mixed search) * @param string Author name * @param ?AUTO_LINK Author ID (NULL: none given) * @param integer Days to search * @param ID_TEXT Sort key * @param ID_TEXT Sort direction * @set ASC DESC * @param boolean Whether to only search titles * @param string Comma-separated list of categories to search under * @return array A triple: The results, results browser, the number of results */ function results($id, $author, $author_id, $days, $sort, $direction, $only_titles, $search_under) { $title = get_page_title('RESULTS'); cache_module_installed_status(); $cutoff = $days == -1 ? NULL : time() - $days * 24 * 60 * 60; // What we're searching for $content = get_param('content', false, true); // Search keyword highlighting in any loaded Comcode global $SEARCH__CONTENT_BITS; $_content_bits = explode(' ', str_replace('"', '', preg_replace('#(^|\\s)\\+#', '', preg_replace('#(^|\\s)\\-#', '', $content)))); $SEARCH__CONTENT_BITS = array(); require_code('textfiles'); $too_common_words = explode(chr(10), read_text_file('too_common_words', '', true)); foreach ($_content_bits as $content_bit) { $content_bit = trim($content_bit); if ($content_bit == '') { continue; } if (!in_array(strtolower($content_bit), $too_common_words)) { $SEARCH__CONTENT_BITS[] = $content_bit; } } $start = get_param_integer('start', 0); $default_max = 10; if (ini_get('memory_limit') != '-1' && ini_get('memory_limit') != '0') { if (intval(preg_replace('#M$#', '', ini_get('memory_limit'))) < 20) { $default_max = 5; } } $max = get_param_integer('max', $default_max); // Also see get_search_rows $save_title = get_param('save_title', ''); if (!is_guest() && $save_title != '' && $start == 0) { static $saved_search = false; if (!$saved_search) { $GLOBALS['SITE_DB']->query_insert('searches_saved', array('s_title' => $save_title, 's_member_id' => get_member(), 's_time' => time(), 's_primary' => $content, 's_auxillary' => serialize(array_merge($_POST, $_GET)))); $saved_search = true; } } $boolean_operator = get_param('conjunctive_operator', 'OR'); $boolean_search = $this->_is_boolean_search(); $content_where = build_content_where($content, $boolean_search, $boolean_operator); disable_php_memory_limit(); // Search under all hooks we've asked to search under $results = array(); $_hooks = find_all_hooks('modules', 'search'); foreach (array_keys($_hooks) as $hook) { require_code('hooks/modules/search/' . filter_naughty_harsh($hook)); $object = object_factory('Hook_search_' . filter_naughty_harsh($hook), true); if (is_null($object)) { continue; } $info = $object->info(); if (is_null($info)) { continue; } $test = get_param_integer('search_' . $hook, 0); if (($test == 1 || get_param_integer('all_defaults', 0) == 1 && $info['default'] || $id == $hook) && ($id == '' || $id == $hook)) { // Category filter if ($search_under != '!' && $search_under != '-1' && array_key_exists('category', $info)) { $cats = explode(',', $search_under); $where_clause = '('; foreach ($cats as $cat) { if (trim($cat) == '') { continue; } if ($where_clause != '(') { $where_clause .= ' OR '; } if ($info['integer_category']) { $where_clause .= (strpos($info['category'], '.') !== false ? '' : 'r.') . $info['category'] . '=' . strval((int) $cat); } else { $where_clause .= db_string_equal_to((strpos($info['category'], '.') !== false ? '' : 'r.') . $info['category'], $cat); } } $where_clause .= ')'; } else { $where_clause = ''; } $only_search_meta = get_param_integer('only_search_meta', 0) == 1; $direction = get_param('direction', 'ASC'); if (function_exists('set_time_limit')) { @set_time_limit(5); } // Prevent errant search hooks (easily written!) taking down a server. Each call given 5 seconds (calling set_time_limit resets the timer). $hook_results = $object->run($content, $only_search_meta, $direction, $max, $start, $only_titles, $content_where, $author, $author_id, $cutoff, $sort, $max, $boolean_operator, $where_clause, $search_under, $boolean_search ? 1 : 0); if (is_null($hook_results)) { continue; } foreach ($hook_results as $i => $result) { $result['object'] = $object; $hook_results[$i] = $result; } $results = sort_search_results($hook_results, $results, $direction); } } if (function_exists('set_time_limit')) { @set_time_limit(15); } global $EXTRA_HEAD; $EXTRA_HEAD->attach('<meta name="robots" content="noindex,nofollow" />'); // XHTMLXHTML // Now glue our templates together $out = build_search_results_interface($results, $start, $max, $direction, $id == ''); if ($out->is_empty()) { if ($days != -1 && $GLOBALS['TOTAL_RESULTS'] == 0) { $ret_maybe = $this->results($id, $author, $author_id, -1, $sort, $direction, $only_titles, $search_under); if (!$ret_maybe[0]->is_empty()) { attach_message(do_lang_tempcode('NO_RESULTS_DAYS', escape_html(integer_format($days))), 'notice'); return $ret_maybe; } } return array(new ocp_tempcode(), new ocp_tempcode(), 0); } require_code('templates_results_browser'); $results_browser = results_browser(do_lang_tempcode('RESULTS'), NULL, $start, 'start', $max, 'max', $GLOBALS['TOTAL_RESULTS'], NULL, 'results', true, true); if ($start == 0) { $GLOBALS['SITE_DB']->query_insert('searches_logged', array('s_member_id' => get_member(), 's_time' => time(), 's_primary' => substr($content, 0, 255), 's_auxillary' => serialize(array_merge($_POST, $_GET)), 's_num_results' => count($results))); } return array($out, $results_browser, $GLOBALS['TOTAL_RESULTS']); }
/** * Check for existing FAQs matching a ticket to be submitted, via searching. * * @param tempcode Page title * @param string Ticket ID we'd be creating * @param string What is being searched for * @return ?tempcode The search results (NULL: could not search) */ function do_search($title, $ticket_id, $content) { require_code('database_search'); // We don't want to display too many --- just enough to show the top results $max = 10; // Search under all hooks we've asked to search under $results = array(); require_code('hooks/modules/search/catalogue_entries'); $object = object_factory('Hook_search_catalogue_entries'); $info = $object->info(); if (is_null($info)) { return NULL; } // Get the ID of the default FAQ catalogue $catalogue_id = $GLOBALS['SITE_DB']->query_value('catalogue_categories', 'id', array('c_name' => 'faqs')); if (is_null($catalogue_id)) { return NULL; } // Category filter $where_clause = 'r.' . $info['category'] . '=' . strval($catalogue_id); $boolean_operator = 'OR'; $content_where = build_content_where($content, true, $boolean_operator); $hook_results = $object->run($content, false, 'ASC', $max, 0, false, $content_where, '', NULL, NULL, 'relevance', NULL, $boolean_operator, $where_clause, NULL, true); if (is_null($hook_results) || count($hook_results) == 0) { return NULL; } foreach ($hook_results as $i => $result) { $result['object'] = $object; $result['type'] = 'catalogue_entries'; $hook_results[$i] = $result; } $results = sort_search_results($hook_results, array(), 'ASC'); $out = build_search_results_interface($results, 0, $max, 'ASC'); return do_template('SUPPORT_TICKETS_SEARCH_SCREEN', array('_GUID' => '427e28208e15494a8f126eb4fb2aa60c', 'TITLE' => $title, 'URL' => build_url(array('page' => '_SELF', 'id' => $ticket_id, 'type' => 'post'), '_SELF'), 'POST_FIELDS' => build_keep_post_fields(), 'RESULTS' => $out)); }
/** * Get some rows, queried from the database according to the search parameters. * * @param ?ID_TEXT The META type used by our content (NULL: Cannot support META search) * @param ?ID_TEXT The name of the field that retrieved META IDs will relate to (NULL: Cannot support META search) * @param string Search string * @param boolean Whether to do a boolean search. * @param ID_TEXT Boolean operator * @set OR AND * @param boolean Whether to only do a META (tags) search * @param ID_TEXT Order direction * @param integer Start position in total results * @param integer Maximum results to return in total * @param boolean Whether to only search titles (as opposed to both titles and content) * @param ID_TEXT The table name * @param array The translateable fields to search over (or an ! which is skipped). The first of these must be the title field or an '!'; if it is '!' then the title field will be the first raw-field * @param string The WHERE clause * @param string The WHERE clause that applies specifically for content (this will be duplicated to check against multiple fields). ? refers to the yet-unknown field name * @param ID_TEXT What to order by * @param string What to select * @param ?array The non-translateable fields to search over (NULL: there are none) * @param ?string The permission module to check category access for (NULL: none) * @param ?string The field that specifies the permissions ID to check category access for (NULL: none) * @param boolean Whether the permissions field is a string * @return array The rows found */ function get_search_rows($meta_type, $meta_id_field, $content, $boolean_search, $boolean_operator, $only_search_meta, $direction, $max, $start, $only_titles, $table, $fields, $where_clause, $content_where, $order, $select = '*', $raw_fields = NULL, $permissions_module = NULL, $permissions_field = NULL, $permissions_field_is_string = false) { if (substr($where_clause, 0, 5) == ' AND ') { $where_clause = substr($where_clause, 5); } if (substr($where_clause, -5) == ' AND ') { $where_clause = substr($where_clause, 0, strlen($where_clause) - 5); } $where_alternative_matches = array(); $had_limit_imposed = false; if (!is_null($permissions_module) && !$GLOBALS['FORUM_DRIVER']->is_super_admin(get_member())) { $g_or = _get_where_clause_groups(get_member()); // this destroys mysqls query optimiser by forcing complexed OR's into the join, so we'll do this in PHP code // $table.=' LEFT JOIN '.$GLOBALS['SITE_DB']->get_table_prefix().'group_category_access z ON ('.db_string_equal_to('z.module_the_name',$permissions_module).' AND z.category_name='.$permissions_field.(($g_or!='')?(' AND '.str_replace('group_id','z.group_id',$g_or)):'').')'; // $where_clause.=' AND '; // $where_clause.='z.category_name IS NOT NULL'; $cat_access = list_to_map('category_name', $GLOBALS['SITE_DB']->query('SELECT category_name FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'group_category_access WHERE ' . db_string_equal_to('module_the_name', $permissions_module) . ($g_or != '' ? ' AND (' . $g_or . ')' : ''))); } if ($only_titles && array_key_exists(0, $fields) && $fields[0] == '') { return array(); } if (is_null($raw_fields)) { $raw_fields = array(); } $db = substr($table, 0, 2) != 'f_' ? $GLOBALS['SITE_DB'] : $GLOBALS['FORUM_DB']; // This is so for example catalogue_entries.php can use brackets in it's table specifier whilst avoiding the table prefix after the first bracket. A bit weird, but that's our convention and it does save a small amount of typing $table_clause = $db->get_table_prefix() . ($table[0] == '(' ? substr($table, 1) : $table); if ($table[0] == '(') { $table_clause = '(' . $table_clause; } $t_rows = array(); $t_count = 0; // Rating ordering, via special encoding if (substr($order, 0, 7) == '_rating') { list(, $rating_type, $meta_rating_id_field) = explode(':', $order); $select .= ',(SELECT AVG(rating) FROM ' . get_table_prefix() . 'rating WHERE ' . db_string_equal_to('rating_for_type', $rating_type) . ' AND rating_for_id=' . $meta_rating_id_field . ') AS compound_rating'; $order = 'compound_rating'; } $translate_join_type = get_value('alternate_search_join_type') === '1' ? 'LEFT JOIN' : 'JOIN'; // Defined-keywords/tags search if (get_param_integer('keep_just_show_query', 0) == 0 && !is_null($meta_type) && $content != '') { $keywords_where = preg_replace('#\\?#', 'tm.text_original', build_content_where($content, $boolean_search, $boolean_operator, true)); $keywords_where = str_replace(' AND (tm.text_original IS NOT NULL)', '', $keywords_where); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. if ($keywords_where != '') { if ($meta_id_field == 'the_zone:the_page') { $meta_join = 'm.meta_for_id=CONCAT(r.the_zone,\':\',r.the_page)'; } else { $meta_join = 'm.meta_for_id=r.' . $meta_id_field; } $extra_join = ''; foreach ($fields as $i => $field) { if ($field == '' || $field == '!' || strpos($select, 't1.text_original') === false) { continue; } $extra_join .= ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); } if (!db_has_subqueries($db->connection_read) || true) { $_keywords_query = $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . $extra_join; $_keywords_query .= ' WHERE ' . $keywords_where; $_keywords_query .= $where_clause != '' ? ' AND ' . $where_clause : ''; } else { $_keywords_query = $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . $extra_join; $_keywords_query .= ' WHERE ' . $keywords_where; $_keywords_query .= $where_clause != '' ? ' AND tm.id IN (SELECT m.id FROM ' . $table_clause . ' LEFT JOIN ' . $db->get_table_prefix() . 'seo_meta m ON (' . db_string_equal_to('m.meta_for_type', $meta_type) . ' AND ' . $meta_join . ') ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate tm ON tm.id=m.meta_keywords AND ' . db_string_equal_to('tm.language', user_lang()) . ' WHERE ' . $where_clause . ' AND ' . $keywords_where . ')' : ''; } $keywords_query = 'SELECT ' . $select . ' FROM ' . $_keywords_query; $_count_query_keywords_search = 'SELECT COUNT(*) FROM ' . $_keywords_query; $group_by_ok = can_arbitrary_groupby() && $meta_id_field === 'id'; if (strpos($table, ' LEFT JOIN') === false) { $group_by_ok = false; } // Don't actually need to do a group by, as no duplication possible $keywords_query .= $group_by_ok ? ' GROUP BY r.id' : ''; if ($order != '' && $order . ' ' . $direction != 'contextual_relevance DESC') { $keywords_query .= ' ORDER BY ' . $order; if ($direction == 'DESC') { $keywords_query .= ' DESC'; } } if ($group_by_ok) { $_count_query_keywords_search = str_replace('COUNT(*)', 'COUNT(DISTINCT r.id)', $_count_query_keywords_search); } $t_keyword_search_rows_count = $db->query_value_null_ok_full($_count_query_keywords_search); if ($t_keyword_search_rows_count > 500) { $t_keyword_search_rows = $db->query($keywords_query, $max + $start); $had_limit_imposed = true; } else { $t_keyword_search_rows = $db->query($keywords_query); } $t_count += $t_keyword_search_rows_count; $t_rows = array_merge($t_rows, $t_keyword_search_rows); } else { $_count_query_keywords_search = NULL; } } else { $_count_query_keywords_search = NULL; } $orig_table_clause = $table_clause; // Main content search if (!$only_search_meta) { if ($content_where != '' || preg_match('#t\\d+\\.text_original#', $where_clause) != 0 || preg_match('#t\\d+\\.text_original#', $select) != 0) { // Each of the fields represents an 'OR' match, so we put it together into a list ($where_alternative_matches) of specifiers for each. Hopefully we will 'UNION' them rather than 'OR' them as it is much more efficient in terms of table index usage $where_alternative_matches = array(); foreach ($fields as $i => $field) { if (strpos($select, 't' . strval($i) . '.text_original') !== false || strpos($where_clause, 't' . strval($i) . '.text_original') !== false) { $tc_add = ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); $orig_table_clause .= $tc_add; } } foreach ($fields as $i => $field) { if ($field == '' || $field == '!') { continue; } if ($field == $order) { $order = 't' . $i . '.text_original'; } // Ah, remap to the textual equivalent then $tc_add = ' ' . $translate_join_type . ' ' . $db->get_table_prefix() . 'translate t' . strval($i) . ' ON t' . strval($i) . '.id=' . $field . ' AND ' . db_string_equal_to('t' . strval($i) . '.language', user_lang()); if (strpos($orig_table_clause, $tc_add) !== false) { $tc_add = ''; } if (!$only_titles || $i == 0) { $where_clause_2 = preg_replace('#\\?#', 't' . strval($i) . '.text_original', $content_where); $where_clause_2 = str_replace(' AND (t' . strval($i) . '.text_original IS NOT NULL)', '', $where_clause_2); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. $where_clause_3 = $where_clause; if ($table == 'f_members' && substr($field, 0, 6) == 'field_' && db_has_subqueries($db->connection_read)) { $where_clause_3 .= ($where_clause == '' ? '' : ' AND ') . 'NOT EXISTS (SELECT * FROM ' . $db->get_table_prefix() . 'f_cpf_perms cpfp WHERE cpfp.member_id=r.id AND cpfp.field_id=' . substr($field, 6) . ' AND cpfp.guest_view=0)'; } if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $_select = preg_replace('#\\?#', 't' . strval($i) . '.text_original', $content_where) . ' AS contextual_relevance'; $_select = str_replace(' AND (t' . strval($i) . '.text_original IS NOT NULL)', '', $_select); // Not needed for translate joins, as these won't be NULL's. Fixes performance issue. } else { $_select = '1'; } $_table_clause = $orig_table_clause . $tc_add; $where_alternative_matches[] = array($where_clause_2, $where_clause_3, $_select, $_table_clause, 't' . strval($i)); } else { $_table_clause = $orig_table_clause . $tc_add; $where_alternative_matches[] = array('1=0', '', '1', $_table_clause, 't' . strval($i)); } } if ($content_where != '') { foreach ($raw_fields as $i => $field) { if ($only_titles && $i != 0) { break; } $where_clause_2 = preg_replace('#\\?#', $field, $content_where); $where_clause_3 = $where_clause; if ($table == 'f_members' && substr($field, 0, 6) == 'field_' && db_has_subqueries($db->connection_read)) { $where_clause_3 .= ($where_clause == '' ? '' : ' AND ') . 'NOT EXISTS (SELECT * FROM ' . $db->get_table_prefix() . 'f_cpf_perms cpfp WHERE cpfp.member_id=r.id AND cpfp.field_id=' . substr($field, 6) . ' AND cpfp.guest_view=0)'; } if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $_select = preg_replace('#\\?#', $field, $content_where) . ' AS contextual_relevance'; } else { $_select = '1'; } $_table_clause = $orig_table_clause; $where_alternative_matches[] = array($where_clause_2, $where_clause_3, $_select, $_table_clause, NULL); } } } if (count($where_alternative_matches) == 0) { $where_alternative_matches[] = array($where_clause, '', '', $table_clause, NULL); } else { if ($order == '' && db_has_expression_ordering($db->connection_read) && $content_where != '') { $order = 'contextual_relevance DESC'; } } // Work out main query global $SITE_INFO; if (isset($SITE_INFO['mysql_old']) && $SITE_INFO['mysql_old'] == '1' || !isset($SITE_INFO['mysql_old']) && is_file(get_file_base() . '/mysql_old')) { $_query = ''; foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, , ) = $parts; $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $select .= ($_select == '' ? '' : ',') . $_select; $_query .= $where_clause_3 != '' ? ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_3 : ''; } $query = 'SELECT ' . $select . ' FROM ' . $table_clause . $_query; } else { $query = ''; foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, $_table_clause, $tid) = $parts; if ($query != '') { $query .= ' LIMIT ' . strval($max); $query .= ' UNION '; } if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $query .= 'SELECT ' . $select . ($_select == '' ? '' : ',') . $_select . ' FROM ' . $_table_clause . ($where_clause_3 == '' ? '' : ' WHERE ' . $where_clause_3); } else { $query .= 'SELECT ' . $select . ($_select == '' ? '' : ',') . $_select . ' FROM ' . $_table_clause; if ($where_clause_2 != '' || $where_clause_3 != '') { $query .= ' WHERE ' . $where_clause_2; $query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } } } } // Work out COUNT(*) query using one of a few possible methods. It's not efficient and stops us doing proper merge-sorting between content types (and possible not accurate - if we use an efficient but non-deduping COUNT strategy) if we have to use this, so we only do it if there are too many rows to fetch in one go. $_query = ''; if (isset($SITE_INFO['mysql_old']) && $SITE_INFO['mysql_old'] == '1' || !isset($SITE_INFO['mysql_old']) && is_file(get_file_base() . '/mysql_old') || strpos(get_db_type(), 'mysql') === false) { foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, , $_table_clause, $tid) = $parts; if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $_query .= $where_clause_3 != '' ? ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_3 : ''; } else { if ($where_clause_2 != '' || $where_clause_3 != '') { $_query .= ($_query == '' ? ' WHERE ' : ' OR ') . $where_clause_2; $_query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } } } $_count_query_main_search = 'SELECT COUNT(*) FROM ' . $table_clause . $_query; } else { foreach ($where_alternative_matches as $parts) { list($where_clause_2, $where_clause_3, $_select, $_table_clause, $tid) = $parts; if ($_query != '') { $_query .= '+'; } if (!db_has_subqueries($db->connection_read) || is_null($tid) || $content_where == '' || true) { $where_clause_3 = $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3); $_query .= '(SELECT COUNT(*) FROM ' . $_table_clause . ($where_clause_3 == '' ? '' : ' WHERE ' . $where_clause_3) . ')'; } else { $_query .= '(SELECT COUNT(*) FROM ' . $_table_clause; if ($where_clause_2 != '' || $where_clause_3 != '') { $_query .= ' WHERE ' . $where_clause_2; $_query .= $where_clause_3 != '' ? ($where_clause_2 == '' ? '' : ' AND ') . $tid . '.id IN (SELECT ' . $tid . '.id FROM ' . $_table_clause . ' WHERE ' . $where_clause_2 . ($where_clause_3 == '' ? '' : ($where_clause_2 == '' ? '' : ' AND ') . $where_clause_3) . ')' : ''; } $_query .= ')'; } } $_count_query_main_search = 'SELECT (' . $_query . ')'; } $group_by_ok = can_arbitrary_groupby() && $meta_id_field === 'id'; if (strpos($table, ' LEFT JOIN') === false) { $group_by_ok = false; } // Don't actually need to do a group by, as no duplication possible. We want to avoid GROUP BY as it forces MySQL to create a temporary table, slowing things down a lot. $query .= $group_by_ok ? ' GROUP BY r.id' : ''; if ($order != '' && $order . ' ' . $direction != 'contextual_relevance DESC') { $query .= ' ORDER BY ' . $order; if ($direction == 'DESC' && substr($order, -4) != ' ASC' && substr($order, -5) != ' DESC') { $query .= ' DESC'; } } if (get_param_integer('keep_show_query', 0) == 1) { attach_message($query, 'inform'); } if (get_param_integer('keep_just_show_query', 0) == 1) { @ini_set('ocproducts.xss_detect', '0'); header('Content-type: text/plain; charset=' . get_charset()); exit($query); } if ($group_by_ok) { $_count_query_main_search = str_replace('COUNT(*)', 'COUNT(DISTINCT r.id)', $_count_query_main_search); } $t_main_search_rows_count = $db->query_value_null_ok_full($_count_query_main_search); if ($t_main_search_rows_count > 500) { $t_main_search_rows = $db->query($query, $max + $start, NULL, false, true); $had_limit_imposed = true; } else { $t_main_search_rows = $db->query($query, NULL, NULL, false, true); } if ($t_main_search_rows === NULL) { $t_main_search_rows = array(); } // In case of a failed search query $t_count += $t_main_search_rows_count; $t_rows = array_merge($t_rows, $t_main_search_rows); } else { $t_main_search_rows = array(); } // Clean results and return // NB: We don't use the count_query's any more (except when using huge data sets, see above), because you can't actually just add them because they overlap. So instead we fetch all results and throw some away. $t_rows = array_merge($t_rows, $t_main_search_rows); if (count($t_rows) > 0) { $t_rows_new = array(); if (array_key_exists('id', $t_rows[0]) || array_key_exists('_primary_id', $t_rows[0])) { $done = array(); foreach ($t_rows as $t_row) { if (array_key_exists('id', $t_row)) { if (array_key_exists($t_row['id'], $done)) { continue; } $done[$t_row['id']] = 1; } elseif (array_key_exists('_primary_id', $t_row)) { if (array_key_exists($t_row['_primary_id'], $done)) { continue; } $done[$t_row['_primary_id']] = 1; } $t_rows_new[] = $t_row; } } else { foreach ($t_rows as $t_row) { unset($t_row['contextual_relevance']); foreach ($t_rows_new as $_t_row) { if ($_t_row == $t_row || array_key_exists('id', $t_row) && array_key_exists('id', $_t_row) && !array_key_exists('_primary_id', $t_row) && !array_key_exists('_primary_id', $_t_row) && $t_row['id'] == $_t_row['id'] || array_key_exists('_primary_id', $t_row) && array_key_exists('_primary_id', $_t_row) && $t_row['_primary_id'] == $_t_row['_primary_id']) { continue 2; } } $t_rows_new[] = $t_row; } } $t_rows = $t_rows_new; } if (get_param_integer('keep_show_query', 0) == 1) { if (array_key_exists(0, $t_rows) && array_key_exists('id', $t_rows[0])) { $results = var_export(array_unique(collapse_1d_complexity('id', $t_rows)), true); } else { $results = var_export($t_rows, true); } attach_message(do_lang('_RESULTS') . ': ' . $results, 'inform'); } if (isset($cat_access)) { $before = count($t_rows); foreach ($t_rows as $i => $row) { if (!array_key_exists(@strval($row[$permissions_field]), $cat_access)) { unset($t_rows[$i]); } } } $final_result_rows = $t_rows; if (!$had_limit_imposed) { // More accurate, as filtered for dupes $t_count = count($t_rows); } $GLOBALS['TOTAL_RESULTS'] += $t_count; array_splice($final_result_rows, $max * 2 + $start); // We return more than max in case our search hook does some extra in-code filtering (Catalogues, Comcode pages). It shouldn't really but sometimes it has to, and it certainly shouldn't filter more than 50%. Also so our overall ordering can be better. return $final_result_rows; }