Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
LanguageFallback
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
4 / 4
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getFirst
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAll
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 getAllIncludingSiteLanguage
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Languages;
22
23use InvalidArgumentException;
24use LocalisationCache;
25
26/**
27 * @since 1.35
28 * @ingroup Language
29 */
30class LanguageFallback {
31    /**
32     * Return a fallback chain for messages in getAll
33     * @since 1.35
34     */
35    public const MESSAGES = 0;
36
37    /**
38     * Return a strict fallback chain in getAll
39     * @since 1.35
40     */
41    public const STRICT = 1;
42
43    /** @var string */
44    private $siteLangCode;
45
46    /** @var LocalisationCache */
47    private $localisationCache;
48
49    /** @var LanguageNameUtils */
50    private $langNameUtils;
51
52    /** @var array */
53    private $fallbackCache = [];
54
55    /**
56     * Do not call this directly. Use MediaWikiServices.
57     *
58     * @since 1.35
59     * @param string $siteLangCode Language code of the site, typically $wgLanguageCode
60     * @param LocalisationCache $localisationCache
61     * @param LanguageNameUtils $langNameUtils
62     */
63    public function __construct(
64        $siteLangCode,
65        LocalisationCache $localisationCache,
66        LanguageNameUtils $langNameUtils
67    ) {
68        $this->siteLangCode = $siteLangCode;
69        $this->localisationCache = $localisationCache;
70        $this->langNameUtils = $langNameUtils;
71    }
72
73    /**
74     * Get the first fallback for a given language.
75     *
76     * @since 1.35
77     * @param string $code
78     * @return string|null
79     */
80    public function getFirst( $code ) {
81        return $this->getAll( $code )[0] ?? null;
82    }
83
84    /**
85     * Get the ordered list of fallback languages.
86     *
87     * @since 1.35
88     * @param string $code Language code
89     * @param int $mode Fallback mode, either MESSAGES (which always falls back to 'en'), or STRICT
90     *   (which only falls back to 'en' when explicitly defined)
91     * @throws InvalidArgumentException If $mode is invalid
92     * @return string[] List of language codes
93     */
94    public function getAll( $code, $mode = self::MESSAGES ) {
95        // XXX The LanguageNameUtils dependency is just because of this line, is it needed?
96        // Especially because isValidBuiltInCode() is just a one-line regex anyway, maybe it should
97        // actually be static?
98        if ( $code === 'en' || !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
99            return [];
100        }
101        switch ( $mode ) {
102            case self::MESSAGES:
103                // For unknown languages, fallbackSequence returns an empty array. Hardcode fallback
104                // to 'en' in that case, as English messages are always defined.
105                $ret = $this->localisationCache->getItem( $code, 'fallbackSequence' ) ?: [ 'en' ];
106                break;
107
108            case self::STRICT:
109                // Use this mode when you don't want to fall back to English unless explicitly
110                // defined, for example, when you have language-variant icons and an international
111                // language-independent fallback.
112                $ret = $this->localisationCache->getItem( $code, 'originalFallbackSequence' );
113                break;
114
115            default:
116                throw new InvalidArgumentException( "Invalid fallback mode \"$mode\"" );
117        }
118
119        return $ret;
120    }
121
122    /**
123     * Get the ordered list of fallback languages, ending with the fallback language chain for the
124     * site language. The site fallback list begins with the site language itself.
125     *
126     * @since 1.35
127     * @param string $code Language code
128     * @return string[][] [ fallbacks, site fallbacks ]
129     */
130    public function getAllIncludingSiteLanguage( $code ) {
131        // Usually, we will only store a tiny number of fallback chains, so we cache in a member.
132        $cacheKey = "{$code}-{$this->siteLangCode}";
133
134        if ( !array_key_exists( $cacheKey, $this->fallbackCache ) ) {
135            $fallbacks = $this->getAll( $code );
136
137            if ( $code === $this->siteLangCode ) {
138                // Don't bother hitting the localisation cache a second time
139                $siteFallbacks = [ $code ];
140            } else {
141                // Append the site's fallback chain, including the site language itself
142                $siteFallbacks = $this->getAll( $this->siteLangCode );
143                array_unshift( $siteFallbacks, $this->siteLangCode );
144
145                // Eliminate any languages already included in the chain
146                $siteFallbacks = array_diff( $siteFallbacks, $fallbacks );
147            }
148
149            $this->fallbackCache[$cacheKey] = [ $fallbacks, $siteFallbacks ];
150        }
151        return $this->fallbackCache[$cacheKey];
152    }
153}