示例#1
0
 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;
 }
示例#2
0
    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;
    }