Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
81 / 90
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SiteMatrixInterwikiResolver
90.00% covered (success)
90.00%
81 / 90
25.00% covered (danger)
25.00%
1 / 4
33.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 accepts
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 loadMatrix
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 siteMatrixLoader
91.78% covered (success)
91.78%
67 / 73
0.00% covered (danger)
0.00%
0 / 1
24.32
1<?php
2
3namespace CirrusSearch;
4
5use MediaWiki\Extension\SiteMatrix\SiteMatrix;
6use MediaWiki\Interwiki\InterwikiLookup;
7use MediaWiki\Registration\ExtensionRegistry;
8use MediaWiki\WikiMap\WikiMap;
9use Wikimedia\Http\MultiHttpClient;
10use Wikimedia\ObjectCache\WANObjectCache;
11
12/**
13 * InterwikiResolver suited for WMF context and uses SiteMatrix.
14 */
15class SiteMatrixInterwikiResolver extends BaseInterwikiResolver {
16
17    private const MATRIX_CACHE_TTL = 600;
18
19    public function __construct(
20        SearchConfig $config,
21        MultiHttpClient $client,
22        WANObjectCache $wanCache,
23        InterwikiLookup $iwLookup
24    ) {
25        parent::__construct( $config, $client, $wanCache, $iwLookup );
26        if ( $config->getWikiId() !== WikiMap::getCurrentWikiId() ) {
27            throw new \RuntimeException( "This resolver cannot with an external wiki config. (config: " .
28                $config->getWikiId() . ", global: " . WikiMap::getCurrentWikiId() );
29        }
30    }
31
32    /**
33     * @param SearchConfig $config
34     * @param ExtensionRegistry|null $extensionRegistry
35     * @return bool true if this resolver can run with the specified config
36     */
37    public static function accepts( SearchConfig $config, ?ExtensionRegistry $extensionRegistry = null ) {
38        $extensionRegistry = $extensionRegistry ?: ExtensionRegistry::getInstance();
39        return $config->getWikiId() === WikiMap::getCurrentWikiId()
40            && $extensionRegistry->isLoaded( 'SiteMatrix' )
41            && $config->has( 'SiteMatrixSites' );
42    }
43
44    protected function loadMatrix() {
45        $cacheKey = $this->wanCache->makeKey( 'cirrussearch-interwiki-matrix', 'v1' );
46        $matrix = $this->wanCache->getWithSetCallback(
47            $cacheKey,
48            self::MATRIX_CACHE_TTL,
49            $this->siteMatrixLoader()
50        );
51        if ( !is_array( $matrix ) ) {
52            // Should we log something if we failed?
53            return [];
54        }
55        return $matrix;
56    }
57
58    /**
59     * @return callable
60     */
61    private function siteMatrixLoader() {
62        return function () {
63            global $wgConf;
64
65            $matrix = new SiteMatrix;
66            $wikiDBname = $this->config->get( 'DBname' );
67            [ , $myLang ] = $wgConf->siteFromDB( $wikiDBname );
68            $siteConf = $this->config->get( 'SiteMatrixSites' );
69            $prefixOverrides = $this->config->get( 'CirrusSearchInterwikiPrefixOverrides' );
70            $sisterProjects = [];
71            $crossLanguage = [];
72            $prefixesByWiki = [];
73            $languageMap = [];
74            $myProject = null;
75
76            if ( !in_array( $myLang, $matrix->getLangList() ) ) {
77                return [];
78            }
79
80            foreach ( $matrix->getSites() as $site ) {
81                if ( $matrix->getDBName( $myLang, $site ) === $wikiDBname ) {
82                    $myProject = $site;
83                    break;
84                }
85            }
86
87            if ( $myProject === null ) {
88                // This is not a "project"
89                return [];
90            }
91
92            foreach ( $matrix->getSites() as $site ) {
93                if ( $site === $myProject ) {
94                    continue;
95                }
96                if ( !$matrix->exist( $myLang, $site ) ) {
97                    continue;
98                }
99                if ( $matrix->isClosed( $myLang, $site ) ) {
100                    continue;
101                }
102                if ( !isset( $siteConf[$site]['prefix'] ) ) {
103                    continue;
104                }
105                $dbName = $matrix->getDBName( $myLang, $site );
106                $prefix = $siteConf[$site]['prefix'];
107
108                if ( isset( $prefixOverrides[$prefix] ) ) {
109                    $prefix = $prefixOverrides[$prefix];
110                }
111
112                if ( !in_array( $prefix, $this->config->get( 'CirrusSearchCrossProjectSearchBlockList' ) ) ) {
113                    $sisterProjects[$prefix] = $dbName;
114                }
115                $prefixesByWiki[$dbName] = $prefix;
116            }
117
118            foreach ( $matrix->getLangList() as $lang ) {
119                if ( $lang === $myLang ) {
120                    continue;
121                }
122
123                $dbname = $matrix->getDBName( $lang, $myProject );
124                if ( !$matrix->exist( $lang, $myProject ) ) {
125                    continue;
126                }
127                if ( $matrix->isClosed( $lang, $myProject ) ) {
128                    continue;
129                }
130                // Bold assumption that the interwiki prefix is equal
131                // to the language.
132                $iw = $this->interwikiLookup->fetch( $lang );
133                // Not a valid interwiki prefix...
134                if ( !$iw ) {
135                    continue;
136                }
137
138                $url = $matrix->getCanonicalUrl( $lang, $myProject );
139                $iwurl = $iw->getURL();
140                if ( strlen( $url ) > strlen( $iwurl ) ) {
141                    continue;
142                }
143                if ( substr_compare( $iwurl, $url, 0, strlen( $url ) ) !== 0 ) {
144                    continue;
145                }
146
147                $crossLanguage[$lang] = $dbname;
148                // In theory it's impossible to override something here
149                // should we log something if the case?
150                $prefixesByWiki[$dbname] = $lang;
151                $wikiLangCode = $wgConf->get( 'wgLanguageCode', $dbname, $myProject,
152                    [ 'lang' => $lang, 'site' => $myProject ] );
153                $languageMap[$wikiLangCode][] = $lang;
154            }
155            // Cleanup unambiguous languages
156            $cleanLanguageMap = [];
157            foreach ( $languageMap as $lang => $dbprefixes ) {
158                if ( array_key_exists( $lang, $dbprefixes )
159                    && ( $dbprefixes === [ $lang ] )
160                ) {
161                    continue;
162                }
163                // if lang is equals to one of the dbprefixes then
164                if ( in_array( $lang, $dbprefixes ) ) {
165                    continue;
166                }
167                if ( count( $dbprefixes ) > 1 ) {
168                    // TODO: Log this ambiguous entry
169                }
170                $cleanLanguageMap[$lang] = reset( $dbprefixes );
171            }
172            return [
173                'sister_projects' => $sisterProjects,
174                'language_map' => $cleanLanguageMap,
175                'cross_language' => $crossLanguage,
176                'prefixes_by_wiki' => $prefixesByWiki,
177            ];
178        };
179    }
180
181}