/** * Process the filters for the query string * * @param vB_Legacy_Current_User $user user requesting the search * @param vB_Search_Criteria $criteria search criteria to process */ protected function process_keywords_filters(vB_Search_Criteria &$criteria) { $keywords = $criteria->get_keywords(); // nothing to process if (empty($keywords)) { return; } $words = array(); // get the map table names for the keywords. these tables will be joined into the search query $has_or_joiner = false; foreach ($keywords as $word_details) { $suffix = vBDBSearch_Core::get_table_name($word_details['word']); //$words[$suffix][$clean_word] = array('wordid'=>false,'joiner'=>$word['joiner']); $words[$word_details['word']] = array('suffix' => $suffix, 'word' => $word_details['word'], 'joiner' => $word_details['joiner']); if ($word_details['joiner'] == "OR") { $has_or_joiner = true; } } // nothing to process if (empty($words)) { return; } $set = $this->db->query_read_slave($query = "\n\t\t\t\t\tSELECT *\n\t\t\t\t\tFROM " . TABLE_PREFIX . "words as words\n\t\t\t\t\tWHERE " . self::make_equals_filter('words', 'word', array_keys($words))); $config = vB::getConfig(); if (!empty($config['Misc']['debug_sql']) or self::DEBUG) { echo "{$query};\n"; } $wordids = array(); while ($word_details = $this->db->fetch_array($set)) { $wordids[$word_details['word']] = $word_details['wordid']; } $this->db->free_result($set); $word_details = array(); foreach ($words as $word => $details) { // if the word was not found if (!isset($wordids[$word])) { // and it's not with a NOT or OR operator if (!$has_or_joiner and $details['joiner'] != 'NOT') { // this word is not indexed so there is nothing to return $this->where[] = "0 /** word is not indexed **/"; $this->sort = array('node.created' => 'ASC'); return; } // still need to add this word to the mix (either as a NOT operator or maybe as an OR). we use the word itself as a key to make it unique $key = $word; $details['wordid'] = 0; } else { $key = $details['wordid'] = $wordids[$word]; } $word_details[$key] = $details; } unset($wordids); unset($words); if (count($word_details) == 1) { $this->process_one_word_rank(array_pop($word_details), $criteria->is_title_only()); } elseif ($has_or_joiner or isset($this->sort['rank'])) { $this->process_existing_words_or($word_details, $criteria->is_title_only()); } else { $this->process_existing_words_and($word_details, $criteria->is_title_only()); } }
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; }