Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
44.26% |
104 / 235 |
|
20.00% |
5 / 25 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
44.26% |
104 / 235 |
|
20.00% |
5 / 25 |
1336.55 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isVectorSkin | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getActiveABTest | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
7 | |||
getVectorSearchResourceLoaderConfig | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
onSkinPageReadyConfig | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
updateActionsMenu | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
updateViewsMenuIcons | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
updateAssociatedPagesMenuIcons | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
appendClassToItem | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
6 | |||
updateUserLinksDropdownItems | |
96.77% |
30 / 31 |
|
0.00% |
0 / 1 |
9 | |||
fixEcho | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
updateUserLinksItems | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
makeMenuItemCollapsible | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
makeIcon | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
updateItemData | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
updateMenuItemData | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
updateMenuItems | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
createMoreOverflowMenu | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
onSkinTemplateNavigation | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
7.23 | |||
onResourceLoaderSiteStylesModulePages | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
onResourceLoaderSiteModulePages | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
onGetPreferences | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
2 | |||
onLocalUserCreated | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
isSkinVersionLegacy | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onGetBetaFeaturePreferences | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
42 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Skins\Vector; |
4 | |
5 | use MediaWiki\Auth\Hook\LocalUserCreatedHook; |
6 | use MediaWiki\Config\Config; |
7 | use MediaWiki\MediaWikiServices; |
8 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
9 | use MediaWiki\ResourceLoader as RL; |
10 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderSiteModulePagesHook; |
11 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderSiteStylesModulePagesHook; |
12 | use MediaWiki\Skins\Hook\SkinPageReadyConfigHook; |
13 | use MediaWiki\Skins\Vector\Hooks\HookRunner; |
14 | use MediaWiki\User\Options\UserOptionsManager; |
15 | use MediaWiki\User\User; |
16 | use RequestContext; |
17 | use RuntimeException; |
18 | use SkinTemplate; |
19 | |
20 | /** |
21 | * Presentation hook handlers for Vector skin. |
22 | * |
23 | * Hook handler method names should be in the form of: |
24 | * on<HookName>() |
25 | * @package Vector |
26 | * @internal |
27 | */ |
28 | class Hooks implements |
29 | GetPreferencesHook, |
30 | LocalUserCreatedHook, |
31 | ResourceLoaderSiteModulePagesHook, |
32 | ResourceLoaderSiteStylesModulePagesHook, |
33 | SkinPageReadyConfigHook |
34 | { |
35 | private Config $config; |
36 | private UserOptionsManager $userOptionsManager; |
37 | |
38 | public function __construct( |
39 | Config $config, |
40 | UserOptionsManager $userOptionsManager |
41 | ) { |
42 | $this->config = $config; |
43 | $this->userOptionsManager = $userOptionsManager; |
44 | } |
45 | |
46 | /** |
47 | * Checks if the current skin is a variant of Vector |
48 | * |
49 | * @param string $skinName |
50 | * @return bool |
51 | */ |
52 | private static function isVectorSkin( string $skinName ): bool { |
53 | return ( |
54 | $skinName === Constants::SKIN_NAME_LEGACY || |
55 | $skinName === Constants::SKIN_NAME_MODERN |
56 | ); |
57 | } |
58 | |
59 | /** |
60 | * @param RL\Context $context |
61 | * @param Config $config |
62 | * @return array |
63 | */ |
64 | public static function getActiveABTest( |
65 | RL\Context $context, |
66 | Config $config |
67 | ) { |
68 | $ab = $config->get( |
69 | Constants::CONFIG_WEB_AB_TEST_ENROLLMENT |
70 | ); |
71 | if ( count( $ab ) === 0 ) { |
72 | // If array is empty then no experiment and need to validate. |
73 | return $ab; |
74 | } |
75 | if ( !array_key_exists( 'buckets', $ab ) ) { |
76 | throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Must contain buckets key.' ); |
77 | } |
78 | if ( !array_key_exists( 'unsampled', $ab['buckets'] ) ) { |
79 | throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Must define an `unsampled` bucket.' ); |
80 | } else { |
81 | // check bucket values. |
82 | foreach ( $ab['buckets'] as $bucketName => $bucketDefinition ) { |
83 | if ( !is_array( $bucketDefinition ) ) { |
84 | throw new RuntimeException( 'Invalid VectorWebABTestEnrollment value: Buckets should be arrays' ); |
85 | } |
86 | $samplingRate = $bucketDefinition['samplingRate']; |
87 | if ( is_string( $samplingRate ) ) { |
88 | throw new RuntimeException( |
89 | 'Invalid VectorWebABTestEnrollment value: Sampling rate should be number between 0 and 1.' |
90 | ); |
91 | } |
92 | } |
93 | } |
94 | |
95 | return $ab; |
96 | } |
97 | |
98 | /** |
99 | * Generates config variables for skins.vector.search Resource Loader module (defined in |
100 | * skin.json). |
101 | * |
102 | * @param RL\Context $context |
103 | * @param Config $config |
104 | * @return array<string,mixed> |
105 | */ |
106 | public static function getVectorSearchResourceLoaderConfig( |
107 | RL\Context $context, |
108 | Config $config |
109 | ): array { |
110 | $vectorSearchConfig = [ |
111 | 'highlightQuery' => |
112 | VectorServices::getLanguageService()->canWordsBeSplitSafely( $context->getLanguage() ) |
113 | ]; |
114 | |
115 | $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ); |
116 | $hookRunner->onVectorSearchResourceLoaderConfig( $vectorSearchConfig ); |
117 | |
118 | return array_merge( $config->get( 'VectorWvuiSearchOptions' ), $vectorSearchConfig ); |
119 | } |
120 | |
121 | /** |
122 | * SkinPageReadyConfig hook handler |
123 | * |
124 | * Replace searchModule provided by skin. |
125 | * |
126 | * @since 1.35 |
127 | * @param RL\Context $context |
128 | * @param mixed[] &$config Associative array of configurable options |
129 | * @return void This hook must not abort, it must return no value |
130 | */ |
131 | public function onSkinPageReadyConfig( |
132 | RL\Context $context, |
133 | array &$config |
134 | ): void { |
135 | // It's better to exit before any additional check |
136 | if ( !self::isVectorSkin( $context->getSkin() ) ) { |
137 | return; |
138 | } |
139 | |
140 | // Tell the `mediawiki.page.ready` module not to wire up search. |
141 | // This allows us to use the new Vue implementation. |
142 | // Context has no knowledge of legacy / modern Vector |
143 | // and from its point of view they are the same thing. |
144 | // Please see the modules `skins.vector.js` and `skins.vector.legacy.js` |
145 | // for the wire up of search. |
146 | $config['search'] = false; |
147 | } |
148 | |
149 | /** |
150 | * Moves watch item from actions to views menu. |
151 | * |
152 | * @internal used inside Hooks::onSkinTemplateNavigation |
153 | * @param array &$content_navigation |
154 | */ |
155 | private static function updateActionsMenu( &$content_navigation ) { |
156 | $key = null; |
157 | if ( isset( $content_navigation['actions']['watch'] ) ) { |
158 | $key = 'watch'; |
159 | } |
160 | if ( isset( $content_navigation['actions']['unwatch'] ) ) { |
161 | $key = 'unwatch'; |
162 | } |
163 | |
164 | // Promote watch link from actions to views and add an icon |
165 | // The second check to isset is pointless but shuts up phan. |
166 | if ( $key !== null && isset( $content_navigation['actions'][ $key ] ) ) { |
167 | $content_navigation['views'][$key] = $content_navigation['actions'][$key]; |
168 | unset( $content_navigation['actions'][$key] ); |
169 | } |
170 | } |
171 | |
172 | /** |
173 | * Adds icons to items in the "views" menu. |
174 | * |
175 | * @internal used inside Hooks::onSkinTemplateNavigation |
176 | * @param array &$content_navigation |
177 | * @param bool $isLegacy is this the legacy Vector skin? |
178 | */ |
179 | private static function updateViewsMenuIcons( &$content_navigation, $isLegacy ) { |
180 | // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset |
181 | foreach ( $content_navigation['views'] as &$item ) { |
182 | $icon = $item['icon'] ?? null; |
183 | if ( $icon ) { |
184 | if ( $isLegacy ) { |
185 | self::appendClassToItem( |
186 | $item['class'], |
187 | [ 'icon' ] |
188 | ); |
189 | } else { |
190 | // Force the item as a button with hidden text. |
191 | $item['button'] = true; |
192 | $item['text-hidden'] = true; |
193 | $item = self::updateMenuItemData( $item, false ); |
194 | } |
195 | } elseif ( !$isLegacy ) { |
196 | // The vector-tab-noicon class is only used in Vector-22. |
197 | self::appendClassToItem( |
198 | $item['class'], |
199 | [ 'vector-tab-noicon' ] |
200 | ); |
201 | } |
202 | } |
203 | } |
204 | |
205 | /** |
206 | * All associated pages menu items do not have icons so are given the vector-tab-noicon class. |
207 | * |
208 | * @internal used inside Hooks::onSkinTemplateNavigation |
209 | * @param array &$content_navigation |
210 | */ |
211 | private static function updateAssociatedPagesMenuIcons( &$content_navigation ) { |
212 | foreach ( $content_navigation['associated-pages'] as &$item ) { |
213 | self::appendClassToItem( |
214 | $item['class'], |
215 | [ 'vector-tab-noicon' ] |
216 | ); |
217 | } |
218 | } |
219 | |
220 | /** |
221 | * Adds class to a property |
222 | * |
223 | * @param array|string &$item to update |
224 | * @param array|string $classes to add to the item |
225 | */ |
226 | private static function appendClassToItem( &$item, $classes ) { |
227 | $existingClasses = $item; |
228 | |
229 | if ( is_array( $existingClasses ) ) { |
230 | // Treat as array |
231 | $newArrayClasses = is_array( $classes ) ? $classes : [ trim( $classes ) ]; |
232 | $item = array_merge( $existingClasses, $newArrayClasses ); |
233 | } elseif ( is_string( $existingClasses ) ) { |
234 | // Treat as string |
235 | $newStrClasses = is_string( $classes ) ? trim( $classes ) : implode( ' ', $classes ); |
236 | $item .= ' ' . $newStrClasses; |
237 | } else { |
238 | // Treat as whatever $classes is |
239 | $item = $classes; |
240 | } |
241 | |
242 | if ( is_string( $item ) ) { |
243 | $item = trim( $item ); |
244 | } |
245 | } |
246 | |
247 | /** |
248 | * Updates personal navigation menu (user links) dropdown for modern Vector: |
249 | * - Adds icons |
250 | * - Makes user page and watchlist collapsible |
251 | * |
252 | * @internal used inside ::updateUserLinksItems |
253 | * @param SkinTemplate $sk |
254 | * @param array &$content_navigation |
255 | * @suppress PhanTypeInvalidDimOffset |
256 | */ |
257 | private static function updateUserLinksDropdownItems( $sk, &$content_navigation ) { |
258 | // For logged-in users in modern Vector, rearrange some links in the personal toolbar. |
259 | $user = $sk->getUser(); |
260 | if ( $user->isRegistered() ) { |
261 | // Remove user page from personal menu dropdown for logged in use |
262 | $content_navigation['user-menu']['userpage']['collapsible'] = true; |
263 | // watchlist may be disabled if $wgGroupPermissions['*']['viewmywatchlist'] = false; |
264 | // See [[phab:T299671]] |
265 | if ( isset( $content_navigation['user-menu']['watchlist'] ) ) { |
266 | $content_navigation['user-menu']['watchlist']['collapsible'] = true; |
267 | } |
268 | |
269 | // Anon editor links handled manually in new anon editor menu |
270 | $logoutMenu = []; |
271 | if ( isset( $content_navigation['user-menu']['logout'] ) ) { |
272 | $logoutMenu['logout'] = $content_navigation['user-menu']['logout']; |
273 | $logoutMenu['logout']['id'] = 'pt-logout'; |
274 | unset( $content_navigation['user-menu']['logout'] ); |
275 | } |
276 | $content_navigation['user-menu-logout'] = $logoutMenu; |
277 | |
278 | self::updateMenuItems( $content_navigation, 'user-menu' ); |
279 | self::updateMenuItems( $content_navigation, 'user-menu-logout' ); |
280 | } else { |
281 | // Remove "Not logged in" from personal menu dropdown for anon users. |
282 | unset( $content_navigation['user-menu']['anonuserpage'] ); |
283 | |
284 | // Make login and create account collapsible |
285 | if ( isset( $content_navigation['user-menu']['login'] ) ) { |
286 | $content_navigation['user-menu']['login']['collapsible'] = true; |
287 | } |
288 | if ( isset( $content_navigation['user-menu']['login-private'] ) ) { |
289 | $content_navigation['user-menu']['login-private']['collapsible'] = true; |
290 | } |
291 | if ( isset( $content_navigation['user-menu']['createaccount'] ) ) { |
292 | $content_navigation['user-menu']['createaccount']['collapsible'] = true; |
293 | } |
294 | |
295 | // Anon editor links handled manually in new anon editor menu |
296 | $anonEditorMenu = []; |
297 | if ( isset( $content_navigation['user-menu']['anoncontribs'] ) ) { |
298 | $anonEditorMenu['anoncontribs'] = $content_navigation['user-menu']['anoncontribs']; |
299 | $anonEditorMenu['anoncontribs']['id'] = 'pt-anoncontribs'; |
300 | unset( $content_navigation['user-menu']['anoncontribs'] ); |
301 | } |
302 | if ( isset( $content_navigation['user-menu']['anontalk'] ) ) { |
303 | $anonEditorMenu['anontalk'] = $content_navigation['user-menu']['anontalk']; |
304 | $anonEditorMenu['anontalk']['id'] = 'pt-anontalk'; |
305 | unset( $content_navigation['user-menu']['anontalk'] ); |
306 | } |
307 | $content_navigation['user-menu-anon-editor'] = $anonEditorMenu; |
308 | |
309 | // Only show icons for anon menu items (login and create account). |
310 | self::updateMenuItems( $content_navigation, 'user-menu' ); |
311 | } |
312 | } |
313 | |
314 | /** |
315 | * Echo has styles that control icons rendering in places we don't want them. |
316 | * This code works around T343838. |
317 | * |
318 | * @param SkinTemplate $sk |
319 | * @param array &$content_navigation |
320 | */ |
321 | private static function fixEcho( $sk, &$content_navigation ) { |
322 | if ( isset( $content_navigation['notifications'] ) ) { |
323 | foreach ( $content_navigation['notifications'] as &$item ) { |
324 | $icon = $item['icon'] ?? null; |
325 | if ( $icon ) { |
326 | $linkClass = $item['link-class'] ?? []; |
327 | $newLinkClass = [ |
328 | // Allows Echo to react to clicks |
329 | 'mw-echo-notification-badge-nojs' |
330 | ]; |
331 | if ( in_array( 'mw-echo-unseen-notifications', $linkClass ) ) { |
332 | $newLinkClass[] = 'mw-echo-unseen-notifications'; |
333 | } |
334 | $item['link-class'] = $newLinkClass; |
335 | } |
336 | } |
337 | } |
338 | } |
339 | |
340 | /** |
341 | * Updates personal navigation menu (user links) for modern Vector wherein user page, create account and login links |
342 | * are removed from the dropdown to be handled separately. In legacy Vector, the custom "user-page" bucket is |
343 | * removed to preserve existing behavior. |
344 | * |
345 | * @internal used inside Hooks::onSkinTemplateNavigation |
346 | * @param SkinTemplate $sk |
347 | * @param array &$content_navigation |
348 | */ |
349 | private static function updateUserLinksItems( $sk, &$content_navigation ) { |
350 | $skinName = $sk->getSkinName(); |
351 | if ( self::isSkinVersionLegacy( $skinName ) ) { |
352 | // Remove user page from personal toolbar since it will be inside the personal menu for logged-in |
353 | // users in legacy Vector. |
354 | unset( $content_navigation['user-page'] ); |
355 | } else { |
356 | self::fixEcho( $sk, $content_navigation ); |
357 | self::updateUserLinksDropdownItems( $sk, $content_navigation ); |
358 | } |
359 | } |
360 | |
361 | /** |
362 | * Modifies list item to make it collapsible. |
363 | * |
364 | * @internal used in ::updateItemData and ::createMoreOverflowMenu |
365 | * @param array &$item |
366 | * @param string $prefix defaults to user-links- |
367 | */ |
368 | private static function makeMenuItemCollapsible( array &$item, string $prefix = 'user-links-' ) { |
369 | $collapseMenuItemClass = $prefix . 'collapsible-item'; |
370 | self::appendClassToItem( $item[ 'class' ], $collapseMenuItemClass ); |
371 | } |
372 | |
373 | /** |
374 | * Make an icon |
375 | * |
376 | * @internal for use inside Vector skin. |
377 | * @param string $name |
378 | * @return string of HTML |
379 | */ |
380 | private static function makeIcon( $name ) { |
381 | // Html::makeLink will pass this through rawElement |
382 | return '<span class="vector-icon mw-ui-icon-' . $name . ' mw-ui-icon-wikimedia-' . $name . '"></span>'; |
383 | } |
384 | |
385 | /** |
386 | * Update template data to include classes and html that handle buttons, icons, and collapsible items. |
387 | * |
388 | * @internal used in ::updateMenuItemData |
389 | * @param array $item data to update |
390 | * @param string $buttonClassProp property to append button classes |
391 | * @param string $iconHtmlProp property to set icon HTML |
392 | * @param bool $unsetIcon should the icon field be unset? |
393 | * @return array $item Updated data |
394 | */ |
395 | private static function updateItemData( |
396 | $item, $buttonClassProp, $iconHtmlProp, $unsetIcon = true |
397 | ) { |
398 | $hasButton = $item['button'] ?? false; |
399 | $hideText = $item['text-hidden'] ?? false; |
400 | $isCollapsible = $item['collapsible'] ?? false; |
401 | $icon = $item['icon'] ?? ''; |
402 | if ( $unsetIcon ) { |
403 | unset( $item['icon'] ); |
404 | } |
405 | unset( $item['button'] ); |
406 | unset( $item['text-hidden'] ); |
407 | unset( $item['collapsible'] ); |
408 | |
409 | if ( $isCollapsible ) { |
410 | self::makeMenuItemCollapsible( $item ); |
411 | } |
412 | if ( $hasButton ) { |
413 | // Hardcoded button classes, this should be fixed by replacing Hooks.php with VectorComponentButton.php |
414 | self::appendClassToItem( $item[ $buttonClassProp ], [ |
415 | 'cdx-button', |
416 | 'cdx-button--fake-button', |
417 | 'cdx-button--fake-button--enabled', |
418 | 'cdx-button--weight-quiet' |
419 | ] ); |
420 | } |
421 | if ( $icon ) { |
422 | if ( $hideText && $hasButton ) { |
423 | self::appendClassToItem( $item[ $buttonClassProp ], [ 'cdx-button--icon-only' ] ); |
424 | } |
425 | |
426 | $item[ $iconHtmlProp ] = self::makeIcon( $icon ); |
427 | } |
428 | return $item; |
429 | } |
430 | |
431 | /** |
432 | * Updates template data for Vector menu items. |
433 | * |
434 | * @internal used inside Hooks::updateMenuItems ::updateViewsMenuIcons and ::updateUserLinksDropdownItems |
435 | * @param array $item menu item data to update |
436 | * @param bool $unsetIcon should the icon field be unset? |
437 | * @return array $item Updated menu item data |
438 | */ |
439 | private static function updateMenuItemData( $item, $unsetIcon = true ) { |
440 | $buttonClassProp = 'link-class'; |
441 | $iconHtmlProp = 'link-html'; |
442 | return self::updateItemData( $item, $buttonClassProp, $iconHtmlProp, $unsetIcon ); |
443 | } |
444 | |
445 | /** |
446 | * Updates user interface preferences for modern Vector to upgrade icon/button menu items. |
447 | * |
448 | * @param array &$content_navigation |
449 | * @param string $menu identifier |
450 | */ |
451 | private static function updateMenuItems( &$content_navigation, $menu ) { |
452 | foreach ( $content_navigation[$menu] as &$item ) { |
453 | $item = self::updateMenuItemData( $item ); |
454 | } |
455 | } |
456 | |
457 | /** |
458 | * Vector 2022 only: |
459 | * Creates an additional menu that will be injected inside the more (cactions) |
460 | * dropdown menu. This menu is a clone of `views` and this menu will only be |
461 | * shown at low resolutions (when the `views` menu is hidden). |
462 | * |
463 | * An additional menu is used instead of adding to the existing cactions menu |
464 | * so that the emptyPortlet logic for that menu is preserved and the cactions menu |
465 | * is not shown at large resolutions when empty (e.g. all items including collapsed |
466 | * items are hidden). |
467 | * |
468 | * @param array &$content_navigation |
469 | */ |
470 | private static function createMoreOverflowMenu( &$content_navigation ) { |
471 | $clonedViews = []; |
472 | foreach ( $content_navigation['views'] ?? [] as $key => $item ) { |
473 | $newItem = $item; |
474 | self::makeMenuItemCollapsible( |
475 | $newItem, |
476 | 'vector-more-' |
477 | ); |
478 | $clonedViews['more-' . $key] = $newItem; |
479 | } |
480 | // Inject collapsible menu items ahead of existing actions. |
481 | $content_navigation['views-overflow'] = $clonedViews; |
482 | } |
483 | |
484 | /** |
485 | * Upgrades Vector's watch action to a watchstar. |
486 | * This is invoked inside SkinVector, not via skin registration, as skin hooks |
487 | * are not guaranteed to run last. |
488 | * This can possibly be revised based on the outcome of T287622. |
489 | * |
490 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinTemplateNavigation |
491 | * @param SkinTemplate $sk |
492 | * @param array &$content_navigation |
493 | */ |
494 | public static function onSkinTemplateNavigation( $sk, &$content_navigation ) { |
495 | $skinName = $sk->getSkinName(); |
496 | // These changes should only happen in Vector. |
497 | if ( !$skinName || !self::isVectorSkin( $skinName ) ) { |
498 | return; |
499 | } |
500 | |
501 | $title = $sk->getRelevantTitle(); |
502 | if ( |
503 | $sk->getConfig()->get( 'VectorUseIconWatch' ) && |
504 | $title && $title->canExist() |
505 | ) { |
506 | self::updateActionsMenu( $content_navigation ); |
507 | } |
508 | |
509 | self::updateUserLinksItems( $sk, $content_navigation ); |
510 | if ( $skinName === Constants::SKIN_NAME_MODERN ) { |
511 | self::createMoreOverflowMenu( $content_navigation ); |
512 | } |
513 | |
514 | // The updating of the views menu happens /after/ the overflow menu has been created |
515 | // this avoids icons showing in the more overflow menu. |
516 | self::updateViewsMenuIcons( $content_navigation, self::isSkinVersionLegacy( $skinName ) ); |
517 | self::updateAssociatedPagesMenuIcons( $content_navigation ); |
518 | } |
519 | |
520 | /** |
521 | * Adds MediaWiki:Vector.css as the skin style that controls classic Vector. |
522 | * |
523 | * @param string $skin |
524 | * @param array &$pages |
525 | */ |
526 | public function onResourceLoaderSiteStylesModulePages( $skin, &$pages ): void { |
527 | if ( $skin === Constants::SKIN_NAME_MODERN && $this->config->get( 'VectorShareUserScripts' ) ) { |
528 | $pages['MediaWiki:Vector.css'] = [ 'type' => 'style' ]; |
529 | } |
530 | } |
531 | |
532 | /** |
533 | * Adds MediaWiki:Vector.css as the skin style that controls classic Vector. |
534 | * |
535 | * @param string $skin |
536 | * @param array &$pages |
537 | */ |
538 | public function onResourceLoaderSiteModulePages( $skin, &$pages ): void { |
539 | if ( $skin === Constants::SKIN_NAME_MODERN && $this->config->get( 'VectorShareUserScripts' ) ) { |
540 | $pages['MediaWiki:Vector.js'] = [ 'type' => 'script' ]; |
541 | } |
542 | } |
543 | |
544 | /** |
545 | * Adds Vector specific user preferences that can only be accessed via API. |
546 | * |
547 | * @param User $user User whose preferences are being modified. |
548 | * @param array[] &$prefs Preferences description array, to be fed to a HTMLForm object. |
549 | */ |
550 | public function onGetPreferences( $user, &$prefs ): void { |
551 | $vectorPrefs = [ |
552 | Constants::PREF_KEY_FONT_SIZE => [ |
553 | 'type' => 'api' |
554 | ], |
555 | Constants::PREF_KEY_PAGE_TOOLS_PINNED => [ |
556 | 'type' => 'api' |
557 | ], |
558 | Constants::PREF_KEY_MAIN_MENU_PINNED => [ |
559 | 'type' => 'api' |
560 | ], |
561 | Constants::PREF_KEY_TOC_PINNED => [ |
562 | 'type' => 'api' |
563 | ], |
564 | Constants::PREF_KEY_APPEARANCE_PINNED => [ |
565 | 'type' => 'api' |
566 | ], |
567 | Constants::PREF_KEY_LIMITED_WIDTH => [ |
568 | 'type' => 'toggle', |
569 | 'label-message' => 'vector-prefs-limited-width', |
570 | 'section' => 'rendering/skin/skin-prefs', |
571 | 'help-message' => 'vector-prefs-limited-width-help', |
572 | 'hide-if' => [ '!==', 'skin', Constants::SKIN_NAME_MODERN ], |
573 | ], |
574 | Constants::PREF_KEY_NIGHT_MODE => [ |
575 | 'type' => 'api' |
576 | ], |
577 | ]; |
578 | $prefs += $vectorPrefs; |
579 | } |
580 | |
581 | /** |
582 | * Called one time when initializing a users preferences for a newly created account. |
583 | * |
584 | * @param User $user Newly created user object. |
585 | * @param bool $isAutoCreated |
586 | */ |
587 | public function onLocalUserCreated( $user, $isAutoCreated ) { |
588 | $default = $this->config->get( Constants::CONFIG_KEY_DEFAULT_SKIN_VERSION_FOR_NEW_ACCOUNTS ); |
589 | if ( $default ) { |
590 | $this->userOptionsManager->setOption( |
591 | $user, |
592 | Constants::PREF_KEY_SKIN, |
593 | $default === Constants::SKIN_VERSION_LEGACY ? |
594 | Constants::SKIN_NAME_LEGACY : Constants::SKIN_NAME_MODERN |
595 | ); |
596 | } |
597 | } |
598 | |
599 | /** |
600 | * Gets whether the current skin version is the legacy version. |
601 | * |
602 | * @param string $skinName hint that can be used to detect modern vector. |
603 | * @return bool |
604 | */ |
605 | private static function isSkinVersionLegacy( $skinName ): bool { |
606 | return $skinName === Constants::SKIN_NAME_LEGACY; |
607 | } |
608 | |
609 | /** |
610 | * Register Vector 2022 beta feature to the beta features list |
611 | * |
612 | * @param User $user User the preferences are for |
613 | * @param array &$betaFeatures |
614 | */ |
615 | public function onGetBetaFeaturePreferences( User $user, array &$betaFeatures ) { |
616 | $skinName = RequestContext::getMain()->getSkinName(); |
617 | // Only Vector 2022 is supported for beta features |
618 | if ( $skinName !== Constants::SKIN_NAME_MODERN ) { |
619 | return; |
620 | } |
621 | // Only add Vector 2022 beta feature if there is at least one beta feature present in config |
622 | $configHasBeta = false; |
623 | foreach ( Constants::VECTOR_BETA_FEATURES as $featureName ) { |
624 | if ( $this->config->has( $featureName ) && $this->config->get( $featureName )[ 'beta' ] === true ) { |
625 | $configHasBeta = true; |
626 | break; |
627 | } |
628 | } |
629 | if ( !$configHasBeta ) { |
630 | return; |
631 | } |
632 | $skinsAssetsPath = $this->config->get( 'StylePath' ); |
633 | $imagesDir = "$skinsAssetsPath/Vector/resources/images"; |
634 | $betaFeatures[ Constants::VECTOR_2022_BETA_KEY ] = [ |
635 | 'label-message' => 'vector-2022-beta-preview-label', |
636 | 'desc-message' => 'vector-2022-beta-preview-description', |
637 | 'screenshot' => [ |
638 | // follow up work to add images is required in T349321 |
639 | 'ltr' => "$imagesDir/vector-2022-beta-preview-ltr.svg", |
640 | 'rtl' => "$imagesDir/vector-2022-beta-preview-rtl.svg", |
641 | ], |
642 | 'info-link' => 'https://www.mediawiki.org/wiki/Special:MyLanguage/Reading/Web/Accessibility_for_reading', |
643 | 'discussion-link' => 'https://www.mediawiki.org/wiki/Talk:Reading/Web/Accessibility_for_reading', |
644 | ]; |
645 | } |
646 | } |