Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 212 |
|
0.00% |
0 / 18 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 212 |
|
0.00% |
0 / 18 |
6972 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
setVersionConstant | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isLanguageInHeader | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isCompactLinksEnabled | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
90 | |||
loadCodexStyles | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
onBeforePageDisplay | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
156 | |||
handleSetLang | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
onSkinTemplateNavigation__Universal | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
addPersonalBarTrigger | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 | |||
getDefaultLanguage | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
onUserGetLanguageObject | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
110 | |||
onResourceLoaderGetConfigVars | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
onMakeGlobalVariablesScript | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
42 | |||
onGetPreferences | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
12 | |||
onGetBetaFeaturePreferences | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
20 | |||
onSkinAfterPortlet | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
90 | |||
getSetLang | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * Hooks for UniversalLanguageSelector extension. |
4 | * |
5 | * Copyright (C) 2012-2018 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon |
6 | * Harris, Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland |
7 | * and other contributors. See CREDITS for a list. |
8 | * |
9 | * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't |
10 | * have to do anything special to choose one license or the other and you don't |
11 | * have to notify anyone which license you are using. You are free to use |
12 | * UniversalLanguageSelector in commercial projects as long as the copyright |
13 | * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. |
14 | * |
15 | * @file |
16 | * @ingroup Extensions |
17 | * @license GPL-2.0-or-later |
18 | * @license MIT |
19 | */ |
20 | |
21 | namespace UniversalLanguageSelector; |
22 | |
23 | use Config; |
24 | use ExtensionRegistry; |
25 | use IBufferingStatsdDataFactory; |
26 | use IContextSource; |
27 | use LanguageCode; |
28 | use MediaWiki\Babel\Babel; |
29 | use MediaWiki\Extension\BetaFeatures\BetaFeatures; |
30 | use MediaWiki\Hook\BeforePageDisplayHook; |
31 | use MediaWiki\Hook\MakeGlobalVariablesScriptHook; |
32 | use MediaWiki\Hook\UserGetLanguageObjectHook; |
33 | use MediaWiki\Html\Html; |
34 | use MediaWiki\Languages\LanguageNameUtils; |
35 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
36 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook; |
37 | use MediaWiki\Skins\Hook\SkinAfterPortletHook; |
38 | use MediaWiki\User\UserOptionsLookup; |
39 | use OutputPage; |
40 | use RequestContext; |
41 | use Skin; |
42 | use SkinTemplate; |
43 | use User; |
44 | |
45 | /** |
46 | * @phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
47 | */ |
48 | class Hooks implements |
49 | BeforePageDisplayHook, |
50 | UserGetLanguageObjectHook, |
51 | ResourceLoaderGetConfigVarsHook, |
52 | MakeGlobalVariablesScriptHook, |
53 | GetPreferencesHook, |
54 | SkinAfterPortletHook |
55 | { |
56 | |
57 | /** @var Config */ |
58 | private $config; |
59 | |
60 | /** @var UserOptionsLookup */ |
61 | private $userOptionsLookup; |
62 | |
63 | /** @var IBufferingStatsdDataFactory */ |
64 | private $statsdDataFactory; |
65 | |
66 | /** @var LanguageNameUtils */ |
67 | private $languageNameUtils; |
68 | |
69 | /** |
70 | * @param Config $config |
71 | * @param UserOptionsLookup $userOptionsLookup |
72 | * @param IBufferingStatsdDataFactory $statsdDataFactory |
73 | * @param LanguageNameUtils $languageNameUtils |
74 | */ |
75 | public function __construct( |
76 | Config $config, |
77 | UserOptionsLookup $userOptionsLookup, |
78 | IBufferingStatsdDataFactory $statsdDataFactory, |
79 | LanguageNameUtils $languageNameUtils |
80 | ) { |
81 | $this->config = $config; |
82 | $this->userOptionsLookup = $userOptionsLookup; |
83 | $this->statsdDataFactory = $statsdDataFactory; |
84 | $this->languageNameUtils = $languageNameUtils; |
85 | } |
86 | |
87 | public static function setVersionConstant() { |
88 | define( 'ULS_VERSION', '2020-07-20' ); |
89 | } |
90 | |
91 | /** |
92 | * Whether user visible ULS features are enabled (language changing, input methods, web |
93 | * fonts, language change undo tooltip). |
94 | * @return bool |
95 | */ |
96 | private function isEnabled(): bool { |
97 | return (bool)$this->config->get( 'ULSEnable' ); |
98 | } |
99 | |
100 | /** |
101 | * Checks whether language is in header. |
102 | * |
103 | * @param Skin $skin |
104 | * @return bool |
105 | */ |
106 | private function isLanguageInHeader( Skin $skin ): bool { |
107 | $languageInHeaderConfig = $skin->getConfig()->get( 'VectorLanguageInHeader' ); |
108 | $userStatus = $skin->getUser()->isAnon() ? 'logged_out' : 'logged_in'; |
109 | return $languageInHeaderConfig[ $userStatus ] ?? true; |
110 | } |
111 | |
112 | /** |
113 | * Whether ULS Compact interlanguage links enabled |
114 | * |
115 | * @param User $user |
116 | * @param Skin $skin |
117 | * @return bool |
118 | */ |
119 | private function isCompactLinksEnabled( User $user, Skin $skin ) { |
120 | // Whether any user visible features are enabled |
121 | if ( !$this->config->get( 'ULSEnable' ) ) { |
122 | return false; |
123 | } |
124 | // Compact links should be disabled in Vector 2022 skin, |
125 | // when the language button is displayed at the top of the content |
126 | if ( $skin->getSkinName() === 'vector-2022' ) { |
127 | return !$this->isLanguageInHeader( $skin ); |
128 | } |
129 | if ( $this->config->get( 'ULSCompactLanguageLinksBetaFeature' ) === true && |
130 | $this->config->get( 'InterwikiMagic' ) === true && |
131 | $this->config->get( 'HideInterlanguageLinks' ) === false && |
132 | ExtensionRegistry::getInstance()->isLoaded( 'BetaFeatures' ) && |
133 | BetaFeatures::isFeatureEnabled( $user, 'uls-compact-links' ) |
134 | ) { |
135 | // Compact language links is a beta feature in this wiki. Check the user's |
136 | // preference. |
137 | return true; |
138 | } |
139 | |
140 | if ( $this->config->get( 'ULSCompactLanguageLinksBetaFeature' ) === false ) { |
141 | // Compact language links is a default feature in this wiki. |
142 | // Check user preference |
143 | return $this->userOptionsLookup |
144 | ->getBoolOption( $user, 'compact-language-links' ); |
145 | } |
146 | |
147 | return false; |
148 | } |
149 | |
150 | /** |
151 | * Adds Codex styles in a way that is compatible with MLEB. |
152 | * |
153 | * @param OutputPage $out |
154 | */ |
155 | private function loadCodexStyles( OutputPage $out ) { |
156 | if ( version_compare( MW_VERSION, '1.41', '<' ) ) { |
157 | // codex-search-styles was added in 1.41 so in older versions for MLEB support |
158 | // we load the full module. |
159 | $out->addModuleStyles( '@wikimedia/codex' ); |
160 | } else { |
161 | // Only needed for skins that do not load Codex. |
162 | if ( !in_array( $out->getSkin()->getSkinName(), [ 'minerva', 'vector-2022' ] ) ) { |
163 | $out->addModuleStyles( 'codex-search-styles' ); |
164 | } |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * @param OutputPage $out |
170 | * @param Skin $skin |
171 | * Hook: BeforePageDisplay |
172 | */ |
173 | public function onBeforePageDisplay( $out, $skin ): void { |
174 | $unsupportedSkins = [ 'minerva', 'apioutput' ]; |
175 | if ( in_array( $skin->getSkinName(), $unsupportedSkins, true ) ) { |
176 | return; |
177 | } |
178 | // Soft dependency to Wikibase client. Don't enable CLL if links are managed manually. |
179 | $excludedLinks = $out->getProperty( 'noexternallanglinks' ); |
180 | $override = is_array( $excludedLinks ) && in_array( '*', $excludedLinks, true ); |
181 | $isCompactLinksEnabled = $this->isCompactLinksEnabled( $out->getUser(), $skin ); |
182 | $isVector2022LanguageInHeader = $skin->getSkinName() === 'vector-2022' && $this->isLanguageInHeader( $skin ); |
183 | $config = [ |
184 | 'wgULSPosition' => $this->config->get( 'ULSPosition' ), |
185 | 'wgULSisCompactLinksEnabled' => $isCompactLinksEnabled, |
186 | 'wgVector2022LanguageInHeader' => $isVector2022LanguageInHeader |
187 | ]; |
188 | |
189 | if ( !$override && $isCompactLinksEnabled ) { |
190 | $out->addModules( 'ext.uls.compactlinks' ); |
191 | // Add styles for the default button in the page. |
192 | $this->loadCodexStyles( $out ); |
193 | } |
194 | |
195 | if ( is_string( $this->config->get( 'ULSGeoService' ) ) ) { |
196 | $out->addModules( 'ext.uls.geoclient' ); |
197 | } |
198 | |
199 | if ( $this->isEnabled() ) { |
200 | // Enable UI language selection for the user. |
201 | $out->addModules( 'ext.uls.interface' ); |
202 | $this->loadCodexStyles( $out ); |
203 | |
204 | $title = $out->getTitle(); |
205 | $isMissingPage = !$title || !$title->exists(); |
206 | // if current page doesn't exist or if it's a talk page, we should use a different layout inside ULS |
207 | // according to T316559. Add JS config variable here, to let frontend know, when this is the case |
208 | $config[ 'wgULSisLanguageSelectorEmpty' ] = $isMissingPage || $title->isTalkPage(); |
209 | } |
210 | |
211 | // This is added here, and not in onResourceLoaderGetConfigVars to allow skins and extensions |
212 | // to vary it. For example, ContentTranslation special pages depend on being able to change it. |
213 | $out->addJsConfigVars( $config ); |
214 | |
215 | if ( $this->config->get( 'ULSPosition' ) === 'personal' ) { |
216 | $out->addModuleStyles( 'ext.uls.pt' ); |
217 | } else { |
218 | $out->addModuleStyles( 'ext.uls.interlanguage' ); |
219 | } |
220 | |
221 | if ( $out->getTitle()->isSpecial( 'Preferences' ) ) { |
222 | $out->addModuleStyles( 'ext.uls.preferencespage' ); |
223 | } |
224 | |
225 | $this->handleSetLang( $out ); |
226 | } |
227 | |
228 | /** |
229 | * Handle setlang query parameter; and decide if the setlang related scripts |
230 | * have to be loaded. |
231 | * @param OutputPage $out |
232 | * @return void |
233 | */ |
234 | protected function handleSetLang( OutputPage $out ): void { |
235 | $languageToSet = $this->getSetLang( $out ); |
236 | |
237 | if ( !$languageToSet ) { |
238 | return; |
239 | } |
240 | |
241 | $this->statsdDataFactory->increment( 'uls.setlang_used' ); |
242 | |
243 | $user = $out->getUser(); |
244 | if ( !$user->isRegistered() && !$out->getConfig()->get( 'ULSAnonCanChangeLanguage' ) ) { |
245 | // User is anon, and cannot change language, return. |
246 | return; |
247 | } |
248 | |
249 | $out->addModules( 'ext.uls.setlang' ); |
250 | } |
251 | |
252 | /** |
253 | * @param SkinTemplate $skin |
254 | * @param array &$links |
255 | */ |
256 | public function onSkinTemplateNavigation__Universal( SkinTemplate $skin, array &$links ) { |
257 | // In modern skins which separate out the user menu, |
258 | // e.g. Vector. (T282196) |
259 | // this should appear in the `user-interface-preferences` menu. |
260 | // For older skins not separating out the user menu this will be prepended. |
261 | if ( isset( $links['user-interface-preferences'] ) ) { |
262 | $links['user-interface-preferences'] = $this->addPersonalBarTrigger( |
263 | $links['user-interface-preferences'], |
264 | $skin |
265 | ); |
266 | } |
267 | } |
268 | |
269 | /** |
270 | * Add some tabs for navigation for users who do not use Ajax interface. |
271 | * @param array &$personal_urls |
272 | * @param SkinTemplate $context SkinTemplate object providing context |
273 | * @return array of modified personal urls |
274 | */ |
275 | private function addPersonalBarTrigger( |
276 | array &$personal_urls, |
277 | SkinTemplate $context |
278 | ) { |
279 | if ( $this->config->get( 'ULSPosition' ) !== 'personal' ) { |
280 | return $personal_urls; |
281 | } |
282 | |
283 | if ( !$this->isEnabled() ) { |
284 | return $personal_urls; |
285 | } |
286 | |
287 | // The element id will be 'pt-uls' |
288 | $mwLangCode = $context->getLanguage()->getCode(); |
289 | |
290 | return [ |
291 | 'uls' => [ |
292 | 'text' => $this->languageNameUtils->getLanguageName( $mwLangCode ), |
293 | 'href' => '#', |
294 | // Skin meta data to allow skin (e.g. Vector) to add icons |
295 | 'icon' => 'wikimedia-language', |
296 | // Skin meta data to allow skin (e.g. Vector) to convert to button. |
297 | 'button' => true, |
298 | 'link-class' => [ 'uls-trigger' ], |
299 | 'active' => true |
300 | ] |
301 | ] + $personal_urls; |
302 | } |
303 | |
304 | /** |
305 | * @param float[] $preferred Mapping of |
306 | * 'Preferred languages by lowercased BCP 47 language codes' => 'weight' |
307 | * @return string MediaWiki internal language code or empty string if there's no matched |
308 | * language code |
309 | */ |
310 | protected function getDefaultLanguage( array $preferred ) { |
311 | /** @var array supported List of Supported languages by MediaWiki internal language codes */ |
312 | $supported = $this->languageNameUtils |
313 | ->getLanguageNames( LanguageNameUtils::AUTONYMS, LanguageNameUtils::SUPPORTED ); |
314 | |
315 | // Convert BCP 47 language code to MediaWiki internal language code and |
316 | // look for a MediaWiki internal language code that is acceptable to the client |
317 | // and known to the wiki. |
318 | foreach ( $preferred as $bcp47LangCode => $weight ) { |
319 | $mwLangCode = LanguageCode::bcp47ToInternal( $bcp47LangCode ); |
320 | if ( isset( $supported[$mwLangCode] ) ) { |
321 | return $mwLangCode; |
322 | } |
323 | } |
324 | |
325 | // Some browsers might: |
326 | // - Sent codes like 'zh-hant-tw': |
327 | // FIXME: Try 'zh-tw', 'zh-hant', 'zh' respectively |
328 | // - Only send codes like 'de-de': |
329 | // Try with bare code 'de' |
330 | foreach ( $preferred as $bcp47LangCode => $weight ) { |
331 | $parts = explode( '-', $bcp47LangCode, 2 ); |
332 | $mwLangCode = $parts[0]; |
333 | if ( isset( $supported[$mwLangCode] ) ) { |
334 | return $mwLangCode; |
335 | } |
336 | } |
337 | |
338 | return ''; |
339 | } |
340 | |
341 | /** |
342 | * Hook to UserGetLanguageObject |
343 | * @param User $user |
344 | * @param string &$code |
345 | * @param IContextSource $context |
346 | */ |
347 | public function onUserGetLanguageObject( $user, &$code, $context ) { |
348 | if ( $this->config->get( 'ULSLanguageDetection' ) ) { |
349 | // Vary any caching based on the header value. Note that |
350 | // we need to vary regardless of whether we end up using |
351 | // the header or not, so that requests without the header |
352 | // don't show up for people with it. |
353 | $context->getOutput()->addVaryHeader( 'Accept-Language' ); |
354 | } |
355 | |
356 | if ( !$this->isEnabled() ) { |
357 | return; |
358 | } |
359 | |
360 | $request = $context->getRequest(); |
361 | |
362 | if ( |
363 | // uselang can be used for temporary override of language preference |
364 | $request->getRawVal( 'uselang' ) || |
365 | // Registered user: use preferences, only when safe to load - T267445 |
366 | ( $user->isSafeToLoad() && $user->isRegistered() ) |
367 | ) { |
368 | return; |
369 | } |
370 | |
371 | // If using cookie storage for anons is OK, read from that |
372 | if ( $this->config->get( 'ULSAnonCanChangeLanguage' ) ) { |
373 | // Try to set the language based on the cookie |
374 | $languageToUse = $request->getCookie( 'language', null, '' ); |
375 | if ( $this->languageNameUtils->isSupportedLanguage( $languageToUse ) ) { |
376 | $code = $languageToUse; |
377 | |
378 | return; |
379 | } |
380 | } |
381 | |
382 | // As last resort, try Accept-Language headers if allowed |
383 | if ( $this->config->get( 'ULSLanguageDetection' ) ) { |
384 | // We added a Vary header at the top of this function, |
385 | // since we're depending upon the Accept-Language header |
386 | $preferred = $request->getAcceptLang(); |
387 | $default = $this->getDefaultLanguage( $preferred ); |
388 | if ( $default !== '' ) { |
389 | $code = $default; |
390 | } |
391 | } |
392 | } |
393 | |
394 | /** |
395 | * Hook: ResourceLoaderGetConfigVars |
396 | * @param array &$vars |
397 | * @param string $skin |
398 | * @param Config $config |
399 | */ |
400 | public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void { |
401 | $extRegistry = ExtensionRegistry::getInstance(); |
402 | $skinConfig = $extRegistry->getAttribute( 'UniversalLanguageSelectorSkinConfig' )[ $skin ] ?? []; |
403 | // Place constant stuff here (not depending on request context) |
404 | |
405 | if ( is_string( $config->get( 'ULSGeoService' ) ) ) { |
406 | $vars['wgULSGeoService'] = $config->get( 'ULSGeoService' ); |
407 | } |
408 | |
409 | $vars['wgULSIMEEnabled'] = $config->get( 'ULSIMEEnabled' ); |
410 | $vars['wgULSWebfontsEnabled'] = $config->get( 'ULSWebfontsEnabled' ); |
411 | $vars['wgULSAnonCanChangeLanguage'] = $config->get( 'ULSAnonCanChangeLanguage' ); |
412 | $vars['wgULSImeSelectors'] = $config->get( 'ULSImeSelectors' ); |
413 | $vars['wgULSNoImeSelectors'] = $config->get( 'ULSNoImeSelectors' ); |
414 | $vars['wgULSNoWebfontsSelectors'] = $config->get( 'ULSNoWebfontsSelectors' ); |
415 | $vars['wgULSDisplaySettingsInInterlanguage'] = $skinConfig['ULSDisplaySettingsInInterlanguage'] ?? false; |
416 | |
417 | if ( is_string( $config->get( 'ULSFontRepositoryBasePath' ) ) ) { |
418 | $vars['wgULSFontRepositoryBasePath'] = $config->get( 'ULSFontRepositoryBasePath' ); |
419 | } else { |
420 | $vars['wgULSFontRepositoryBasePath'] = $config->get( 'ExtensionAssetsPath' ) . |
421 | '/UniversalLanguageSelector/data/fontrepo/fonts/'; |
422 | } |
423 | |
424 | if ( $config->has( 'InterwikiSortingSortPrepend' ) && |
425 | $config->get( 'InterwikiSortingSortPrepend' ) !== [] |
426 | ) { |
427 | $vars['wgULSCompactLinksPrepend'] = $config->get( 'InterwikiSortingSortPrepend' ); |
428 | } |
429 | } |
430 | |
431 | /** |
432 | * Hook: MakeGlobalVariablesScript |
433 | * @param array &$vars |
434 | * @param OutputPage $out |
435 | */ |
436 | public function onMakeGlobalVariablesScript( &$vars, $out ): void { |
437 | // Place request context dependent stuff here |
438 | $user = $out->getUser(); |
439 | $loggedIn = $user->isRegistered(); |
440 | |
441 | // Do not output accept languages if there is risk it will get cached across requests |
442 | if ( $out->getConfig()->get( 'ULSAnonCanChangeLanguage' ) || $loggedIn ) { |
443 | $vars['wgULSAcceptLanguageList'] = array_keys( $out->getRequest()->getAcceptLang() ); |
444 | } |
445 | |
446 | if ( $loggedIn && ExtensionRegistry::getInstance()->isLoaded( 'Babel' ) ) { |
447 | $userLanguageInfo = Babel::getCachedUserLanguageInfo( $user ); |
448 | |
449 | // This relies on the fact that Babel levels are 'N' and |
450 | // the digits 0 to 5 as strings, and that in reverse |
451 | // ASCII order they will be 'N', '5', '4', '3', '2', '1', '0'. |
452 | arsort( $userLanguageInfo ); |
453 | |
454 | $vars['wgULSBabelLanguages'] = array_keys( $userLanguageInfo ); |
455 | } |
456 | |
457 | // An optimization to avoid loading all of uls.data just to get the autonym |
458 | $langCode = $out->getLanguage()->getCode(); |
459 | $vars['wgULSCurrentAutonym'] = $this->languageNameUtils->getLanguageName( $langCode ); |
460 | |
461 | $setLangCode = $this->getSetLang( $out ); |
462 | if ( $setLangCode ) { |
463 | $vars['wgULSCurrentLangCode'] = $langCode; |
464 | $vars['wgULSSetLangCode'] = $setLangCode; |
465 | $vars['wgULSSetLangName'] = $this->languageNameUtils->getLanguageName( $setLangCode ); |
466 | } |
467 | } |
468 | |
469 | /** |
470 | * @param User $user User whose preferences are being modified |
471 | * @param array &$preferences Preferences description array, to be fed to an HTMLForm object |
472 | * @return bool|void True or no return value to continue or false to abort |
473 | */ |
474 | public function onGetPreferences( $user, &$preferences ) { |
475 | // T259037: Does not work well on Minerva |
476 | $skin = RequestContext::getMain()->getSkin(); |
477 | if ( $skin->getSkinName() === 'minerva' ) { |
478 | return; |
479 | } |
480 | |
481 | $preferences['uls-preferences'] = [ |
482 | 'type' => 'api', |
483 | ]; |
484 | |
485 | // A link shown for accessing ULS language settings from preferences screen |
486 | $preferences['languagesettings'] = [ |
487 | 'type' => 'info', |
488 | 'raw' => true, |
489 | 'section' => 'personal/i18n', |
490 | // We use this class to hide this from no-JS users |
491 | 'cssclass' => 'uls-preferences-link-wrapper', |
492 | 'default' => "<a id='uls-preferences-link' class='uls-settings-trigger' role='button' tabindex='0'>" . |
493 | wfMessage( 'ext-uls-language-settings-preferences-link' )->escaped() . "</a>", |
494 | ]; |
495 | |
496 | if ( $this->config->get( 'ULSCompactLanguageLinksBetaFeature' ) === false ) { |
497 | $preferences['compact-language-links'] = [ |
498 | 'type' => 'check', |
499 | 'section' => 'rendering/languages', |
500 | 'label-message' => [ |
501 | 'ext-uls-compact-language-links-preference', |
502 | 'mediawikiwiki:Special:MyLanguage/Universal_Language_Selector/Compact_Language_Links' |
503 | ] |
504 | ]; |
505 | } |
506 | } |
507 | |
508 | /** |
509 | * @param User $user |
510 | * @param array[] &$prefs |
511 | */ |
512 | public function onGetBetaFeaturePreferences( $user, array &$prefs ) { |
513 | if ( $this->config->get( 'ULSCompactLanguageLinksBetaFeature' ) === true && |
514 | $this->config->get( 'InterwikiMagic' ) === true && |
515 | $this->config->get( 'HideInterlanguageLinks' ) === false |
516 | ) { |
517 | $extensionAssetsPath = $this->config->get( 'ExtensionAssetsPath' ); |
518 | $imagesDir = "$extensionAssetsPath/UniversalLanguageSelector/resources/images"; |
519 | $prefs['uls-compact-links'] = [ |
520 | 'label-message' => 'uls-betafeature-label', |
521 | 'desc-message' => 'uls-betafeature-desc', |
522 | 'screenshot' => [ |
523 | 'ltr' => "$imagesDir/compact-links-ltr.svg", |
524 | 'rtl' => "$imagesDir/compact-links-rtl.svg", |
525 | ], |
526 | 'info-link' => |
527 | 'https://www.mediawiki.org/wiki/Special:MyLanguage/' . |
528 | 'Universal_Language_Selector/Compact_Language_Links', |
529 | 'discussion-link' => |
530 | 'https://www.mediawiki.org/wiki/Talk:Universal_Language_Selector/Compact_Language_Links', |
531 | ]; |
532 | } |
533 | } |
534 | |
535 | /** |
536 | * @param Skin $skin |
537 | * @param string $name |
538 | * @param string &$content |
539 | */ |
540 | public function onSkinAfterPortlet( $skin, $name, &$content ) { |
541 | if ( $name !== 'lang' ) { |
542 | return; |
543 | } |
544 | |
545 | // The ULS settings cog is only needed on projects which show the ULS button in the sidebar |
546 | // e.g. it is shown in the personal menu |
547 | if ( $this->config->get( 'ULSPosition' ) !== 'interlanguage' ) { |
548 | return; |
549 | } |
550 | |
551 | $hasLanguages = $skin->getLanguages() !== []; |
552 | // For Vector 2022, the ULS settings cog is not needed for projects |
553 | // where a dedicated language button in the header ($wgVectorLanguageInHeader is true). |
554 | if ( $skin->getSkinName() === 'vector-2022' ) { |
555 | $languageInHeaderConfig = $skin->getConfig()->get( 'VectorLanguageInHeader' ); |
556 | $languageInHeader = $languageInHeaderConfig[ |
557 | $skin->getUser()->isAnon() ? 'logged_out' : 'logged_in' ] ?? true; |
558 | if ( $hasLanguages && $languageInHeader ) { |
559 | return; |
560 | } |
561 | } |
562 | |
563 | if ( !$this->isEnabled() ) { |
564 | return; |
565 | } |
566 | |
567 | // An empty span will force the language portal to always display in |
568 | // the skins that support it! e.g. Vector. (T275147) |
569 | if ( !$hasLanguages ) { |
570 | // If no languages force it on. |
571 | $content .= Html::element( |
572 | 'span', |
573 | [ 'class' => 'uls-after-portlet-link', ], |
574 | '' |
575 | ); |
576 | } |
577 | } |
578 | |
579 | /** |
580 | * @param OutputPage $out |
581 | * @return string|null |
582 | */ |
583 | private function getSetLang( OutputPage $out ): ?string { |
584 | $setLangCode = $out->getRequest()->getRawVal( 'setlang' ); |
585 | if ( $setLangCode && $this->languageNameUtils->isSupportedLanguage( $setLangCode ) ) { |
586 | return $setLangCode; |
587 | } |
588 | |
589 | return null; |
590 | } |
591 | } |