Beispiel #1
0
 /**
  * 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']);
 }
Beispiel #2
0
 /**
  * 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));
 }
Beispiel #3
0
/**
 * 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;
}