Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
LanguageBabelBox
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 7
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 getText
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 addCategories
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 addCategory
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 getCategoryName
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 getCategoryLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Contains code for language boxes.
4 *
5 * @file
6 * @author Robert Leverington
7 * @author Robin Pepermans
8 * @author Niklas Laxström
9 * @author Brian Wolff
10 * @author Purodha Blissenbach
11 * @author Sam Reed
12 * @author Siebrand Mazeland
13 * @license GPL-2.0-or-later
14 */
15
16declare( strict_types = 1 );
17
18namespace MediaWiki\Babel\BabelBox;
19
20use LanguageCode;
21use MediaWiki\Babel\BabelAutoCreate;
22use MediaWiki\Babel\BabelLanguageCodes;
23use MediaWiki\MediaWikiServices;
24use MediaWiki\Title\Title;
25use ParserOutput;
26
27/**
28 * Class for babel language boxes.
29 */
30class LanguageBabelBox implements BabelBox {
31
32    /**
33     * @var Title
34     */
35    private $title;
36
37    /**
38     * @var string
39     */
40    private $code;
41
42    /**
43     * @var string
44     */
45    private $level;
46
47    /**
48     * @var bool
49     */
50    private $createCategories;
51
52    /**
53     * Construct a babel box for the given language and level.
54     *
55     * @param Title $title
56     * @param string $code Language code to use.
57     *   This is a mediawiki-internal code (not necessarily a valid BCP-47 code)
58     * @param string $level Level of ability to use.
59     */
60    public function __construct(
61        Title $title,
62        string $code,
63        string $level
64    ) {
65        $this->title = $title;
66        $this->code = BabelLanguageCodes::getCode( $code ) ?? $code;
67        $this->level = $level;
68    }
69
70    /**
71     * Return the babel box code.
72     *
73     * @return string A babel box for the given language and level.
74     */
75    public function render(): string {
76        $code = $this->code;
77        $catCode = BabelLanguageCodes::getCategoryCode( $code );
78        $bcp47 = LanguageCode::bcp47( $code );
79
80        $portal = wfMessage( 'babel-portal', $catCode )->inContentLanguage()->text();
81        if ( $portal !== '' ) {
82            $portal = "[[$portal|$catCode]]";
83        } else {
84            $portal = $catCode;
85        }
86        $header = "$portal<span class=\"mw-babel-box-level-{$this->level}\">-{$this->level}</span>";
87
88        $name = BabelLanguageCodes::getName( $code );
89        $text = self::getText( $this->title, $name, $code, $this->level );
90
91        $dir_current = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( $code )->getDir();
92
93        $dir_head = $this->title->getPageLanguage()->getDir();
94
95        return <<<EOT
96<div class="mw-babel-box mw-babel-box-{$this->level} mw-babel-box-{$catCode}" dir="$dir_head">
97{|
98! dir="$dir_head" | $header
99| dir="$dir_current" lang="$bcp47" | $text
100|}
101</div>
102EOT;
103    }
104
105    /**
106     * Get the text to display in the language box for specific language and
107     * level. If MediaWiki:Babel-<level>-n (the message that includes the
108     * language autonym) is translated into the given language, use that
109     * otherwise use MediaWiki:Babel-<level> (the message that takes the
110     * language name as a parameter)
111     *
112     * @param Title $title
113     * @param string $name
114     * @param string $code Mediawiki-internal language code of language to use.
115     * @param string $level Level to use.
116     * @return string Text for display, in wikitext format.
117     */
118    private static function getText(
119        Title $title,
120        string $name,
121        string $code,
122        string $level
123    ): string {
124        $categoryLevel = self::getCategoryLink( $title, $level, $code );
125        $categoryMain = self::getCategoryLink( $title, null, $code );
126
127        // Give grep a chance to find the usages:
128        // babel-0-n, babel-1-n, babel-2-n, babel-3-n, babel-4-n, babel-5-n, babel-N-n
129        $text = wfMessage( "babel-$level-n",
130            $categoryLevel, $categoryMain, '', $title->getDBkey()
131        )->inLanguage( $code )->text();
132
133        $fallbackLanguage = MediaWikiServices::getInstance()->getLanguageFallback()->getFirst( $code );
134        // Because of T75473, the above wfMessage call will ignore any
135        // MediaWiki namespace overrides for fallback languages. Hence, we
136        // must explicitly ignore them here, or else the comparison will fail,
137        // resulting in a message claiming that the user knows the fallback
138        // language (probably English), rather than the language
139        // they actually specified.
140        $fallback = wfMessage( "babel-$level-n",
141            $categoryLevel, $categoryMain, '', $title->getDBkey()
142        )->useDatabase( false )->inLanguage( $fallbackLanguage ?? $code )->text();
143
144        // Give grep a chance to find the usages:
145        // babel-0, babel-1, babel-2, babel-3, babel-4, babel-5, babel-N
146        if ( $text == $fallback ) {
147            $text = wfMessage( "babel-$level",
148                $categoryLevel, $categoryMain, $name, $title->getDBkey()
149            )->inLanguage( $code )->text();
150        }
151
152        return $text;
153    }
154
155    /**
156     * Add appropriate categories for the language box to the given parser output
157     *
158     * @param ParserOutput $parserOutput Output to add categories to
159     */
160    public function addCategories( ParserOutput $parserOutput ): void {
161        global $wgBabelCategorizeNamespaces;
162
163        if (
164            $wgBabelCategorizeNamespaces !== null &&
165            !$this->title->inNamespaces( $wgBabelCategorizeNamespaces )
166        ) {
167            return;
168        }
169
170        # Add main category
171        if ( $this->level !== '0' ) {
172            self::addCategory( $parserOutput, $this->code, null, $this->level );
173        }
174
175        # Add level category
176        self::addCategory( $parserOutput, $this->code, $this->level, false );
177    }
178
179    /**
180     * Adds one category to the given ParserOutput, and arranges for its creation if it doesn't exist.
181     *
182     * @param ParserOutput $parserOutput Parser output to use
183     * @param string $code Code of language that the category is for.
184     * @param string|null $level Level that the category is for.
185     * @param string|bool $sortkey The sortkey to use for the category, or false to use the default sort
186     */
187    private function addCategory( ParserOutput $parserOutput,
188        string $code, ?string $level, $sortkey
189    ) {
190        global $wgBabelAutoCreate;
191        $isOverridden = false;
192        $category = self::getCategoryName( $level, $code, $isOverridden );
193        if ( $category === null ) {
194            return;
195        }
196        if ( $sortkey === false ) {
197            $sortkey = $parserOutput->getPageProperty( 'defaultsort' );
198        }
199        $parserOutput->addCategory( $category, $sortkey ?? '' );
200
201        if ( $wgBabelAutoCreate ) {
202            // Now arrange for autocreation (in LinksUpdate hook) unless the category was overridden locally
203            // (to reduce the risk if a compromised admin edits MediaWiki:Babel-category-override)
204            $title = Title::makeTitleSafe( NS_CATEGORY, $category );
205            $text = BabelAutoCreate::getCategoryText( $code, $level );
206            if ( !$isOverridden && !$title->exists() ) {
207                $parserOutput->appendExtensionData( "babel-tocreate", $category );
208                $parserOutput->setExtensionData( "babel-category-text-$category", $text );
209            }
210        }
211    }
212
213    /**
214     * Replace the placeholder variables from the category names configuration
215     * array with actual values.
216     *
217     * @param ?string $level Level of babel category in question, or null for the main category
218     * @param string $code Mediawiki-internal language code of category.
219     * @param bool &$isOverridden Output parameter. Set to true if the category is overridden on-wiki
220     * so that the caller knows not to create categories.
221     * @return string|null Category name with variables replaced and possibly
222     * overridden by the wiki, or null if no category is desired.
223     */
224    private static function getCategoryName( ?string $level, string $code, bool &$isOverridden ): ?string {
225        global $wgLanguageCode, $wgBabelAllowOverride, $wgBabelMainCategory, $wgBabelCategoryNames;
226
227        $categoryDef = $level !== null ? $wgBabelCategoryNames[$level] : $wgBabelMainCategory;
228        if ( $categoryDef === false ) {
229            return null;
230        }
231
232        $category = strtr( $categoryDef, [
233            '%code%' => BabelLanguageCodes::getCategoryCode( $code ),
234            '%wikiname%' => BabelLanguageCodes::getName( $code, $wgLanguageCode ),
235            '%nativename%' => BabelLanguageCodes::getName( $code )
236        ] );
237
238        $oldCategory = $category;
239
240        // Chance to locally override categorization
241        if ( $wgBabelAllowOverride ) {
242            $category = wfMessage( "babel-category-override",
243                $category, $code, $level
244            )->inContentLanguage()->text();
245            if ( $category !== $oldCategory ) {
246                $isOverridden = true;
247            }
248        }
249
250        // Normalize using Title
251        $title = Title::makeTitleSafe( NS_CATEGORY, $category );
252        if ( !$title ) {
253            // babel-category-override return an invalid page name
254            return null;
255        }
256
257        return $title->getDBkey();
258    }
259
260    /**
261     * Returns the right link target for a category (either the category itself or the
262     * title given to get a self-link)
263     * @param Title $title
264     * @param ?string $level Level of babel category in question, or null for the main category
265     * @param string $code Mediawiki-internal language code of category.
266     * @return string Link target to use for the given category
267     */
268    private static function getCategoryLink( Title $title, ?string $level, string $code ): string {
269        $isOverridden = false;
270        $category = self::getCategoryName( $level, $code, $isOverridden );
271        if ( $category !== null ) {
272            return ":Category:" . $category;
273        }
274        return ":" . $title->getFullText();
275    }
276}