Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.08% covered (warning)
77.08%
37 / 48
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
NamespacesFunctionScoreBuilder
77.08% covered (warning)
77.08%
37 / 48
33.33% covered (danger)
33.33%
1 / 3
26.31
0.00% covered (danger)
0.00%
0 / 1
 __construct
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
10.81
 getBoostForNamespace
64.29% covered (warning)
64.29%
9 / 14
0.00% covered (danger)
0.00%
0 / 1
7.64
 append
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace CirrusSearch\Search\Rescore;
4
5use CirrusSearch\SearchConfig;
6use Elastica\Query\FunctionScore;
7use MediaWiki\Logger\LoggerFactory;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Title\NamespaceInfo;
10
11/**
12 * Builds a set of functions with namespaces.
13 * Uses a weight function with a filter for each namespace.
14 * Activated only if more than one namespace is requested.
15 */
16class NamespacesFunctionScoreBuilder extends FunctionScoreBuilder {
17    /**
18     * @var null|float[] initialized version of $wgCirrusSearchNamespaceWeights with all string keys
19     * translated into integer namespace codes using $this->language.
20     */
21    private $normalizedNamespaceWeights;
22
23    /**
24     * @var int[] List of namespace id's
25     */
26    private $namespacesToBoost;
27
28    /**
29     * @var NamespaceInfo
30     */
31    private $namespaceInfo;
32
33    /**
34     * @param SearchConfig $config
35     * @param int[]|null $namespaces
36     * @param float $weight
37     * @param NamespaceInfo|null $namespaceInfo
38     */
39    public function __construct( SearchConfig $config, $namespaces, $weight, ?NamespaceInfo $namespaceInfo = null ) {
40        parent::__construct( $config, $weight );
41
42        $this->namespaceInfo = $namespaceInfo ?: MediaWikiServices::getInstance()->getNamespaceInfo();
43        $this->namespacesToBoost =
44            $namespaces ?: $this->namespaceInfo->getValidNamespaces();
45        if ( !$this->namespacesToBoost || count( $this->namespacesToBoost ) == 1 ) {
46            // nothing to boost, no need to initialize anything else.
47            return;
48        }
49        $this->normalizedNamespaceWeights = [];
50        foreach ( $config->get( 'CirrusSearchNamespaceWeights' ) as $ns =>
51                  $weight
52        ) {
53            if ( !is_int( $ns ) && !ctype_digit( $ns ) ) {
54                LoggerFactory::getInstance( 'CirrusSearch' )->warning(
55                    'Namespace names are no longer accepted as namespaces in '
56                    . "CirrusSearchNamespaceWeights. Ignoring {invalid_ns}",
57                    [ 'invalid_ns' => $ns ]
58                );
59
60                continue;
61            }
62            // Now $ns should always be an integer.
63            $this->normalizedNamespaceWeights[(int)$ns] = $weight;
64        }
65    }
66
67    /**
68     * Get the weight of a namespace.
69     *
70     * @param int $namespace
71     * @return float the weight of the namespace
72     */
73    private function getBoostForNamespace( $namespace ) {
74        if ( isset( $this->normalizedNamespaceWeights[$namespace] ) ) {
75            return $this->normalizedNamespaceWeights[$namespace];
76        }
77        if ( $this->namespaceInfo->isSubject( $namespace ) ) {
78            if ( $namespace === NS_MAIN ) {
79                return 1;
80            }
81
82            return $this->config->get( 'CirrusSearchDefaultNamespaceWeight' );
83        }
84        $subjectNs = $this->namespaceInfo->getSubject( $namespace );
85        if ( isset( $this->normalizedNamespaceWeights[$subjectNs] ) ) {
86            return $this->config->get( 'CirrusSearchTalkNamespaceWeight' ) *
87                   $this->normalizedNamespaceWeights[$subjectNs];
88        }
89        if ( $namespace === NS_TALK ) {
90            return $this->config->get( 'CirrusSearchTalkNamespaceWeight' );
91        }
92
93        return $this->config->get( 'CirrusSearchDefaultNamespaceWeight' ) *
94               $this->config->get( 'CirrusSearchTalkNamespaceWeight' );
95    }
96
97    public function append( FunctionScore $functionScore ) {
98        if ( !$this->namespacesToBoost || count( $this->namespacesToBoost ) == 1 ) {
99            // nothing to boost, no need to initialize anything else.
100            return;
101        }
102
103        // first build the opposite map, this will allow us to add a
104        // single factor function per weight by using a terms filter.
105        $weightToNs = [];
106        foreach ( $this->namespacesToBoost as $ns ) {
107            $weight = $this->getBoostForNamespace( $ns ) * $this->weight;
108            $key = (string)$weight;
109            if ( $key == '1' ) {
110                // such weights would have no effect
111                // we can ignore them.
112                continue;
113            }
114            if ( !isset( $weightToNs[$key] ) ) {
115                $weightToNs[$key] = [
116                    'weight' => $weight,
117                    'ns' => [ $ns ]
118                ];
119            } else {
120                $weightToNs[$key]['ns'][] = $ns;
121            }
122        }
123        foreach ( $weightToNs as $weight => $namespacesAndWeight ) {
124            $filter = new \Elastica\Query\Terms( 'namespace', $namespacesAndWeight['ns'] );
125            $functionScore->addWeightFunction( $namespacesAndWeight['weight'], $filter );
126        }
127    }
128}