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