/**
  * @return array(string)
  */
 public function getServerList()
 {
     // This clause provides backwards compatability with previous versions
     // of CirrusSearch. Once this variable is removed cluster configuration
     // will work as expected.
     if ($this->config->has('CirrusSearchServers')) {
         return $this->config->get('CirrusSearchServers');
     } else {
         return $this->config->getElement('CirrusSearchClusters', $this->cluster);
     }
 }
 /**
  * @param string $query
  * @param boolean $isRescore
  * @return \Elastica\Query\Simple
  */
 public function wrapInSaferIfPossible($query, $isRescore)
 {
     // @todo: move this code to a common base class when Filters is refactored as non-static
     $saferQuery = $this->config->getElement('CirrusSearchWikimediaExtraPlugin', 'safer');
     if (is_null($saferQuery)) {
         return $query;
     }
     $saferQuery['query'] = $query->toArray();
     $tooLargeAction = $isRescore ? 'convert_to_match_all_query' : 'convert_to_term_queries';
     $saferQuery['phrase']['phrase_too_large_action'] = $tooLargeAction;
     return new \Elastica\Query\Simple(array('safer' => $saferQuery));
 }
 private function decideCluster(SearchConfig $config)
 {
     $cluster = $this->getOption('cluster', null);
     if ($cluster === null) {
         return null;
     }
     if ($config->has('CirrusSearchServers')) {
         $this->error('Not configured for cluster operations.', 1);
     }
     $hosts = $config->getElement('CirrusSearchClusters', $cluster);
     if ($hosts === null) {
         $this->error('Unknown cluster.', 1);
     }
     return $cluster;
 }
 /**
  * If there is any boosting to be done munge the the current query to get it right.
  */
 private function installBoosts()
 {
     // Quick note:  At the moment ".isEmpty()" is _much_ faster then ".empty".  Never
     // use ".empty".  See https://github.com/elasticsearch/elasticsearch/issues/5086
     if ($this->sort !== 'relevance') {
         // Boosts are irrelevant if you aren't sorting by, well, relevance
         return;
     }
     $functionScore = new \Elastica\Query\FunctionScore();
     $useFunctionScore = false;
     // Customize score by boosting based on incoming links count
     if ($this->boostLinks) {
         $useFunctionScore = true;
         if ($this->config->getElement('CirrusSearchWikimediaExtraPlugin', 'field_value_factor_with_default')) {
             $functionScore->addFunction('field_value_factor_with_default', array('field' => 'incoming_links', 'modifier' => 'log2p', 'missing' => 0));
         } else {
             $scoreBoostExpression = "log10(doc['incoming_links'].value + 2)";
             $functionScore->addScriptScoreFunction(new \Elastica\Script($scoreBoostExpression, null, 'expression'));
         }
     }
     // Customize score by decaying a portion by time since last update
     if ($this->preferRecentDecayPortion > 0 && $this->preferRecentHalfLife > 0) {
         // Convert half life for time in days to decay constant for time in milliseconds.
         $decayConstant = log(2) / $this->preferRecentHalfLife / 86400000;
         $parameters = array('decayConstant' => $decayConstant, 'decayPortion' => $this->preferRecentDecayPortion, 'nonDecayPortion' => 1 - $this->preferRecentDecayPortion, 'now' => time() * 1000);
         // e^ct where t is last modified time - now which is negative
         $exponentialDecayExpression = "exp(decayConstant * (doc['timestamp'].value - now))";
         if ($this->preferRecentDecayPortion !== 1.0) {
             $exponentialDecayExpression = "{$exponentialDecayExpression} * decayPortion + nonDecayPortion";
         }
         $functionScore->addScriptScoreFunction(new \Elastica\Script($exponentialDecayExpression, $parameters, 'expression'));
         $useFunctionScore = true;
     }
     // Add boosts for pages that contain certain templates
     if ($this->boostTemplates) {
         foreach ($this->boostTemplates as $name => $boost) {
             $match = new \Elastica\Query\Match();
             $match->setFieldQuery('template', $name);
             $filterQuery = new \Elastica\Filter\Query($match);
             $filterQuery->setCached(true);
             $functionScore->addBoostFactorFunction($boost, $filterQuery);
         }
         $useFunctionScore = true;
     }
     // Add boosts for namespaces
     $namespacesToBoost = $this->namespaces ?: MWNamespace::getValidNamespaces();
     if ($namespacesToBoost) {
         // Group common weights together and build a single filter per weight
         // to save on filters.
         $weightToNs = array();
         foreach ($namespacesToBoost as $ns) {
             $weight = $this->getBoostForNamespace($ns);
             $weightToNs[(string) $weight][] = $ns;
         }
         if (count($weightToNs) > 1) {
             unset($weightToNs['1']);
             // That'd be redundant.
             foreach ($weightToNs as $weight => $namespaces) {
                 $filter = new \Elastica\Filter\Terms('namespace', $namespaces);
                 $functionScore->addBoostFactorFunction($weight, $filter);
                 $useFunctionScore = true;
             }
         }
     }
     // Boost pages in a user's language
     $userLang = $this->config->getUserLanguage();
     $userWeight = $this->config->getElement('CirrusSearchLanguageWeight', 'user');
     if ($userWeight) {
         $functionScore->addBoostFactorFunction($userWeight, new \Elastica\Filter\Term(array('language' => $userLang)));
         $useFunctionScore = true;
     }
     // And a wiki's language, if it's different
     $wikiWeight = $this->config->getElement('CirrusSearchLanguageWeight', 'wiki');
     if ($userLang != $this->config->get('LanguageCode') && $wikiWeight) {
         $functionScore->addBoostFactorFunction($wikiWeight, new \Elastica\Filter\Term(array('language' => $this->config->get('LanguageCode'))));
         $useFunctionScore = true;
     }
     if (!$useFunctionScore) {
         // Nothing to do
         return;
     }
     // The function score is done as a rescore on top of everything else
     $this->rescore[] = array('window_size' => $this->config->get('CirrusSearchFunctionRescoreWindowSize'), 'query' => array('rescore_query' => $functionScore, 'query_weight' => 1.0, 'rescore_query_weight' => 1.0, 'score_mode' => 'multiply'));
 }
 /**
  * Produce a set of completion suggestions for text using _suggest
  * See https://www.elastic.co/guide/en/elasticsearch/reference/1.6/search-suggesters-completion.html
  *
  * WARNING: experimental API
  *
  * @param string $text Search term
  * @param string[]|null $variants Search term variants
  * (usually issued from $wgContLang->autoConvertToAllVariants( $text ) )
  * @param array $context
  * @return Status
  */
 public function suggest($text, $variants = null, $context = null)
 {
     $this->setTermAndVariants($text, $variants);
     $this->context = $context;
     list($profiles, $suggest) = $this->buildQuery();
     $queryOptions = array();
     $queryOptions['timeout'] = $this->config->getElement('CirrusSearchSearchShardTimeout', 'default');
     $this->connection->setTimeout($queryOptions['timeout']);
     $index = $this->connection->getIndex($this->indexBaseName, Connection::TITLE_SUGGEST_TYPE);
     $logContext = array('query' => $text, 'queryType' => $this->queryType);
     $searcher = $this;
     $limit = $this->limit;
     $result = Util::doPoolCounterWork('CirrusSearch-Search', $this->user, function () use($searcher, $index, $suggest, $logContext, $queryOptions, $profiles, $text, $limit) {
         $description = "{queryType} search for '{query}'";
         $searcher->start($description, $logContext);
         try {
             $result = $index->request("_suggest", Request::POST, $suggest, $queryOptions);
             if ($result->isOk()) {
                 $result = $searcher->postProcessSuggest($result, $profiles, $limit);
                 return $searcher->success($result);
             }
             return $result;
         } catch (\Elastica\Exception\ExceptionInterface $e) {
             return $searcher->failure($e);
         }
     });
     return $result;
 }