Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
17.00% |
85 / 500 |
|
12.82% |
5 / 39 |
CRAP | |
0.00% |
0 / 1 |
SkinMinerva | |
17.00% |
85 / 500 |
|
12.82% |
5 / 39 |
13015.21 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
hasPageActions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
hasSecondaryActions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isFallbackEditor | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageActions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getNotificationFallbackButton | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getCombinedNotificationButton | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
getNotificationCircleButton | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
getNotificationButton | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
runOnSkinTemplateNavigationHooks | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
90 | |||
getTemplateData | |
0.00% |
0 / 80 |
|
0.00% |
0 / 1 |
72 | |||
getNotificationButtons | |
96.55% |
28 / 29 |
|
0.00% |
0 / 1 |
7 | |||
isHistoryPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasPageTabs | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
getTabsData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getMainMenu | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
6 | |||
getPersonalToolsMenu | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
getSubjectPage | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
56 | |||
doEditSectionLinksHTML | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getPageClasses | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
resolveNightModeQueryValue | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
56 | |||
getHtmlElementAttributes | |
89.47% |
17 / 19 |
|
0.00% |
0 / 1 |
6.04 | |||
hasCategoryLinks | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
getRelativeHistoryLink | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getGenericHistoryLink | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
shouldUseSpecialHistory | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getHistoryUrl | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getHistoryLink | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
72 | |||
getRevisionEditorData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getTaglineHtml | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
getUserPageHeadingHtml | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
prepareBanners | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getLanguageButton | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getTalkButton | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
getSecondaryActions | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
156 | |||
getJsConfigVars | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultModules | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
getPageSpecificStyles | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
132 | |||
getFeatureSpecificStyles | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 |
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 | |
21 | namespace MediaWiki\Minerva\Skins; |
22 | |
23 | use MediaWiki\Cache\GenderCache; |
24 | use MediaWiki\Extension\Notifications\Controller\NotificationController; |
25 | use MediaWiki\Html\Html; |
26 | use MediaWiki\Language\Language; |
27 | use MediaWiki\Linker\LinkRenderer; |
28 | use MediaWiki\Linker\LinkTarget; |
29 | use MediaWiki\Minerva\LanguagesHelper; |
30 | use MediaWiki\Minerva\Menu\Definitions; |
31 | use MediaWiki\Minerva\Menu\Main\AdvancedMainMenuBuilder; |
32 | use MediaWiki\Minerva\Menu\Main\DefaultMainMenuBuilder; |
33 | use MediaWiki\Minerva\Menu\Main\MainMenuDirector; |
34 | use MediaWiki\Minerva\Menu\PageActions\PageActions; |
35 | use MediaWiki\Minerva\Menu\User\AdvancedUserMenuBuilder; |
36 | use MediaWiki\Minerva\Menu\User\DefaultUserMenuBuilder; |
37 | use MediaWiki\Minerva\Menu\User\UserMenuDirector; |
38 | use MediaWiki\Minerva\Permissions\IMinervaPagePermissions; |
39 | use MediaWiki\Minerva\Permissions\MinervaPagePermissions; |
40 | use MediaWiki\Minerva\SkinOptions; |
41 | use MediaWiki\Registration\ExtensionRegistry; |
42 | use MediaWiki\Revision\RevisionLookup; |
43 | use MediaWiki\SpecialPage\SpecialPage; |
44 | use MediaWiki\Title\NamespaceInfo; |
45 | use MediaWiki\Title\Title; |
46 | use MediaWiki\User\Options\UserOptionsManager; |
47 | use MediaWiki\User\UserIdentityUtils; |
48 | use MediaWiki\Utils\MWTimestamp; |
49 | use RuntimeException; |
50 | use SkinMustache; |
51 | use SkinTemplate; |
52 | use SpecialMobileHistory; |
53 | |
54 | /** |
55 | * Minerva: Born from the godhead of Jupiter with weapons! |
56 | * A skin that works on both desktop and mobile |
57 | * @ingroup Skins |
58 | */ |
59 | class SkinMinerva extends SkinMustache { |
60 | /** @const LEAD_SECTION_NUMBER integer which corresponds to the lead section |
61 | * in editing mode |
62 | */ |
63 | public const LEAD_SECTION_NUMBER = 0; |
64 | |
65 | /** @var string Name of this skin */ |
66 | public $skinname = 'minerva'; |
67 | /** @var string Name of this used template */ |
68 | public $template = 'MinervaTemplate'; |
69 | |
70 | /** @var array|null */ |
71 | private ?array $contentNavigationUrls; |
72 | |
73 | private GenderCache $genderCache; |
74 | private LinkRenderer $linkRenderer; |
75 | private LanguagesHelper $languagesHelper; |
76 | private Definitions $definitions; |
77 | private PageActions $pageActions; |
78 | private IMinervaPagePermissions $permissions; |
79 | private SkinOptions $skinOptions; |
80 | private SkinUserPageHelper $skinUserPageHelper; |
81 | private NamespaceInfo $namespaceInfo; |
82 | private RevisionLookup $revisionLookup; |
83 | private UserIdentityUtils $userIdentityUtils; |
84 | private UserOptionsManager $userOptionsManager; |
85 | |
86 | /** |
87 | * @param GenderCache $genderCache |
88 | * @param LinkRenderer $linkRenderer |
89 | * @param LanguagesHelper $languagesHelper |
90 | * @param Definitions $definitions |
91 | * @param PageActions $pageActions |
92 | * @param MinervaPagePermissions $permissions |
93 | * @param SkinOptions $skinOptions |
94 | * @param SkinUserPageHelper $skinUserPageHelper |
95 | * @param NamespaceInfo $namespaceInfo |
96 | * @param RevisionLookup $revisionLookup |
97 | * @param UserIdentityUtils $userIdentityUtils |
98 | * @param UserOptionsManager $userOptionsManager |
99 | * @param array $options |
100 | */ |
101 | public function __construct( |
102 | GenderCache $genderCache, |
103 | LinkRenderer $linkRenderer, |
104 | LanguagesHelper $languagesHelper, |
105 | Definitions $definitions, |
106 | PageActions $pageActions, |
107 | MinervaPagePermissions $permissions, |
108 | SkinOptions $skinOptions, |
109 | SkinUserPageHelper $skinUserPageHelper, |
110 | NamespaceInfo $namespaceInfo, |
111 | RevisionLookup $revisionLookup, |
112 | UserIdentityUtils $userIdentityUtils, |
113 | UserOptionsManager $userOptionsManager, |
114 | $options = [] |
115 | ) { |
116 | parent::__construct( $options ); |
117 | $this->genderCache = $genderCache; |
118 | $this->linkRenderer = $linkRenderer; |
119 | $this->languagesHelper = $languagesHelper; |
120 | $this->definitions = $definitions |
121 | ->setContext( $this->getContext() ); |
122 | $this->pageActions = $pageActions; |
123 | $this->permissions = $permissions |
124 | ->setContext( $this->getContext() ); |
125 | $this->skinOptions = $skinOptions; |
126 | $this->skinUserPageHelper = $skinUserPageHelper |
127 | ->setContext( $this->getContext() ) |
128 | ->setTitle( $this->getTitle() ); |
129 | $this->namespaceInfo = $namespaceInfo; |
130 | $this->revisionLookup = $revisionLookup; |
131 | $this->userIdentityUtils = $userIdentityUtils; |
132 | $this->userOptionsManager = $userOptionsManager; |
133 | } |
134 | |
135 | /** |
136 | * @return bool |
137 | */ |
138 | private function hasPageActions(): bool { |
139 | $title = $this->getTitle(); |
140 | return !$title->isSpecialPage() && !$title->isMainPage() && |
141 | $this->getActionName() === 'view'; |
142 | } |
143 | |
144 | /** |
145 | * @return bool |
146 | */ |
147 | private function hasSecondaryActions(): bool { |
148 | return !$this->skinUserPageHelper->isUserPage(); |
149 | } |
150 | |
151 | /** |
152 | * @return bool |
153 | */ |
154 | private function isFallbackEditor(): bool { |
155 | return $this->getActionName() === 'edit'; |
156 | } |
157 | |
158 | /** |
159 | * Returns available page actions if the page has any. |
160 | * |
161 | * @param array $nav result of SkinTemplate::buildContentNavigationUrls |
162 | * @return array|null |
163 | */ |
164 | private function getPageActions( array $nav ): ?array { |
165 | if ( $this->isFallbackEditor() || !$this->hasPageActions() ) { |
166 | return null; |
167 | } |
168 | |
169 | $pageActionsDirector = $this->pageActions->getPageActionsDirector( $this->getContext() ); |
170 | $sidebar = $this->buildSidebar(); |
171 | $actions = $nav['actions'] ?? []; |
172 | $views = $nav['views'] ?? []; |
173 | return $pageActionsDirector->buildMenu( $sidebar['TOOLBOX'], $actions, $views ); |
174 | } |
175 | |
176 | /** |
177 | * A notification icon that links to Special:Mytalk when Echo is not installed. |
178 | * Consider upstreaming this to core or removing at a future date. |
179 | * |
180 | * @return array |
181 | */ |
182 | private function getNotificationFallbackButton(): array { |
183 | return [ |
184 | 'icon' => 'bellOutline', |
185 | 'href' => SpecialPage::getTitleFor( 'Mytalk' )->getLocalURL( |
186 | [ 'returnto' => $this->getTitle()->getPrefixedText() ] |
187 | ), |
188 | ]; |
189 | } |
190 | |
191 | /** |
192 | * @param array $alert |
193 | * @param array $notice |
194 | * @return array |
195 | */ |
196 | private function getCombinedNotificationButton( array $alert, array $notice ): array { |
197 | // Sum the notifications from the two original buttons |
198 | $notifCount = ( $alert['data']['counter-num'] ?? 0 ) + ( $notice['data']['counter-num'] ?? 0 ); |
199 | $alert['data']['counter-num'] = $notifCount; |
200 | // @phan-suppress-next-line PhanUndeclaredClassReference |
201 | if ( class_exists( NotificationController::class ) ) { |
202 | // @phan-suppress-next-line PhanUndeclaredClassMethod |
203 | $alert['data']['counter-text'] = NotificationController::formatNotificationCount( $notifCount ); |
204 | } else { |
205 | $alert['data']['counter-text'] = $notifCount; |
206 | } |
207 | |
208 | $linkClassAlert = $alert['link-class'] ?? []; |
209 | $hasUnseenAlerts = is_array( $linkClassAlert ) && in_array( 'mw-echo-unseen-notifications', $linkClassAlert ); |
210 | // The circle should only appear if there are unseen notifications. |
211 | // Once the notifications are seen (by opening the notification drawer) |
212 | // then the icon reverts to a gray circle, but on page refresh |
213 | // it should revert back to a bell icon. |
214 | // If you try and change this behaviour, at time of writing |
215 | // (December 2022) JavaScript will correct it. |
216 | if ( $notifCount > 0 && $hasUnseenAlerts ) { |
217 | $linkClass = $notice['link-class'] ?? []; |
218 | $hasUnseenNotices = is_array( $linkClass ) && in_array( 'mw-echo-unseen-notifications', $linkClass ); |
219 | return $this->getNotificationCircleButton( $alert, $hasUnseenNotices ); |
220 | } else { |
221 | return $this->getNotificationButton( $alert ); |
222 | } |
223 | } |
224 | |
225 | /** |
226 | * Minerva differs from other skins in that for users with unread notifications |
227 | * instead of a bell with a small square indicating the number of notifications |
228 | * it shows a red circle with a number inside. Ideally Vector and Minerva would |
229 | * be treated the same but we'd need to talk to a designer about consolidating these |
230 | * before making such a decision. |
231 | * |
232 | * @param array $alert |
233 | * @param bool $hasUnseenNotices does the user have unseen notices? |
234 | * @return array |
235 | */ |
236 | private function getNotificationCircleButton( array $alert, bool $hasUnseenNotices ): array { |
237 | $alertCount = $alert['data']['counter-num'] ?? 0; |
238 | $linkClass = $alert['link-class'] ?? []; |
239 | $hasSeenAlerts = is_array( $linkClass ) && in_array( 'mw-echo-unseen-notifications', $linkClass ); |
240 | $alertText = $alert['data']['counter-text'] ?? $alertCount; |
241 | $alert['icon'] = 'circle'; |
242 | $alert['class'] = 'notification-count'; |
243 | if ( $hasSeenAlerts || $hasUnseenNotices ) { |
244 | $alert['class'] .= ' notification-unseen mw-echo-unseen-notifications'; |
245 | } |
246 | return $alert; |
247 | } |
248 | |
249 | /** |
250 | * Removes the OOUI icon class and adds Minerva notification classes. |
251 | * |
252 | * @param array $alert |
253 | * @return array |
254 | */ |
255 | private function getNotificationButton( array $alert ): array { |
256 | $linkClass = $alert['link-class']; |
257 | $alert['link-class'] = array_filter( |
258 | $linkClass, |
259 | static function ( $class ) { |
260 | return $class !== 'oo-ui-icon-bellOutline'; |
261 | } |
262 | ); |
263 | $alert['icon'] = 'bellOutline'; |
264 | return $alert; |
265 | } |
266 | |
267 | /** |
268 | * Caches content navigation urls locally for use inside getTemplateData |
269 | * |
270 | * @inheritDoc |
271 | */ |
272 | protected function runOnSkinTemplateNavigationHooks( SkinTemplate $skin, &$contentNavigationUrls ) { |
273 | parent::runOnSkinTemplateNavigationHooks( $skin, $contentNavigationUrls ); |
274 | // There are some SkinTemplate modifications that occur after the execution of this hook |
275 | // to add rel attributes and ID attributes. |
276 | // The only one Minerva needs is this one so we manually add it. |
277 | foreach ( array_keys( $contentNavigationUrls['associated-pages'] ) as $id ) { |
278 | if ( in_array( $id, [ 'user_talk', 'talk' ] ) ) { |
279 | $contentNavigationUrls['associated-pages'][ $id ]['rel'] = 'discussion'; |
280 | } |
281 | } |
282 | $this->contentNavigationUrls = $contentNavigationUrls; |
283 | |
284 | // |
285 | // Echo Technical debt!! |
286 | // * Convert the Echo button into a single button |
287 | // * Switch out the icon. |
288 | // |
289 | if ( $this->getUser()->isRegistered() ) { |
290 | if ( count( $contentNavigationUrls['notifications'] ) === 0 ) { |
291 | // Shown to logged in users when Echo is not installed: |
292 | $contentNavigationUrls['notifications']['mytalks'] = $this->getNotificationFallbackButton(); |
293 | } elseif ( $this->skinOptions->get( SkinOptions::SINGLE_ECHO_BUTTON ) ) { |
294 | // Combine notification icons. Minerva only shows one entry point to notifications. |
295 | // This can be reconsidered with a solution to https://phabricator.wikimedia.org/T142981 |
296 | $alert = $contentNavigationUrls['notifications']['notifications-alert'] ?? null; |
297 | $notice = $contentNavigationUrls['notifications']['notifications-notice'] ?? null; |
298 | if ( $alert && $notice ) { |
299 | unset( $contentNavigationUrls['notifications']['notifications-notice'] ); |
300 | $contentNavigationUrls['notifications']['notifications-alert'] = |
301 | $this->getCombinedNotificationButton( $alert, $notice ); |
302 | } |
303 | } else { |
304 | // Show desktop alert icon. |
305 | $alert = $contentNavigationUrls['notifications']['notifications-alert'] ?? null; |
306 | if ( $alert ) { |
307 | // Correct the icon to be the bell filled rather than the outline to match |
308 | // Echo's badge. |
309 | $linkClass = $alert['link-class'] ?? []; |
310 | $alert['link-class'] = array_filter( $linkClass, static function ( $class ) { |
311 | return $class !== 'oo-ui-icon-bellOutline'; |
312 | } ); |
313 | $contentNavigationUrls['notifications']['notifications-alert'] = $alert; |
314 | } |
315 | } |
316 | } |
317 | } |
318 | |
319 | /** |
320 | * @inheritDoc |
321 | */ |
322 | public function getTemplateData(): array { |
323 | $data = parent::getTemplateData(); |
324 | // FIXME: Can we use $data instead of calling buildContentNavigationUrls ? |
325 | $nav = $this->contentNavigationUrls; |
326 | if ( $nav === null ) { |
327 | throw new RuntimeException( 'contentNavigationUrls was not set as expected.' ); |
328 | } |
329 | if ( !$this->hasCategoryLinks() ) { |
330 | unset( $data['html-categories'] ); |
331 | } |
332 | |
333 | // Special handling for certain pages. |
334 | // This is technical debt that should be upstreamed to core. |
335 | $isUserPage = $this->skinUserPageHelper->isUserPage(); |
336 | $isUserPageAccessible = $this->skinUserPageHelper->isUserPageAccessibleToCurrentUser(); |
337 | if ( $isUserPage && $isUserPageAccessible ) { |
338 | $data['html-title-heading'] = $this->getUserPageHeadingHtml( $data['html-title-heading' ] ); |
339 | } |
340 | |
341 | $usermessage = $data['html-user-message'] ?? ''; |
342 | if ( $usermessage ) { |
343 | $data['html-user-message'] = Html::warningBox( |
344 | '<span class="minerva-icon minerva-icon--userTalk-warning"></span> ' |
345 | . $usermessage, |
346 | 'minerva-anon-talk-message' |
347 | ); |
348 | } |
349 | $allLanguages = $data['data-portlets']['data-languages']['array-items'] ?? []; |
350 | $allVariants = $data['data-portlets']['data-variants']['array-items'] ?? []; |
351 | $notifications = $data['data-portlets']['data-notifications']['array-items'] ?? []; |
352 | $associatedPages = $data['data-portlets']['data-associated-pages'] ?? []; |
353 | |
354 | return $data + [ |
355 | 'has-minerva-languages' => $allLanguages || $allVariants, |
356 | 'array-minerva-banners' => $this->prepareBanners( $data['html-site-notice'] ), |
357 | 'data-minerva-search-box' => $data['data-search-box'] + [ |
358 | 'data-btn' => [ |
359 | 'data-icon' => [ |
360 | 'icon' => 'search', |
361 | ], |
362 | 'label' => $this->msg( 'searchbutton' )->escaped(), |
363 | 'classes' => 'skin-minerva-search-trigger', |
364 | 'array-attributes' => [ |
365 | [ |
366 | 'key' => 'id', |
367 | 'value' => 'searchIcon', |
368 | ] |
369 | ] |
370 | ], |
371 | ], |
372 | 'data-minerva-main-menu-btn' => [ |
373 | 'data-icon' => [ |
374 | 'icon' => 'menu', |
375 | ], |
376 | 'tag-name' => 'label', |
377 | 'classes' => 'toggle-list__toggle', |
378 | 'array-attributes' => [ |
379 | [ |
380 | 'key' => 'for', |
381 | 'value' => 'main-menu-input', |
382 | ], |
383 | [ |
384 | 'key' => 'id', |
385 | 'value' => 'mw-mf-main-menu-button', |
386 | ], |
387 | [ |
388 | 'key' => 'aria-hidden', |
389 | 'value' => 'true', |
390 | ], |
391 | [ |
392 | 'key' => 'data-event-name', |
393 | 'value' => 'ui.mainmenu', |
394 | ], |
395 | ], |
396 | 'text' => $this->msg( 'mobile-frontend-main-menu-button-tooltip' )->escaped(), |
397 | ], |
398 | 'data-minerva-main-menu' => $this->getMainMenu()->getMenuData( |
399 | $nav, |
400 | $this->buildSidebar() |
401 | )['items'], |
402 | 'html-minerva-tagline' => $this->getTaglineHtml(), |
403 | 'html-minerva-user-menu' => $this->getPersonalToolsMenu( $nav['user-menu'] ), |
404 | 'is-minerva-beta' => $this->skinOptions->get( SkinOptions::BETA_MODE ), |
405 | 'data-minerva-notifications' => $notifications ? [ |
406 | 'array-buttons' => $this->getNotificationButtons( $notifications ), |
407 | ] : null, |
408 | 'data-minerva-tabs' => $this->getTabsData( $nav, $associatedPages ), |
409 | 'data-minerva-page-actions' => $this->getPageActions( $nav ), |
410 | 'data-minerva-secondary-actions' => $this->getSecondaryActions( $nav ), |
411 | 'html-minerva-subject-link' => $this->getSubjectPage(), |
412 | 'data-minerva-history-link' => $this->getHistoryLink( $this->getTitle() ), |
413 | ]; |
414 | } |
415 | |
416 | /** |
417 | * Prepares the notification badges for the Button template. |
418 | * |
419 | * @internal |
420 | * @param array $notifications |
421 | * @return array |
422 | */ |
423 | public static function getNotificationButtons( array $notifications ): array { |
424 | $btns = []; |
425 | |
426 | foreach ( $notifications as $notification ) { |
427 | $linkData = $notification['array-links'][ 0 ] ?? []; |
428 | $icon = $linkData['icon'] ?? null; |
429 | if ( !$icon ) { |
430 | continue; |
431 | } |
432 | $id = $notification['id'] ?? null; |
433 | $classes = ''; |
434 | $attributes = []; |
435 | |
436 | // We don't want to output multiple attributes. |
437 | // Iterate through the attributes and pull out ID and class which |
438 | // will be defined separately. |
439 | foreach ( $linkData[ 'array-attributes' ] as $keyValuePair ) { |
440 | if ( $keyValuePair['key'] === 'class' ) { |
441 | $classes = $keyValuePair['value']; |
442 | } elseif ( $keyValuePair['key'] === 'id' ) { |
443 | // ignore. We want to use the LI `id` instead. |
444 | } else { |
445 | $attributes[] = $keyValuePair; |
446 | } |
447 | } |
448 | // add LI ID to end for use on the button. |
449 | if ( $id ) { |
450 | $attributes[] = [ |
451 | 'key' => 'id', |
452 | 'value' => $id, |
453 | ]; |
454 | } |
455 | $btns[] = [ |
456 | 'tag-name' => 'a', |
457 | // FIXME: Move preg_replace when Echo no longer provides this class. |
458 | 'classes' => preg_replace( '/oo-ui-icon-(bellOutline|tray)/', '', $classes ), |
459 | 'array-attributes' => $attributes, |
460 | 'data-icon' => [ |
461 | 'icon' => $icon, |
462 | ], |
463 | 'label' => $linkData['text'] ?? '', |
464 | ]; |
465 | } |
466 | return $btns; |
467 | } |
468 | |
469 | /** |
470 | * @return bool |
471 | */ |
472 | private function isHistoryPage(): bool { |
473 | return $this->getRequest()->getRawVal( 'action' ) === 'history'; |
474 | } |
475 | |
476 | /** |
477 | * Tabs are available if a page has page actions but is not the talk page of |
478 | * the main page. |
479 | * |
480 | * Special pages have tabs if SkinOptions::TABS_ON_SPECIALS is enabled. |
481 | * This is used by Extension:GrowthExperiments |
482 | * |
483 | * @return bool |
484 | */ |
485 | private function hasPageTabs(): bool { |
486 | $title = $this->getTitle(); |
487 | $isSpecialPageOrHistory = $title->isSpecialPage() || |
488 | $this->isHistoryPage(); |
489 | $subjectPage = $this->namespaceInfo->getSubjectPage( $title ); |
490 | $isMainPageTalk = Title::newFromLinkTarget( $subjectPage )->isMainPage(); |
491 | return ( |
492 | $this->hasPageActions() && !$isMainPageTalk && |
493 | $this->skinOptions->get( SkinOptions::TALK_AT_TOP ) |
494 | ) || ( |
495 | $isSpecialPageOrHistory && |
496 | $this->skinOptions->get( SkinOptions::TABS_ON_SPECIALS ) |
497 | ); |
498 | } |
499 | |
500 | /** |
501 | * @param array $contentNavigationUrls |
502 | * @param array $associatedPages - data-associated-pages from template data, currently only used for ID |
503 | * @return array |
504 | */ |
505 | private function getTabsData( array $contentNavigationUrls, array $associatedPages ): array { |
506 | $hasPageTabs = $this->hasPageTabs(); |
507 | if ( !$hasPageTabs ) { |
508 | return []; |
509 | } |
510 | return $contentNavigationUrls ? [ |
511 | 'items' => array_values( $contentNavigationUrls['associated-pages'] ), |
512 | 'id' => $associatedPages['id'] ?? null, |
513 | ] : []; |
514 | } |
515 | |
516 | /** |
517 | * Build the Main Menu Director by passing the skin options |
518 | * |
519 | * @return MainMenuDirector |
520 | */ |
521 | protected function getMainMenu(): MainMenuDirector { |
522 | $showMobileOptions = $this->skinOptions->get( SkinOptions::MOBILE_OPTIONS ); |
523 | // Add a donate link (see https://phabricator.wikimedia.org/T219793) |
524 | $showDonateLink = $this->skinOptions->get( SkinOptions::SHOW_DONATE ); |
525 | $builder = $this->skinOptions->get( SkinOptions::MAIN_MENU_EXPANDED ) ? |
526 | new AdvancedMainMenuBuilder( |
527 | $showMobileOptions, |
528 | $showDonateLink, |
529 | $this->definitions |
530 | ) : |
531 | new DefaultMainMenuBuilder( |
532 | $showMobileOptions, |
533 | $showDonateLink, |
534 | $this->getUser(), |
535 | $this->definitions, |
536 | $this->userIdentityUtils |
537 | ); |
538 | return new MainMenuDirector( $builder ); |
539 | } |
540 | |
541 | /** |
542 | * Prepare all Minerva menus |
543 | * |
544 | * @param array $personalUrls result of SkinTemplate::buildPersonalUrls |
545 | * @return string|null |
546 | */ |
547 | private function getPersonalToolsMenu( array $personalUrls ): ?string { |
548 | $builder = $this->skinOptions->get( SkinOptions::PERSONAL_MENU ) ? |
549 | new AdvancedUserMenuBuilder( |
550 | $this->getContext(), |
551 | $this->getUser(), |
552 | $this->definitions |
553 | ) : |
554 | new DefaultUserMenuBuilder(); |
555 | |
556 | $userMenuDirector = new UserMenuDirector( |
557 | $builder, |
558 | $this->getSkin() |
559 | ); |
560 | return $userMenuDirector->renderMenuData( $personalUrls ); |
561 | } |
562 | |
563 | /** |
564 | * @return string |
565 | */ |
566 | protected function getSubjectPage(): string { |
567 | $title = $this->getTitle(); |
568 | |
569 | // If it's a talk page, add a link to the main namespace page |
570 | // In AMC we do not need to do this as there is an easy way back to the article page |
571 | // via the talk/article tabs. |
572 | if ( $title->isTalkPage() && !$this->skinOptions->get( SkinOptions::TALK_AT_TOP ) ) { |
573 | // if it's a talk page for which we have a special message, use it |
574 | switch ( $title->getNamespace() ) { |
575 | case NS_USER_TALK: |
576 | $msg = 'mobile-frontend-talk-back-to-userpage'; |
577 | break; |
578 | case NS_PROJECT_TALK: |
579 | $msg = 'mobile-frontend-talk-back-to-projectpage'; |
580 | break; |
581 | case NS_FILE_TALK: |
582 | $msg = 'mobile-frontend-talk-back-to-filepage'; |
583 | break; |
584 | default: |
585 | // generic (all other NS) |
586 | $msg = 'mobile-frontend-talk-back-to-page'; |
587 | } |
588 | $subjectPage = $this->namespaceInfo->getSubjectPage( $title ); |
589 | |
590 | return $this->linkRenderer->makeLink( |
591 | $subjectPage, |
592 | $this->msg( $msg, $title->getText() )->text(), |
593 | [ |
594 | 'data-event-name' => 'talk.returnto', |
595 | 'class' => 'return-link' |
596 | ] |
597 | ); |
598 | } else { |
599 | return ''; |
600 | } |
601 | } |
602 | |
603 | /** |
604 | * Modifies the template data before parsing in SkinMustache. |
605 | * |
606 | * @inheritDoc |
607 | */ |
608 | final protected function doEditSectionLinksHTML( array $links, Language $lang ): string { |
609 | $transformedLinks = []; |
610 | foreach ( $links as $key => $link ) { |
611 | $transformedLinks[] = $link + [ |
612 | 'data-icon' => [ |
613 | 'icon' => $link['icon'], |
614 | ], |
615 | ]; |
616 | } |
617 | return parent::doEditSectionLinksHTML( $transformedLinks, $lang ); |
618 | } |
619 | |
620 | /** |
621 | * Takes a title and returns classes to apply to the body tag |
622 | * @param Title $title |
623 | * @return string |
624 | */ |
625 | public function getPageClasses( $title ): string { |
626 | $className = parent::getPageClasses( $title ); |
627 | $className .= ' ' . ( $this->skinOptions->get( SkinOptions::BETA_MODE ) |
628 | ? 'beta' : 'stable' ); |
629 | |
630 | if ( $this->getUser()->isRegistered() ) { |
631 | $className .= ' is-authenticated'; |
632 | } |
633 | |
634 | // The new treatment should only apply to the main namespace |
635 | if ( |
636 | $title->getNamespace() === NS_MAIN && |
637 | $this->skinOptions->get( SkinOptions::PAGE_ISSUES ) |
638 | ) { |
639 | $className .= ' issues-group-B'; |
640 | } |
641 | |
642 | return $className; |
643 | } |
644 | |
645 | /** |
646 | * Converts "1", "2", and "0" to equivalent values. |
647 | * |
648 | * @return string |
649 | */ |
650 | private static function resolveNightModeQueryValue( string $value ): string { |
651 | switch ( $value ) { |
652 | case 'day': |
653 | case 'night': |
654 | case 'os': |
655 | return $value; |
656 | case '1': |
657 | return 'night'; |
658 | case '2': |
659 | return 'os'; |
660 | default: |
661 | return 'day'; |
662 | } |
663 | } |
664 | |
665 | /** |
666 | * Provides skin-specific modifications to the HTML element attributes |
667 | * |
668 | * Currently only used for adding the night mode class |
669 | * |
670 | * @return array |
671 | */ |
672 | public function getHtmlElementAttributes(): array { |
673 | $attributes = parent::getHtmlElementAttributes(); |
674 | |
675 | // check to see if night mode is enabled via query params or by config |
676 | $webRequest = $this->getRequest(); |
677 | $forceNightMode = $webRequest->getRawVal( 'minervanightmode' ); |
678 | |
679 | // get skin config of night mode to check what is execluded |
680 | $nightModeConfig = $this->getConfig()->get( 'MinervaNightModeOptions' ); |
681 | $featuresHelper = new FeaturesHelper(); |
682 | $shouldDisableNightMode = $featuresHelper->shouldDisableNightMode( $nightModeConfig, |
683 | $webRequest, |
684 | $this->getTitle() |
685 | ); |
686 | |
687 | if ( |
688 | $this->skinOptions->get( SkinOptions::NIGHT_MODE ) || $forceNightMode !== null |
689 | ) { |
690 | $user = $this->getUser(); |
691 | $value = $this->userOptionsManager->getOption( $user, 'minerva-theme' ); |
692 | |
693 | // if forcing a (valid) setting via query params, take priority over the user option |
694 | if ( $forceNightMode !== null && in_array( $forceNightMode, [ '1', '0', '2', 'day', 'night', 'os' ] ) ) { |
695 | $value = self::resolveNightModeQueryValue( $forceNightMode ); |
696 | } |
697 | |
698 | // For T356653 add a class to the page to allow the client to detect we've |
699 | // intentionally disabled night mode. |
700 | if ( $shouldDisableNightMode ) { |
701 | $attributes[ 'class' ] .= ' skin-night-mode-page-disabled'; |
702 | return $attributes; |
703 | } |
704 | |
705 | $attributes[ 'class' ] .= " skin-theme-clientpref-$value"; |
706 | } |
707 | |
708 | return $attributes; |
709 | } |
710 | |
711 | /** |
712 | * Whether the output page contains category links and the category feature is enabled. |
713 | * @return bool |
714 | */ |
715 | private function hasCategoryLinks(): bool { |
716 | if ( !$this->skinOptions->get( SkinOptions::CATEGORIES ) ) { |
717 | return false; |
718 | } |
719 | $categoryLinks = $this->getOutput()->getCategoryLinks(); |
720 | |
721 | if ( !count( $categoryLinks ) ) { |
722 | return false; |
723 | } |
724 | return !empty( $categoryLinks['normal'] ) || !empty( $categoryLinks['hidden'] ); |
725 | } |
726 | |
727 | /** |
728 | * Get a history link which describes author and relative time of last edit |
729 | * @param Title $title The Title object of the page being viewed |
730 | * @param string $timestamp |
731 | * @return array |
732 | */ |
733 | protected function getRelativeHistoryLink( Title $title, string $timestamp ): array { |
734 | $user = $this->getUser(); |
735 | $userDate = $this->getLanguage()->userDate( $timestamp, $user ); |
736 | $text = $this->msg( |
737 | 'minerva-last-modified-date', $userDate, |
738 | $this->getLanguage()->userTime( $timestamp, $user ) |
739 | )->parse(); |
740 | return [ |
741 | // Use $edit['timestamp'] (Unix format) instead of $timestamp (MW format) |
742 | 'data-timestamp' => wfTimestamp( TS_UNIX, $timestamp ), |
743 | 'href' => $this->getHistoryUrl( $title ), |
744 | 'text' => $text, |
745 | ] + $this->getRevisionEditorData( $title ); |
746 | } |
747 | |
748 | /** |
749 | * Get a history link which makes no reference to user or last edited time |
750 | * @param Title $title The Title object of the page being viewed |
751 | * @return array |
752 | */ |
753 | protected function getGenericHistoryLink( Title $title ): array { |
754 | $text = $this->msg( 'mobile-frontend-history' )->plain(); |
755 | return [ |
756 | 'href' => $this->getHistoryUrl( $title ), |
757 | 'text' => $text, |
758 | ]; |
759 | } |
760 | |
761 | /** |
762 | * Checks if the Special:History page is being used. |
763 | * @param Title $title The Title object of the page being viewed |
764 | * @return bool |
765 | */ |
766 | private function shouldUseSpecialHistory( Title $title ): bool { |
767 | return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) && |
768 | SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->getUser() ); |
769 | } |
770 | |
771 | /** |
772 | * Get the URL for the history page for the given title using Special:History |
773 | * when available. |
774 | * @param Title $title The Title object of the page being viewed |
775 | * @return string |
776 | */ |
777 | protected function getHistoryUrl( Title $title ): string { |
778 | return $this->shouldUseSpecialHistory( $title ) ? |
779 | SpecialPage::getTitleFor( 'History', $title )->getLocalURL() : |
780 | $title->getLocalURL( [ 'action' => 'history' ] ); |
781 | } |
782 | |
783 | /** |
784 | * Prepare the content for the 'last edited' message, e.g. 'Last edited on 30 August |
785 | * 2013, at 23:31'. This message is different for the main page since main page |
786 | * content is typically transcluded rather than edited directly. |
787 | * |
788 | * The relative time is only rendered on the latest revision. |
789 | * For older revisions the last modified information will not render with a relative time |
790 | * nor will it show the name of the editor. |
791 | * @param Title $title The Title object of the page being viewed |
792 | * @return array|null |
793 | */ |
794 | protected function getHistoryLink( Title $title ): ?array { |
795 | if ( !$title->exists() || |
796 | $this->getActionName() !== 'view' |
797 | ) { |
798 | return null; |
799 | } |
800 | // Do not show the last modified bar on diff pages [T350515] |
801 | $request = $this->getRequest(); |
802 | if ( $request->getCheck( 'diff' ) ) { |
803 | return null; |
804 | } |
805 | |
806 | $out = $this->getOutput(); |
807 | |
808 | if ( !$out->getRevisionId() || !$out->isRevisionCurrent() || $title->isMainPage() ) { |
809 | $historyLink = $this->getGenericHistoryLink( $title ); |
810 | } else { |
811 | // Get rev_timestamp of current revision (preloaded by MediaWiki core) |
812 | $timestamp = $out->getRevisionTimestamp(); |
813 | if ( !$timestamp ) { |
814 | # No cached timestamp, load it from the database |
815 | $timestamp = $this->revisionLookup->getTimestampFromId( $out->getRevisionId() ); |
816 | } |
817 | $historyLink = $this->getRelativeHistoryLink( $title, $timestamp ); |
818 | } |
819 | |
820 | return $historyLink + [ |
821 | 'historyIcon' => [ |
822 | 'icon' => 'modified-history', |
823 | 'size' => 'medium' |
824 | ], |
825 | 'arrowIcon' => [ |
826 | 'icon' => 'expand', |
827 | 'size' => 'small' |
828 | ] |
829 | ]; |
830 | } |
831 | |
832 | /** |
833 | * Returns data attributes representing the editor for the current revision. |
834 | * @param LinkTarget $title The Title object of the page being viewed |
835 | * @return array representing user with name and gender fields. Empty if the editor no longer |
836 | * exists in the database or is hidden from public view. |
837 | */ |
838 | private function getRevisionEditorData( LinkTarget $title ): array { |
839 | $rev = $this->revisionLookup->getRevisionByTitle( $title ); |
840 | $result = []; |
841 | if ( $rev ) { |
842 | $revUser = $rev->getUser(); |
843 | // Note the user will only be returned if that information is public |
844 | if ( $revUser ) { |
845 | $editorName = $revUser->getName(); |
846 | $editorGender = $this->genderCache->getGenderOf( $revUser, __METHOD__ ); |
847 | $result += [ |
848 | 'data-user-name' => $editorName, |
849 | 'data-user-gender' => $editorGender, |
850 | ]; |
851 | } |
852 | } |
853 | return $result; |
854 | } |
855 | |
856 | /** |
857 | * Returns the HTML representing the tagline |
858 | * @return string HTML for tagline |
859 | */ |
860 | protected function getTaglineHtml(): string { |
861 | $tagline = ''; |
862 | |
863 | $pageUser = $this->skinUserPageHelper->getPageUser(); |
864 | if ( $pageUser ) { |
865 | $fromDate = $pageUser->getRegistration(); |
866 | |
867 | if ( $this->skinUserPageHelper->isUserPageAccessibleToCurrentUser() && is_string( $fromDate ) ) { |
868 | $fromDateTs = wfTimestamp( TS_UNIX, $fromDate ); |
869 | |
870 | // This is shown when js is disabled. js enhancement made due to caching |
871 | $tagline = $this->msg( 'mobile-frontend-user-page-member-since', |
872 | $this->getLanguage()->userDate( new MWTimestamp( $fromDateTs ), $this->getUser() ), |
873 | $pageUser )->text(); |
874 | |
875 | // Define html attributes for usage with js enhancement (unix timestamp, gender) |
876 | $attrs = [ 'id' => 'tagline-userpage', |
877 | 'data-userpage-registration-date' => $fromDateTs, |
878 | 'data-userpage-gender' => $this->genderCache->getGenderOf( $pageUser, __METHOD__ ) ]; |
879 | } |
880 | } else { |
881 | if ( $this->getTitle() ) { |
882 | $out = $this->getOutput(); |
883 | $tagline = $out->getProperty( 'wgMFDescription' ); |
884 | } |
885 | } |
886 | |
887 | $attrs[ 'class' ] = 'tagline'; |
888 | return Html::element( 'div', $attrs, $tagline ); |
889 | } |
890 | |
891 | /** |
892 | * Returns the HTML representing the heading. |
893 | * |
894 | * @param string $heading The heading suggested by core. |
895 | * @return string HTML for header |
896 | */ |
897 | private function getUserPageHeadingHtml( string $heading ): string { |
898 | // The heading is just the username without namespace |
899 | return Html::element( 'h1', |
900 | // These IDs and classes should match Skin::getTemplateData |
901 | [ |
902 | 'id' => 'firstHeading', |
903 | 'class' => 'firstHeading mw-first-heading mw-minerva-user-heading', |
904 | ], |
905 | $this->skinUserPageHelper->getPageUser()->getName() |
906 | ); |
907 | } |
908 | |
909 | /** |
910 | * Load internal banner content to show in pre content in template |
911 | * Beware of HTML caching when using this function. |
912 | * Content set as "internalbanner" |
913 | * @param string $siteNotice HTML fragment |
914 | * @return array |
915 | */ |
916 | protected function prepareBanners( string $siteNotice ): array { |
917 | $banners = []; |
918 | if ( $siteNotice && $this->getConfig()->get( 'MinervaEnableSiteNotice' ) ) { |
919 | $banners[] = $siteNotice; |
920 | } else { |
921 | $banners[] = '<div id="siteNotice"></div>'; |
922 | } |
923 | return $banners; |
924 | } |
925 | |
926 | /** |
927 | * Returns an array with details for a language button. |
928 | * @return array |
929 | */ |
930 | protected function getLanguageButton(): array { |
931 | return [ |
932 | 'array-attributes' => [ |
933 | [ |
934 | 'key' => 'href', |
935 | 'value' => '#p-lang' |
936 | ] |
937 | ], |
938 | 'tag-name' => 'a', |
939 | 'classes' => 'language-selector button', |
940 | 'label' => $this->msg( 'mobile-frontend-language-article-heading' )->text() |
941 | ]; |
942 | } |
943 | |
944 | /** |
945 | * Returns an array with details for a talk button. |
946 | * @param Title $talkTitle Title object of the talk page |
947 | * @param string $label Button label |
948 | * @return array |
949 | */ |
950 | protected function getTalkButton( Title $talkTitle, string $label ): array { |
951 | return [ |
952 | 'array-attributes' => [ |
953 | [ |
954 | 'key' => 'href', |
955 | 'value' => $talkTitle->getLinkURL(), |
956 | ], |
957 | [ |
958 | 'key' => 'data-title', |
959 | 'value' => $talkTitle->getFullText(), |
960 | ] |
961 | ], |
962 | 'tag-name' => 'a', |
963 | 'classes' => 'talk button', |
964 | 'label' => $label, |
965 | ]; |
966 | } |
967 | |
968 | /** |
969 | * Returns an array of links for page secondary actions |
970 | * @param array $contentNavigationUrls |
971 | * @return array|null |
972 | */ |
973 | protected function getSecondaryActions( array $contentNavigationUrls ): ?array { |
974 | if ( $this->isFallbackEditor() || !$this->hasSecondaryActions() ) { |
975 | return null; |
976 | } |
977 | |
978 | $buttons = []; |
979 | // always add a button to link to the talk page |
980 | // it will link to the wikitext talk page |
981 | $title = $this->getTitle(); |
982 | $subjectPage = Title::newFromLinkTarget( $this->namespaceInfo->getSubjectPage( $title ) ); |
983 | $talkAtBottom = !$this->skinOptions->get( SkinOptions::TALK_AT_TOP ) || |
984 | $subjectPage->isMainPage(); |
985 | if ( !$this->skinUserPageHelper->isUserPage() && |
986 | $this->permissions->isTalkAllowed() && $talkAtBottom && |
987 | // When showing talk at the bottom we restrict this so it is not shown to anons |
988 | // https://phabricator.wikimedia.org/T54165 |
989 | // This whole code block can be removed when SkinOptions::TALK_AT_TOP is always true |
990 | $this->getUser()->isRegistered() |
991 | ) { |
992 | $namespaces = $contentNavigationUrls['associated-pages']; |
993 | // FIXME [core]: This seems unnecessary.. |
994 | $subjectId = $title->getNamespaceKey( '' ); |
995 | $talkId = $subjectId === 'main' ? 'talk' : "{$subjectId}_talk"; |
996 | |
997 | if ( isset( $namespaces[$talkId] ) ) { |
998 | $talkButton = $namespaces[$talkId]; |
999 | $talkTitle = Title::newFromLinkTarget( $this->namespaceInfo->getTalkPage( $title ) ); |
1000 | |
1001 | $buttons['talk'] = $this->getTalkButton( $talkTitle, $talkButton['text'] ); |
1002 | } |
1003 | } |
1004 | |
1005 | if ( |
1006 | $this->languagesHelper->doesTitleHasLanguagesOrVariants( $this->getOutput(), $title ) && |
1007 | $title->isMainPage() |
1008 | ) { |
1009 | $buttons['language'] = $this->getLanguageButton(); |
1010 | } |
1011 | |
1012 | return $buttons; |
1013 | } |
1014 | |
1015 | /** |
1016 | * @inheritDoc |
1017 | * @return array |
1018 | */ |
1019 | protected function getJsConfigVars(): array { |
1020 | return array_merge( parent::getJsConfigVars(), [ |
1021 | 'wgMinervaPermissions' => [ |
1022 | 'watchable' => $this->permissions->isAllowed( IMinervaPagePermissions::WATCHABLE ), |
1023 | 'watch' => $this->permissions->isAllowed( IMinervaPagePermissions::WATCH ), |
1024 | ], |
1025 | 'wgMinervaFeatures' => $this->skinOptions->getAll(), |
1026 | 'wgMinervaDownloadNamespaces' => $this->getConfig()->get( 'MinervaDownloadNamespaces' ), |
1027 | ] ); |
1028 | } |
1029 | |
1030 | /** |
1031 | * Returns the javascript entry modules to load. Only modules that need to |
1032 | * be overriden or added conditionally should be placed here. |
1033 | * @return array |
1034 | */ |
1035 | public function getDefaultModules(): array { |
1036 | $modules = parent::getDefaultModules(); |
1037 | |
1038 | // FIXME: T223204: Dequeue default content modules except for the history |
1039 | // action. Allow default content modules on history action in order to |
1040 | // enable toggling of the filters. |
1041 | // Long term this won't be necessary when T111565 is resolved and a |
1042 | // more general solution can be used. |
1043 | if ( $this->getActionName() !== 'history' ) { |
1044 | // dequeue default content modules (toc, collapsible, etc.) |
1045 | $modules['content'] = array_diff( $modules['content'], [ |
1046 | // T111565 |
1047 | 'jquery.makeCollapsible', |
1048 | // Minerva provides its own implementation. Loading this will break display. |
1049 | 'mediawiki.toc' |
1050 | ] ); |
1051 | // dequeue styles associated with `content` key. |
1052 | $modules['styles']['content'] = array_diff( $modules['styles']['content'], [ |
1053 | // T111565 |
1054 | 'jquery.makeCollapsible.styles', |
1055 | ] ); |
1056 | } |
1057 | |
1058 | $modules['styles']['skin.page'] = $this->getPageSpecificStyles(); |
1059 | $modules['styles']['skin.features'] = $this->getFeatureSpecificStyles(); |
1060 | |
1061 | return $modules; |
1062 | } |
1063 | |
1064 | /** |
1065 | * Provide styles required to present the server rendered page in this skin. Additional styles |
1066 | * may be loaded dynamically by the client. |
1067 | * |
1068 | * Any styles returned by this method are loaded on the critical rendering path as linked |
1069 | * stylesheets. I.e., they are required to load on the client before first paint. |
1070 | * |
1071 | * @return array |
1072 | */ |
1073 | protected function getPageSpecificStyles(): array { |
1074 | $styles = []; |
1075 | $title = $this->getTitle(); |
1076 | $request = $this->getRequest(); |
1077 | $requestAction = $this->getActionName(); |
1078 | $viewAction = $requestAction === 'view'; |
1079 | |
1080 | if ( $title->isMainPage() ) { |
1081 | $styles[] = 'skins.minerva.mainPage.styles'; |
1082 | } elseif ( $this->skinUserPageHelper->isUserPage() ) { |
1083 | $styles[] = 'skins.minerva.userpage.styles'; |
1084 | } |
1085 | |
1086 | if ( $this->getUser()->isRegistered() ) { |
1087 | $styles[] = 'skins.minerva.loggedin.styles'; |
1088 | } |
1089 | |
1090 | // When any of these features are enabled in production |
1091 | // remove the if condition |
1092 | // and move the associated LESS file inside `skins.minerva.amc.styles` |
1093 | // into a more appropriate module. |
1094 | if ( |
1095 | // T356117 - enable on all special pages - some special pages e.g. Special:Contribute have tabs. |
1096 | $title->isSpecialPage() || |
1097 | ( $this->isHistoryPage() && !$this->shouldUseSpecialHistory( $title ) ) || |
1098 | $this->skinOptions->get( SkinOptions::PERSONAL_MENU ) || |
1099 | $this->skinOptions->get( SkinOptions::TALK_AT_TOP ) || |
1100 | $this->skinOptions->get( SkinOptions::HISTORY_IN_PAGE_ACTIONS ) || |
1101 | $this->skinOptions->get( SkinOptions::TOOLBAR_SUBMENU ) |
1102 | ) { |
1103 | // SkinOptions::PERSONAL_MENU + SkinOptions::TOOLBAR_SUBMENU uses ToggleList |
1104 | // SkinOptions::TALK_AT_TOP uses tabs.less |
1105 | // SkinOptions::HISTORY_IN_PAGE_ACTIONS + SkinOptions::TOOLBAR_SUBMENU uses pageactions.less |
1106 | $styles[] = 'skins.minerva.amc.styles'; |
1107 | } |
1108 | |
1109 | return $styles; |
1110 | } |
1111 | |
1112 | /** |
1113 | * Provide styles required to present the server rendered page with related features in this skin. |
1114 | * Additional styles may be loaded dynamically by the client. |
1115 | * |
1116 | * Any styles returned by this method are loaded on the critical rendering path as linked |
1117 | * stylesheets. I.e., they are required to load on the client before first paint. |
1118 | * |
1119 | * @return array |
1120 | */ |
1121 | protected function getFeatureSpecificStyles(): array { |
1122 | $styles = []; |
1123 | |
1124 | if ( $this->hasCategoryLinks() ) { |
1125 | $styles[] = 'skins.minerva.categories.styles'; |
1126 | } |
1127 | |
1128 | if ( $this->skinOptions->get( SkinOptions::PERSONAL_MENU ) ) { |
1129 | // If ever enabled as the default, please remove the duplicate icons |
1130 | // inside skins.minerva.mainMenu.icons. See comment for MAIN_MENU_EXPANDED |
1131 | $styles[] = 'skins.minerva.personalMenu.icons'; |
1132 | } |
1133 | |
1134 | if ( |
1135 | $this->skinOptions->get( SkinOptions::MAIN_MENU_EXPANDED ) |
1136 | ) { |
1137 | // If ever enabled as the default, please review skins.minerva.mainMenu.icons |
1138 | // and remove any unneeded icons |
1139 | $styles[] = 'skins.minerva.mainMenu.advanced.icons'; |
1140 | } |
1141 | |
1142 | if ( |
1143 | $this->skinOptions->get( SkinOptions::PERSONAL_MENU ) || |
1144 | $this->skinOptions->get( SkinOptions::TOOLBAR_SUBMENU ) |
1145 | ) { |
1146 | // SkinOptions::PERSONAL_MENU requires the `userTalk` icon. |
1147 | // SkinOptions::TOOLBAR_SUBMENU requires the rest of the icons including `overflow`. |
1148 | // Note `skins.minerva.overflow.icons` is pulled down by skins.minerva.scripts but the menu can |
1149 | // work without JS. |
1150 | $styles[] = 'skins.minerva.overflow.icons'; |
1151 | } |
1152 | |
1153 | return $styles; |
1154 | } |
1155 | } |