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