Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
SiteMatrix
0.00% covered (danger)
0.00%
0 / 134
0.00% covered (danger)
0.00%
0 / 22
3080
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
306
 getLangList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSpecials
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCountPerSite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSiteUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getUrl
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getCanonicalUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSitename
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguageCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSetting
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getDBName
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 exist
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isClosed
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 isPrivate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isFishbowl
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isNonGlobal
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isSpecial
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 extractDbList
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 extractFile
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\SiteMatrix;
4
5use InvalidArgumentException;
6use LanguageCode;
7use MediaWiki\MediaWikiServices;
8
9/**
10 * Service to access
11 */
12class SiteMatrix {
13    /** @var string[] Language codes used by this wikifarm, sorted alphabetically. */
14    protected $langlist;
15
16    /**
17     * Sites (aka project families) used by this wikifarm. These will be things like 'wiktionary'.
18     *
19     * @var string[]
20     * @see $wgSiteMatrixSites
21     */
22    protected $sites;
23
24    /**
25     * Human-readable site names. (Except they aren't...)
26     *
27     * @var string[] site => name<br/>iw-prefix
28     * @see $wgSiteMatrixSites
29     */
30    protected $names;
31
32    /**
33     * Wiki family domain names.
34     *
35     * @var string[] site => host
36     * @see $wgSiteMatrixSites
37     */
38    protected $hosts;
39
40    /** @var string[]|null Lazy-loaded dbname list of private wikis. */
41    protected $private;
42
43    /** @var string[]|null Lazy-loaded dbname list of fishbowl wikis. */
44    protected $fishbowl;
45
46    /** @var string[]|null Lazy-loaded dbname list of closed wikis. */
47    protected $closed;
48
49    /** @var string[]|null Lazy-loaded dbname list of non-SUL wikis. */
50    protected $nonglobal;
51
52    /**
53     * Special wikis (which are multilingual or otherwise not split by language),
54     * partially sorted by language.
55     * Language codes use _ instead of -.
56     *
57     * @var array[] [ <language code>, <family> ]
58     */
59    protected $specials;
60
61    /**
62     * A matrix of which wikis exist in which language.
63     * Language codes use - instead of _.
64     *
65     * @var array[] site => language => 1
66     */
67    protected $matrix;
68
69    /**
70     * A matrix of which wikis' DB name in which language.
71     * Language codes use - instead of _.
72     *
73     * @var array[] site => language => DBname
74     */
75    protected $matrixDBnames;
76
77    /** @var int Total number of wikis. */
78    protected $count;
79
80    /**
81     * The number of wikis in each wiki family.
82     *
83     * @var int[] site => count
84     */
85    protected $countPerSite;
86
87    /**
88     * Create and load the site matrix.
89     * Goes through $wgLocalDatabases and uses a bunch of $wgSiteMatrix* settings to
90     * sort it into project families and special projects.
91     */
92    public function __construct() {
93        global $wgSiteMatrixFile, $wgSiteMatrixSites;
94        global $wgLocalDatabases, $wgConf;
95
96        $wgConf->loadFullData();
97
98        if ( $wgSiteMatrixFile !== null && file_exists( $wgSiteMatrixFile ) ) {
99            $this->langlist = $this->extractFile( $wgSiteMatrixFile );
100            $hideEmpty = false;
101        } else {
102            $this->langlist = array_keys(
103                MediaWikiServices::getInstance()->getLanguageNameUtils()->getLanguageNames()
104            );
105            $hideEmpty = true;
106        }
107
108        sort( $this->langlist );
109        $xLanglist = array_flip( $this->langlist );
110
111        $this->sites = [];
112        $this->names = [];
113        $this->hosts = [];
114
115        foreach ( $wgSiteMatrixSites as $site => $conf ) {
116            $this->sites[] = $site;
117            $this->names[$site] = $conf['name'] . ( isset( $conf['prefix'] ) ?
118                '<br />' . $conf['prefix'] : '' );
119            $this->hosts[$site] = $conf['host'];
120        }
121
122        # Initialize $countPerSite
123        $this->countPerSite = [];
124        foreach ( $this->sites as $site ) {
125            $this->countPerSite[$site] = 0;
126        }
127
128        # Tabulate the matrix
129        $this->specials = [];
130        $this->matrix = [];
131        $this->matrixDBnames = [];
132        foreach ( $wgLocalDatabases as $db ) {
133            # Find suffix
134            $found = false;
135            foreach ( $this->sites as $site ) {
136                $m = [];
137                if ( preg_match( "/(.*)$site\$/", $db, $m ) ) {
138                    $langPrefix = $m[1];
139                    # The language code prefix might be deprecated language codes
140                    $langCode = LanguageCode::replaceDeprecatedCodes(
141                        str_replace( '_', '-', $langPrefix )
142                    );
143                    # Non-deprecated language codes were already added in the extractFile function
144                    if ( isset( $xLanglist[$langCode] ) ) {
145                        $this->matrix[$site][$langCode] = 1;
146                        $this->matrixDBnames[$site][$langCode] = $db;
147                        $this->countPerSite[$site]++;
148                    } else {
149                        $this->specials[] = [ $langPrefix, $site ];
150                    }
151                    $found = true;
152                    break;
153                }
154            }
155            if ( !$found ) {
156                [ $major, $minor ] = $wgConf->siteFromDB( $db );
157                if ( $major !== null ) {
158                    $this->specials[] = [ str_replace( '-', '_', $minor ), $major ];
159                }
160            }
161        }
162
163        uasort( $this->specials, static function ( $a1, $a2 ) {
164            return strcmp( $a1[0], $a2[0] );
165        } );
166
167        if ( $hideEmpty ) {
168            foreach ( $xLanglist as $lang => $_ ) {
169                $empty = true;
170                foreach ( $this->sites as $site ) {
171                    if ( !empty( $this->matrix[$site][$lang] ) ) {
172                        $empty = false;
173                    }
174                }
175                if ( $empty ) {
176                    unset( $xLanglist[$lang] );
177                }
178            }
179            $this->langlist = array_keys( $xLanglist );
180        }
181
182        $this->count = count( $wgLocalDatabases );
183    }
184
185    /**
186     * Get language codes used by this wikifarm (sorted alphabetically).
187     *
188     * @return string[]
189     */
190    public function getLangList() {
191        return $this->langlist;
192    }
193
194    /**
195     * Get family names in an almost-human-readable format (will be something like 'Wikipedia<br/>w').
196     *
197     * @return string[] family => name
198     */
199    public function getNames() {
200        return $this->names;
201    }
202
203    /**
204     * Get the list of project families used by this wikifarm.
205     *
206     * @return string[]
207     */
208    public function getSites() {
209        return $this->sites;
210    }
211
212    /**
213     * Get list of special wikis (which are multilingual or otherwise not split by language),
214     * partially sorted by language.
215     * Language codes use _ instead of -.
216     *
217     * @return array[] [ <language code>, <family> ]
218     */
219    public function getSpecials() {
220        return $this->specials;
221    }
222
223    /**
224     * Get the total number of wikis.
225     *
226     * @return int
227     */
228    public function getCount() {
229        return $this->count;
230    }
231
232    /**
233     * Get the total number of wikis in a wiki family.
234     *
235     * @param string $site
236     * @return int
237     */
238    public function getCountPerSite( $site ) {
239        return $this->countPerSite[$site];
240    }
241
242    /**
243     * Get the base URL of a wiki family (e.g. '//www.wikipedia.org/') with trailing /.
244     *
245     * @param string $site
246     * @return string
247     */
248    public function getSiteUrl( $site ) {
249        return '//' . $this->hosts[$site] . '/';
250    }
251
252    /**
253     * Get the base URL of a wiki (as in $wgServer / $wgCanonicalServer).
254     *
255     * @param string $langCode Language code (may not equal to language subdomain)
256     * @param string $major Site group code
257     * @param bool $canonical use canonical url.
258     * @return mixed
259     */
260    public function getUrl( $langCode, $major, $canonical = false ) {
261        return $this->getSetting(
262            $canonical ? 'wgCanonicalServer' : 'wgServer',
263            $langCode,
264            $major
265        );
266    }
267
268    /**
269     * Shortcut for getUrl( $langCode, $major, true ).
270     *
271     * @param string $langCode Language code (may not equal to language subdomain)
272     * @param string $major Site group code
273     * @return mixed
274     */
275    public function getCanonicalUrl( $langCode, $major ) {
276        return $this->getSetting( 'wgCanonicalServer', $langCode, $major );
277    }
278
279    /**
280     * Get human-readable name of a wiki.
281     *
282     * @param string $langCode Language code (may not equal to language subdomain)
283     * @param string $major
284     * @return string
285     */
286    public function getSitename( $langCode, $major ) {
287        return $this->getSetting( 'wgSitename', $langCode, $major );
288    }
289
290    /**
291     * Get the normalised IETF language tag.
292     *
293     * @param string $langCode
294     * @param string $major
295     * @return string
296     */
297    public function getLanguageCode( $langCode, $major ) {
298        return LanguageCode::bcp47( $this->getSetting( 'wgLanguageCode', $langCode, $major ) );
299    }
300
301    /**
302     * @param string $setting Setting name
303     * @param string $langCode Language code (may not equal to language DB name prefix)
304     * @param string $dbSuffix e.g. 'wiki' for 'enwiki' or 'wikisource' for 'enwikisource'
305     * @return mixed
306     */
307    private function getSetting( $setting, $langCode, $dbSuffix ) {
308        global $wgConf;
309
310        $dbname = $this->getDBName( $langCode, $dbSuffix );
311
312        [ $major, $minor ] = $wgConf->siteFromDB( $dbname );
313        if ( $major === null ) {
314            throw new InvalidArgumentException( "Invalid DB name \"$dbname\"" );
315        }
316        $minor = str_replace( '_', '-', $minor );
317
318        return $wgConf->get(
319            $setting,
320            $dbname,
321            $major,
322            [ 'lang' => $minor, 'site' => $major ]
323        );
324    }
325
326    /**
327     * @param string $langCode Language code (may not equal to language DB name prefix)
328     * @param string $major
329     * @return string
330     */
331    public function getDBName( $langCode, $major ) {
332        if ( isset( $this->matrix[$major] ) && isset( $this->matrix[$major][$langCode] ) ) {
333            return $this->matrixDBnames[$major][$langCode];
334        }
335        return str_replace( '-', '_', $langCode ) . $major;
336    }
337
338    /**
339     * Check whether a wiki exists.
340     *
341     * @param string $langCode Language code (may not equal to language DB name prefix)
342     * @param string $major Site group code
343     * @return bool
344     */
345    public function exist( $langCode, $major ) {
346        return !empty( $this->matrix[$major][$langCode] );
347    }
348
349    /**
350     * Check whether a wiki is closed (not editable).
351     *
352     * @param string $langCode Language code (may not equal to language DB name prefix)
353     * @param string $major Site group code
354     * @return bool
355     */
356    public function isClosed( $langCode, $major ) {
357        global $wgSiteMatrixClosedSites;
358
359        $dbname = $this->getDBName( $langCode, $major );
360
361        if ( $wgSiteMatrixClosedSites === null ) {
362            // Fallback to old behavior checking read-only settings;
363            // not very reliable.
364            global $wgConf;
365
366            [ $major, $langCode ] = $wgConf->siteFromDB( $dbname );
367            if ( $major === null ) {
368                // No such suffix
369                return false;
370            }
371
372            if ( $wgConf->get( 'wgReadOnly', $dbname, $major, [ 'site' => $major, 'lang' => $langCode ] ) ) {
373                return true;
374            }
375            $readOnlyFile = $wgConf->get( 'wgReadOnlyFile',
376                $dbname,
377                $major,
378                [ 'site' => $major, 'lang' => $langCode ]
379            );
380            if ( $readOnlyFile && file_exists( $readOnlyFile ) ) {
381                return true;
382            }
383            return false;
384        }
385
386        if ( $this->closed == null ) {
387            $this->closed = $this->extractDbList( $wgSiteMatrixClosedSites );
388        }
389        return in_array( $dbname, $this->closed );
390    }
391
392    /**
393     * Check whether a wiki is private (not publicly readable).
394     *
395     * @param string $dbname Database name
396     * @return bool
397     */
398    public function isPrivate( $dbname ) {
399        global $wgSiteMatrixPrivateSites;
400
401        if ( $this->private == null ) {
402            $this->private = $this->extractDbList( $wgSiteMatrixPrivateSites );
403        }
404
405        return in_array( $dbname, $this->private );
406    }
407
408    /**
409     * Check whether a wiki is a fishbowl (publicly readable but not publicly editable).
410     *
411     * @param string $dbname Database name
412     * @return bool
413     */
414    public function isFishbowl( $dbname ) {
415        global $wgSiteMatrixFishbowlSites;
416
417        if ( $this->fishbowl == null ) {
418            $this->fishbowl = $this->extractDbList( $wgSiteMatrixFishbowlSites );
419        }
420
421        return in_array( $dbname, $this->fishbowl );
422    }
423
424    /**
425     * Check whether a wiki is non-global (not using single sign-on).
426     *
427     * @param string $dbname Database name
428     * @return bool
429     */
430    public function isNonGlobal( $dbname ) {
431        global $wgSiteMatrixNonGlobalSites;
432
433        if ( $this->nonglobal == null ) {
434            $this->nonglobal = $this->extractDbList( $wgSiteMatrixNonGlobalSites );
435        }
436
437        return in_array( $dbname, $this->nonglobal );
438    }
439
440    /**
441     * Check whether a wiki is special (the only wiki in its wiki family; typically this means
442     * a multilingual wiki).
443     *
444     * @param string $dbname Database name
445     * @return bool
446     */
447    public function isSpecial( $dbname ) {
448        return in_array( $dbname, $this->specials );
449    }
450
451    /**
452     * Pull a list of dbnames from a given text file, or pass through an array.
453     * Used for the DB list configuration settings.
454     *
455     * @param string[]|string $listOrFilename Array of strings, or string with a file name
456     * @return string[]
457     */
458    private function extractDbList( $listOrFilename ) {
459        if ( is_string( $listOrFilename ) ) {
460            return $this->extractFile( $listOrFilename );
461        } elseif ( is_array( $listOrFilename ) ) {
462            return $listOrFilename;
463        } else {
464            return [];
465        }
466    }
467
468    /**
469     * Pull a list of dbnames from a given text file.
470     *
471     * @param string $filename
472     * @return string[]
473     */
474    private function extractFile( $filename ) {
475        $langCodes = array_map( 'trim', file( $filename ) );
476        foreach ( $langCodes as $langCode ) {
477            $nonDeprecatedLangCode = LanguageCode::replaceDeprecatedCodes( $langCode );
478            if ( $langCode !== $nonDeprecatedLangCode ) {
479                array_push( $langCodes, $nonDeprecatedLangCode );
480            }
481        }
482        return array_unique( $langCodes );
483    }
484}