/**
* 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->addWeightFunction($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->addWeightFunction($weight, $filter);
$useFunctionScore = true;
}
}
}
// Boost pages in a user's language
$userLang = $this->config->getUserLanguage();
$userWeight = $this->config->getElement('CirrusSearchLanguageWeight', 'user');
if ($userWeight) {
$functionScore->addWeightFunction($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->addWeightFunction($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'));
}