public function getSearchResults($params, $db, $check_only = false) { if ($check_only) { return !empty($params['criteria']); } //No cleaning done we only expect the criteria object $this->db = $db; $criteria =& $params['criteria']; $cacheKey = $params['cacheKey']; $this->filters = array('make_equals_filter' => $criteria->get_equals_filters(), 'make_notequals_filter' => $criteria->get_notequals_filters(), 'make_range_filter' => $criteria->get_range_filters()); $this->process_sort($criteria); $this->process_keywords_filters($criteria); if (!empty($this->filters['make_equals_filter']['view'])) { $this->process_view_filters($this->filters['make_equals_filter']['view']); unset($this->filters['make_equals_filter']['view']); } if (!empty($this->filters['make_equals_filter']['follow'])) { $this->process_follow_filters($this->filters['make_equals_filter']['follow'], $criteria); unset($this->filters['make_equals_filter']['follow']); } // channel if (!empty($this->filters['make_equals_filter']['my_channels'])) { $this->process_my_channels_filter($this->filters['make_equals_filter']['my_channels']); unset($this->filters['make_equals_filter']['my_channels']); } //handle equals filters $this->process_filters($criteria, 'make_equals_filter', $db, $cacheKey ? true : false); //handle notequals filters $this->process_filters($criteria, 'make_notequals_filter', $db, $cacheKey ? true : false); //handle range filters $this->process_filters($criteria, 'make_range_filter', $db, $cacheKey ? true : false); $query_joins = ""; if (count($this->join)) { $query_joins = implode(" \n\t\t\t\t", $this->join) . " "; } if (!$this->done_permission_check) { $permflags = $this->getNodePermTerms($cacheKey ? true : false); if (strpos($query_joins, 'AS starter') !== false and !empty($permflags['joins']['starter'])) { //we don't need the starter join. We already have that. unset($permflags['joins']['starter']); } if (!empty($permflags['joins'])) { $query_joins .= implode("\n", $permflags['joins']) . "\n"; } } else { $permflags = array('joins' => false, 'where' => false); } $query_where = ""; if (count($this->where)) { $query_where = "WHERE " . implode(" AND \n\t\t\t\t", $this->where); } else { if (!empty($permflags['where'])) { $query_where = " WHERE "; } } $query_where .= $permflags['where'] . "\n"; $query_limit = false; if (!$criteria->getNoLimit()) { $maxresults = vB::getDatastore()->getOption('maxresults'); $maxresults = $maxresults > 0 ? $maxresults : 0; if (!empty($maxresults)) { $query_limit = "LIMIT " . $maxresults; } } $query_what = $this->what; // Add starter info to result set so that we know what to remove for the second pass. if ($cacheKey) { $userdata = vB::getUserContext()->getAllChannelAccess(); if (!empty($userdata['selfonly'])) { $query_what .= ", starter.parentid, starter.userid"; if (strpos($query_joins, 'node AS starter') === false) { $query_joins .= "\nLEFT JOIN " . TABLE_PREFIX . "node AS starter ON starter.nodeid = IF(node.starter = 0, node.nodeid, node.starter)\n"; } } } $query_order = false; $ordercriteria = array(); $union_what = array($query_what); $union_order = array(); if ($criteria->get_include_sticky()) { if (!empty($this->join['closure'])) { $ordercriteria[] = 'closure.depth ASC'; } $ordercriteria[] = 'node.sticky DESC'; $union_what[] = 'node.sticky'; $union_order[] = 'sticky DESC'; } if ($criteria->getIncludeStarter()) { $ordercriteria[] = 'isstarter DESC'; $query_what .= ', IF(node.nodeid = node.starter, 1, 0) as isstarter'; $union_what[] = 'IF(node.nodeid = node.starter, 1, 0) as isstarter'; $union_order[] = 'isstarter DESC'; } foreach ($this->sort as $field => $dir) { //fall back to default search when no table is joined in that contains score fields if ($field == 'rank' and strpos($query_joins, 'temp_search') === false and strpos($query_joins, 'searchtowords_') === false) { $field = 'node.created'; } if ($field != 'rank') { $ordercriteria[] = $field . " " . $dir; $field_pieces = explode('.', $field); $union_what[] = $field; $union_order[] = array_pop($field_pieces) . " " . $dir; } else { //we need to use the temporary table to compute the final score if (!empty($this->join['temp_search']) and strpos($this->join['temp_search'], 'temp_search AS temp_search')) { $scorefield = "(\n\t\t\t\t\t\tscore *\n\t\t\t\t\t\t(words_nr / (words_nr + " . $this->occurance_factor . ")) *\n\t\t\t\t\t\tGREATEST(1, 5 - ((UNIX_TIMESTAMP() - node.created)/" . $this->date_factor . ")) *\n\t\t\t\t\t\tIF(is_title > 0 , " . $this->title_boost . ", 1) / GREATEST(\n\t\t\t\t\t\t\t(4 * (words_nr - 1) / distance) + 1\n\t\t\t\t\t\t\t, 1)\n\t\t\t\t\t)"; } else { $scorefield = "(\n\t\t\t\t\tscore *\n\t\t\t\t\tGREATEST(1, 5 - ((UNIX_TIMESTAMP() - node.created)/" . $this->date_factor . ")) *\n\t\t\t\t\tIF(is_title > 0 , " . $this->title_boost . ", 1))"; } $ordercriteria[] = $scorefield . $dir; $union_what[] = $scorefield . ' AS rank'; $union_order[] = 'rank ' . $dir; } } // Adding to be always ordered by nodeid in the end. See VBV-4898 $ordercriteria[] = "node.nodeid ASC"; $union_what[] = 'node.nodeid AS nodeid2'; $union_order[] = 'nodeid2 ASC'; // we need to use union in some case to be able to take advantage of the table indexes if (!empty($this->union_condition)) { $unions = array(); $counter = 0; foreach ($this->union_condition as $conditions) { $qjoins = $query_joins; // we need to duplicate the temp_search table because in mysql you can have only one instance of a temp table in query if ($counter > 0 and strpos($query_joins, 'temp_search AS temp_search') !== false) { $tablename = "temp_search{$counter}"; if ($this->db->query_first("SHOW TABLES LIKE '" . TABLE_PREFIX . "{$tablename}'")) { $this->db->query_write($query = "TRUNCATE TABLE " . TABLE_PREFIX . $tablename); } else { $this->db->query_write($query = "CREATE TABLE " . TABLE_PREFIX . "{$tablename} LIKE " . TABLE_PREFIX . "temp_search"); } $this->db->query_write($query = "INSERT INTO " . TABLE_PREFIX . "{$tablename} SELECT * FROM " . TABLE_PREFIX . "temp_search"); $qjoins = str_replace('temp_search AS temp_search', "{$tablename} AS temp_search", $query_joins); } $unions[] = "\n\t\t\t(\n\t\t\t\tSELECT " . implode(", ", $union_what) . "\n\t\t\t\tFROM " . TABLE_PREFIX . $this->from . "\n\t\t\t\t{$qjoins}\n\t\t\t\t" . $query_where . "\t\t\t\tAND " . implode(" AND \n\t\t\t\t", $conditions) . "\n\t\t\t)"; $counter++; } $query = implode("\n\t\t\tUNION", $unions) . "\n\t\t\t" . "ORDER BY " . implode(',', $union_order); } else { $query = "\n\t\t\tSELECT {$query_what}\n\t\t\tFROM " . TABLE_PREFIX . $this->from . "\n\t\t\t{$query_joins}\n\t\t\t{$query_where}\n\t\t\tORDER BY " . implode(',', $ordercriteria); } $query .= "\n\t\t\t{$query_limit}\n\t\t\t" . "\n/**" . __FUNCTION__ . (defined('THIS_SCRIPT') ? '- ' . THIS_SCRIPT : '') . "**/"; $resultclass = 'vB_dB_' . $this->db_type . '_Result'; $config = vB::getConfig(); if (!empty($config['Misc']['debug_sql']) or self::DEBUG) { echo "{$query};\n"; } $post_processors = $criteria->get_post_processors(); if (empty($post_processors)) { $res = array(); $results = new $resultclass($db, $query); if ($cacheKey) { if ($results and $results->valid()) { foreach ($results as $item) { $res[$item['nodeid']] = $item; } } if (!empty($this->filters['make_equals_filter']['channelid'])) { $moreres = vB_Search_Core::saveSecondPassResults($res, $cacheKey, $this->filters['make_equals_filter']['channelid']); } else { $moreres = vB_Search_Core::saveSecondPassResults($res, $cacheKey); } $obj = new ArrayObject($moreres); return $obj->getIterator(); } else { return $results; } } $resultset = new $resultclass($db, $query); foreach ($post_processors as $post_processor) { // get the node ids from the current $resultset $results = array(); foreach ($resultset as $result) { $results[] = $result['nodeid']; } // nothing else to process if (empty($results)) { break; } // create new $resultset based on those node ids $resultset = vB::getDbAssertor()->assertQuery('vBDBSearch:' . $post_processor, array('nodeids' => $results, 'criteria' => $criteria)); } return $resultset; }
public function getResults(vB_Search_Criteria $criteria) { $config = vB::getConfig(); $vBDBSearch_Core = new vBDBSearch_Core(); $keywords = $criteria->get_raw_keywords(); // if there are no keywords, fall back to DB search if (empty($keywords)) { return $vBDBSearch_Core->getResults($criteria); } $results = $this->getTwoPassResults($criteria); //getTwoPassResults will return an array of results or a string cache key if (is_array($results)) { $nodeids = array(); foreach ($results as $nodeid => $node) { $nodeids[$nodeid] = $nodeid; } return $nodeids; } else { $cacheKey = $results; } $this->filters = array('make_equals_filter' => $criteria->get_equals_filters(), 'make_notequals_filter' => $criteria->get_notequals_filters(), 'make_range_filter' => $criteria->get_range_filters()); $this->process_sort($criteria); if (!empty($this->filters['make_equals_filter']['view'])) { $this->process_view_filters($this->filters['make_equals_filter']['view']); unset($this->filters['make_equals_filter']['view']); } if (!empty($this->filters['make_equals_filter']['follow'])) { $this->process_follow_filters($this->filters['make_equals_filter']['follow'], $criteria); unset($this->filters['make_equals_filter']['follow']); } // my channels if (!empty($this->filters['make_equals_filter']['my_channels'])) { $this->process_my_channels_filter($this->filters['make_equals_filter']['my_channels']); unset($this->filters['make_equals_filter']['my_channels']); } //handle equals filters $this->process_filters($criteria, 'make_equals_filter', $cacheKey ? true : false); //handle notequals filters $this->process_filters($criteria, 'make_notequals_filter', $cacheKey ? true : false); //handle range filters $this->process_filters($criteria, 'make_range_filter', $cacheKey ? true : false); $this->setNodePermTerms($cacheKey ? true : false); //Sphinx doesn't recognize 'OR' as a valid operator. Not only does this break our //search tests, it will confuse users who try to use db search queries on sphinx. $keywords = $criteria->get_raw_keywords(); $keywords = str_replace(' OR ', ' | ', $keywords); $field = $criteria->is_title_only() ? '@title ' : ''; array_unshift($this->where, "MATCH('{$field}" . $this->sphinxEscapeKeywords($keywords) . "')"); $post_processors = $criteria->get_post_processors(); $query_limit = false; if (!$criteria->getNoLimit()) { $maxresults = vB::getDatastore()->getOption('maxresults'); $maxresults = $maxresults > 0 ? $maxresults : 0; /* This is a hacky compromise (ew) and not fully tested, and we may end up taking it out or modifying it. * This is to handle the possibility that a post sphinx-search process (like when using the unread_only * filter) can severely limit the # of search results after it was already limited by $maxresults. Users * might be expecting that the very end result set is limited by the maxresults option, meaning we should * place the LIMIT after the post processes, but if we don't place a limit on sphinx search, it might * happily return all the nodes ever (not a very useful search in that case, but could happen). So the * compromise is to use an "expected post process cull ratio" so that the final result is closer to the * $maxresults. This means that any post processors should limit the max results themselves! */ if (!empty($post_processors)) { // 1.5 is a completely arbitrary value. We have absolutely no research to show that 1.5 is the best ratio, but // it just seems like a nice and conservative starting point. $maxresults = $maxresults * 1.5; } if (!empty($maxresults)) { $query_limit = "LIMIT " . $maxresults; } } $groupby = ''; if (!empty($this->groupby)) { $groupby = ' GROUP BY ' . $this->groupby; } $query = ' SELECT ' . implode(', ', $this->what) . ' FROM ' . $this->table . ' WHERE ' . implode(' AND ', $this->where) . $groupby . ' ORDER BY ' . implode(', ', $this->sort) . "\n\t\t\t\t{$query_limit}\n\t\t\t/**" . __FUNCTION__ . (defined('THIS_SCRIPT') ? '- ' . THIS_SCRIPT : '') . "**/"; $res = $this->sphinxDB->query($query); if (!empty($config['Misc']['debug_sql']) or self::DEBUG) { echo "{$query};\n"; } if ($cacheKey) { $this->what[] = 'starteruser'; $this->what[] = 'starterparent'; } $results = array(); $nodeids = array(); while ($row = $this->sphinxDB->fetch_array($res)) { $value = current($row); $nodeids[$value] = $value; if ($cacheKey) { $results[$value] = array('nodeid' => $value, 'parentid' => $row['starterparent'], 'userid' => $row['starteruser']); } } if (empty($nodeids)) { return array(); } if (empty($post_processors)) { if ($cacheKey) { $nodeids = array(); if (!empty($this->filters['make_equals_filter']['channelid'])) { $results = vB_Search_Core::saveSecondPassResults($results, $cacheKey, $this->filters['make_equals_filter']['channelid']); } else { $results = vB_Search_Core::saveSecondPassResults($results, $cacheKey); } foreach ($results as $nodeid => $node) { $nodeids[$nodeid] = $nodeid; } } } else { foreach ($post_processors as $post_processor) { $vBSphinxSearch_Core = new vBSphinxSearch_Core(); // why do we need a new instance every time...? $nodeids = $vBSphinxSearch_Core->{$post_processor}($nodeids, $criteria); } if (empty($nodeids)) { return array(); } $criteria->reset_post_processors(); } return $nodeids; }