Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
3.86% |
11 / 285 |
|
5.56% |
1 / 18 |
CRAP | |
0.00% |
0 / 1 |
SkinVector22 | |
3.86% |
11 / 285 |
|
5.56% |
1 / 18 |
3936.84 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
runOnSkinTemplateNavigationHooks | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isResponsive | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
isTocAvailable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canHaveLanguages | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
removeAddTopicButton | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getFeatureManager | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isLanguagesInContentAt | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
110 | |||
isLanguagesInContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getLanguagesCached | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isULSExtensionEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isVisualEditorTabPositionFirst | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
shouldHideLanguages | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
30 | |||
mergeViewOverflowIntoActions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getHtmlElementAttributes | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
extractPageToolsFromSidebar | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
getULSLabels | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getTemplateData | |
0.00% |
0 / 187 |
|
0.00% |
0 / 1 |
306 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Skins\Vector; |
4 | |
5 | use ExtensionRegistry; |
6 | use MediaWiki\MediaWikiServices; |
7 | use MediaWiki\Skins\Vector\Components\VectorComponentAppearance; |
8 | use MediaWiki\Skins\Vector\Components\VectorComponentButton; |
9 | use MediaWiki\Skins\Vector\Components\VectorComponentDropdown; |
10 | use MediaWiki\Skins\Vector\Components\VectorComponentLanguageDropdown; |
11 | use MediaWiki\Skins\Vector\Components\VectorComponentMainMenu; |
12 | use MediaWiki\Skins\Vector\Components\VectorComponentPageTools; |
13 | use MediaWiki\Skins\Vector\Components\VectorComponentPinnableContainer; |
14 | use MediaWiki\Skins\Vector\Components\VectorComponentSearchBox; |
15 | use MediaWiki\Skins\Vector\Components\VectorComponentStickyHeader; |
16 | use MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents; |
17 | use MediaWiki\Skins\Vector\Components\VectorComponentUserLinks; |
18 | use MediaWiki\Skins\Vector\Components\VectorComponentVariants; |
19 | use MediaWiki\Skins\Vector\FeatureManagement\FeatureManager; |
20 | use MediaWiki\Skins\Vector\FeatureManagement\FeatureManagerFactory; |
21 | use RuntimeException; |
22 | use SkinMustache; |
23 | use SkinTemplate; |
24 | |
25 | /** |
26 | * @ingroup Skins |
27 | * @package Vector |
28 | * @internal |
29 | */ |
30 | class SkinVector22 extends SkinMustache { |
31 | private const STICKY_HEADER_ENABLED_CLASS = 'vector-sticky-header-enabled'; |
32 | /** @var null|array for caching purposes */ |
33 | private $languages; |
34 | |
35 | private FeatureManagerFactory $featureManagerFactory; |
36 | private ?FeatureManager $featureManager = null; |
37 | |
38 | public function __construct( |
39 | FeatureManagerFactory $featureManagerFactory, |
40 | array $options |
41 | ) { |
42 | parent::__construct( $options ); |
43 | |
44 | // Cannot use the context in the constructor, setContext is called after construction |
45 | $this->featureManagerFactory = $featureManagerFactory; |
46 | } |
47 | |
48 | /** |
49 | * @inheritDoc |
50 | */ |
51 | protected function runOnSkinTemplateNavigationHooks( SkinTemplate $skin, &$content_navigation ) { |
52 | parent::runOnSkinTemplateNavigationHooks( $skin, $content_navigation ); |
53 | Hooks::onSkinTemplateNavigation( $skin, $content_navigation ); |
54 | } |
55 | |
56 | /** |
57 | * @inheritDoc |
58 | */ |
59 | public function isResponsive() { |
60 | // Check it's enabled by user preference and configuration |
61 | $responsive = parent::isResponsive() && $this->getConfig()->get( 'VectorResponsive' ); |
62 | // For historic reasons, the viewport is added when Vector is loaded on the mobile |
63 | // domain. This is only possible for 3rd parties or by useskin parameter as there is |
64 | // no preference for changing mobile skin. Only need to check if $responsive is falsey. |
65 | if ( !$responsive && ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) ) { |
66 | $mobFrontContext = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' ); |
67 | if ( $mobFrontContext->shouldDisplayMobileView() ) { |
68 | return true; |
69 | } |
70 | } |
71 | return $responsive; |
72 | } |
73 | |
74 | /** |
75 | * Whether or not toc data is available |
76 | * |
77 | * @param array $parentData Template data |
78 | * @return bool |
79 | */ |
80 | private function isTocAvailable( array $parentData ): bool { |
81 | return !empty( $parentData['data-toc'][ 'array-sections' ] ); |
82 | } |
83 | |
84 | /** |
85 | * This should be upstreamed to the Skin class in core once the logic is finalized. |
86 | * Returns false if the page is a special page without any languages, or if an action |
87 | * other than view is being used. |
88 | * |
89 | * @return bool |
90 | */ |
91 | private function canHaveLanguages(): bool { |
92 | $action = $this->getActionName(); |
93 | |
94 | // FIXME: This logic should be moved into the ULS extension or core given the button is hidden, |
95 | // it should not be rendered, short term fix for T328996. |
96 | if ( $action === 'history' ) { |
97 | return false; |
98 | } |
99 | |
100 | $title = $this->getTitle(); |
101 | // Defensive programming - if a special page has added languages explicitly, best to show it. |
102 | if ( $title && $title->isSpecialPage() && $this->getLanguagesCached() === [] ) { |
103 | return false; |
104 | } |
105 | return true; |
106 | } |
107 | |
108 | /** |
109 | * Remove the add topic button from data-views if present |
110 | * |
111 | * @param array &$parentData Template data |
112 | * @return bool An add topic button was removed |
113 | */ |
114 | private function removeAddTopicButton( array &$parentData ): bool { |
115 | $views = $parentData['data-portlets']['data-views']['array-items']; |
116 | $hasAddTopicButton = false; |
117 | $html = ''; |
118 | foreach ( $views as $i => $view ) { |
119 | if ( $view['id'] === 'ca-addsection' ) { |
120 | array_splice( $views, $i, 1 ); |
121 | $hasAddTopicButton = true; |
122 | continue; |
123 | } |
124 | $html .= $view['html-item']; |
125 | } |
126 | $parentData['data-portlets']['data-views']['array-items'] = $views; |
127 | $parentData['data-portlets']['data-views']['html-items'] = $html; |
128 | return $hasAddTopicButton; |
129 | } |
130 | |
131 | private function getFeatureManager(): FeatureManager { |
132 | if ( $this->featureManager === null ) { |
133 | $this->featureManager = $this->featureManagerFactory->createFeatureManager( $this->getContext() ); |
134 | } |
135 | return $this->featureManager; |
136 | } |
137 | |
138 | /** |
139 | * @param string $location Either 'top' or 'bottom' is accepted. |
140 | * @return bool |
141 | */ |
142 | protected function isLanguagesInContentAt( string $location ): bool { |
143 | if ( !$this->canHaveLanguages() ) { |
144 | return false; |
145 | } |
146 | $featureManager = $this->getFeatureManager(); |
147 | $inContent = $featureManager->isFeatureEnabled( |
148 | Constants::FEATURE_LANGUAGE_IN_HEADER |
149 | ); |
150 | $title = $this->getTitle(); |
151 | $isMainPage = $title ? $title->isMainPage() : false; |
152 | |
153 | switch ( $location ) { |
154 | case 'top': |
155 | return $isMainPage ? $inContent && $featureManager->isFeatureEnabled( |
156 | Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER |
157 | ) : $inContent; |
158 | case 'bottom': |
159 | return $inContent && $isMainPage && !$featureManager->isFeatureEnabled( |
160 | Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER |
161 | ); |
162 | default: |
163 | throw new RuntimeException( 'unknown language button location' ); |
164 | } |
165 | } |
166 | |
167 | /** |
168 | * Whether or not the languages are out of the sidebar and in the content either at |
169 | * the top or the bottom. |
170 | * |
171 | * @return bool |
172 | */ |
173 | final protected function isLanguagesInContent(): bool { |
174 | return $this->isLanguagesInContentAt( 'top' ) || $this->isLanguagesInContentAt( 'bottom' ); |
175 | } |
176 | |
177 | /** |
178 | * Calls getLanguages with caching. |
179 | * |
180 | * @return array |
181 | */ |
182 | protected function getLanguagesCached(): array { |
183 | if ( $this->languages === null ) { |
184 | $this->languages = $this->getLanguages(); |
185 | } |
186 | return $this->languages; |
187 | } |
188 | |
189 | /** |
190 | * Check whether ULS is enabled |
191 | * |
192 | * @return bool |
193 | */ |
194 | final protected function isULSExtensionEnabled(): bool { |
195 | return ExtensionRegistry::getInstance()->isLoaded( 'UniversalLanguageSelector' ); |
196 | } |
197 | |
198 | /** |
199 | * Check whether Visual Editor Tab Position is first |
200 | * |
201 | * @param array $dataViews |
202 | * @return bool |
203 | */ |
204 | final protected function isVisualEditorTabPositionFirst( $dataViews ): bool { |
205 | $names = [ 've-edit', 'edit' ]; |
206 | // find if under key 'name' 've-edit' or 'edit' is the before item in the array |
207 | for ( $i = 0; $i < count( $dataViews[ 'array-items' ] ); $i++ ) { |
208 | if ( in_array( $dataViews[ 'array-items' ][ $i ][ 'name' ], $names ) ) { |
209 | return $dataViews[ 'array-items' ][ $i ][ 'name' ] === $names[ 0 ]; |
210 | } |
211 | } |
212 | return false; |
213 | } |
214 | |
215 | /** |
216 | * Show the ULS button if it's modern Vector, languages in header is enabled, |
217 | * the ULS extension is enabled, and we are on a subect page. Hide it otherwise. |
218 | * There is no point in showing the language button if ULS extension is unavailable |
219 | * as there is no ways to add languages without it. |
220 | * @return bool |
221 | */ |
222 | protected function shouldHideLanguages(): bool { |
223 | $title = $this->getTitle(); |
224 | $isSubjectPage = $title && $title->exists() && !$title->isTalkPage(); |
225 | return !$this->isLanguagesInContent() || !$this->isULSExtensionEnabled() || !$isSubjectPage; |
226 | } |
227 | |
228 | /** |
229 | * Merges the `view-overflow` menu into the `action` menu. |
230 | * This ensures that the previous state of the menu e.g. emptyPortlet class |
231 | * is preserved. |
232 | * |
233 | * @param array $data |
234 | * @return array |
235 | */ |
236 | private function mergeViewOverflowIntoActions( array $data ): array { |
237 | $portlets = $data['data-portlets']; |
238 | $actions = $portlets['data-actions']; |
239 | $overflow = $portlets['data-views-overflow']; |
240 | // if the views overflow menu is not empty, then signal that the more menu despite |
241 | // being initially empty now has collapsible items. |
242 | if ( !$overflow['is-empty'] ) { |
243 | $data['data-portlets']['data-actions']['class'] .= ' vector-has-collapsible-items'; |
244 | } |
245 | $data['data-portlets']['data-actions']['html-items'] = $overflow['html-items'] . $actions['html-items']; |
246 | return $data; |
247 | } |
248 | |
249 | /** |
250 | * @inheritDoc |
251 | */ |
252 | public function getHtmlElementAttributes() { |
253 | $original = parent::getHtmlElementAttributes(); |
254 | $featureManager = $this->getFeatureManager(); |
255 | $original['class'] .= ' ' . implode( ' ', $featureManager->getFeatureBodyClass() ); |
256 | |
257 | if ( $featureManager->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER ) ) { |
258 | // T290518: Add scroll padding to root element when the sticky header is |
259 | // enabled. This class needs to be server rendered instead of added from |
260 | // JS in order to correctly handle situations where the sticky header |
261 | // isn't visible yet but we still need scroll padding applied (e.g. when |
262 | // the user navigates to a page with a hash fragment in the URI). For this |
263 | // reason, we can't rely on the `vector-sticky-header-visible` class as it |
264 | // is added too late. |
265 | // |
266 | // Please note that this class applies scroll padding which does not work |
267 | // when applied to the body tag in Chrome, Safari, and Firefox (and |
268 | // possibly others). It must instead be applied to the html tag. |
269 | $original['class'] = implode( ' ', [ $original['class'] ?? '', self::STICKY_HEADER_ENABLED_CLASS ] ); |
270 | } |
271 | $original['class'] = trim( $original['class'] ); |
272 | |
273 | return $original; |
274 | } |
275 | |
276 | /** |
277 | * Pulls the page tools menu out of $sidebar into $pageToolsMenu |
278 | * |
279 | * @param array &$sidebar |
280 | * @param array &$pageToolsMenu |
281 | */ |
282 | private static function extractPageToolsFromSidebar( array &$sidebar, array &$pageToolsMenu ) { |
283 | $restPortlets = $sidebar[ 'array-portlets-rest' ] ?? []; |
284 | $toolboxMenuIndex = array_search( |
285 | VectorComponentPageTools::TOOLBOX_ID, |
286 | array_column( |
287 | $restPortlets, |
288 | 'id' |
289 | ) |
290 | ); |
291 | |
292 | if ( $toolboxMenuIndex !== false ) { |
293 | // Splice removes the toolbox menu from the $restPortlets array |
294 | // and current returns the first value of array_splice, i.e. the $toolbox menu data. |
295 | $pageToolsMenu = array_splice( $restPortlets, $toolboxMenuIndex ); |
296 | $sidebar['array-portlets-rest'] = $restPortlets; |
297 | } |
298 | } |
299 | |
300 | /** |
301 | * Get the ULS button label, accounting for the number of available |
302 | * languages. |
303 | * |
304 | * @return array |
305 | */ |
306 | final protected function getULSLabels(): array { |
307 | $numLanguages = count( $this->getLanguagesCached() ); |
308 | |
309 | if ( $numLanguages === 0 ) { |
310 | return [ |
311 | 'label' => $this->msg( 'vector-no-language-button-label' )->text(), |
312 | 'aria-label' => $this->msg( 'vector-no-language-button-aria-label' )->text() |
313 | ]; |
314 | } else { |
315 | return [ |
316 | 'label' => $this->msg( 'vector-language-button-label' )->numParams( $numLanguages )->text(), |
317 | 'aria-label' => $this->msg( 'vector-language-button-aria-label' )->numParams( $numLanguages )->text() |
318 | ]; |
319 | } |
320 | } |
321 | |
322 | /** |
323 | * @return array |
324 | */ |
325 | public function getTemplateData(): array { |
326 | $parentData = parent::getTemplateData(); |
327 | $parentData = $this->mergeViewOverflowIntoActions( $parentData ); |
328 | $portlets = $parentData['data-portlets']; |
329 | |
330 | $langData = $portlets['data-languages'] ?? null; |
331 | $config = $this->getConfig(); |
332 | $featureManager = $this->getFeatureManager(); |
333 | |
334 | $sidebar = $parentData[ 'data-portlets-sidebar' ]; |
335 | $pageToolsMenu = []; |
336 | self::extractPageToolsFromSidebar( $sidebar, $pageToolsMenu ); |
337 | |
338 | $hasAddTopicButton = $config->get( 'VectorPromoteAddTopic' ) && |
339 | $this->removeAddTopicButton( $parentData ); |
340 | |
341 | $langButtonClass = $langData['class'] ?? ''; |
342 | $ulsLabels = $this->getULSLabels(); |
343 | $user = $this->getUser(); |
344 | $localizer = $this->getContext(); |
345 | $title = $this->getTitle(); |
346 | |
347 | // If the table of contents has no items, we won't output it. |
348 | // empty array is interpreted by Mustache as falsey. |
349 | $tocComponents = []; |
350 | if ( $this->isTocAvailable( $parentData ) ) { |
351 | // @phan-suppress-next-line SecurityCheck-XSS |
352 | $dataToc = new VectorComponentTableOfContents( |
353 | $parentData['data-toc'], |
354 | $localizer, |
355 | $config, |
356 | $featureManager |
357 | ); |
358 | $isPinned = $dataToc->isPinned(); |
359 | $tocComponents = [ |
360 | 'data-toc' => $dataToc, |
361 | 'data-toc-pinnable-container' => new VectorComponentPinnableContainer( |
362 | VectorComponentTableOfContents::ID, |
363 | $isPinned |
364 | ), |
365 | 'data-page-titlebar-toc-dropdown' => new VectorComponentDropdown( |
366 | 'vector-page-titlebar-toc', |
367 | // label |
368 | $this->msg( 'vector-toc-collapsible-button-label' ), |
369 | // class |
370 | 'vector-page-titlebar-toc vector-button-flush-left', |
371 | // icon |
372 | 'listBullet', |
373 | ), |
374 | 'data-page-titlebar-toc-pinnable-container' => new VectorComponentPinnableContainer( |
375 | 'vector-page-titlebar-toc', |
376 | $isPinned |
377 | ), |
378 | 'data-sticky-header-toc-dropdown' => new VectorComponentDropdown( |
379 | 'vector-sticky-header-toc', |
380 | // label |
381 | $this->msg( 'vector-toc-collapsible-button-label' ), |
382 | // class |
383 | 'mw-portlet mw-portlet-sticky-header-toc vector-sticky-header-toc vector-button-flush-left', |
384 | // icon |
385 | 'listBullet' |
386 | ), |
387 | 'data-sticky-header-toc-pinnable-container' => new VectorComponentPinnableContainer( |
388 | 'vector-sticky-header-toc', |
389 | $isPinned |
390 | ), |
391 | ]; |
392 | $this->getOutput()->addHtmlClasses( 'vector-toc-available' ); |
393 | } else { |
394 | $this->getOutput()->addHtmlClasses( 'vector-toc-not-available' ); |
395 | } |
396 | |
397 | $isRegistered = $user->isRegistered(); |
398 | $userPage = $isRegistered ? $this->buildPersonalPageItem() : []; |
399 | $isAppearanceEnabled = $featureManager->isFeatureEnabled( |
400 | Constants::FEATURE_APPEARANCE, |
401 | ); |
402 | |
403 | $components = $tocComponents + [ |
404 | 'data-add-topic-button' => $hasAddTopicButton ? new VectorComponentButton( |
405 | $this->msg( [ 'vector-2022-action-addsection', 'skin-action-addsection' ] )->text(), |
406 | 'speechBubbleAdd-progressive', |
407 | 'ca-addsection', |
408 | '', |
409 | [ 'data-event-name' => 'addsection-header' ], |
410 | 'quiet', |
411 | 'progressive', |
412 | false, |
413 | $title->getLocalURL( [ 'action' => 'edit', 'section' => 'new' ] ) |
414 | ) : null, |
415 | 'data-variants' => new VectorComponentVariants( |
416 | $portlets['data-variants'], |
417 | $title->getPageLanguage(), |
418 | $this->msg( 'vector-language-variant-switcher-label' ) |
419 | ), |
420 | 'data-vector-user-links' => new VectorComponentUserLinks( |
421 | $localizer, |
422 | $user, |
423 | $portlets, |
424 | $this->getOptions()['link'], |
425 | $userPage[ 'icon' ] ?? '' |
426 | ), |
427 | 'data-lang-dropdown' => $langData ? new VectorComponentLanguageDropdown( |
428 | $ulsLabels['label'], |
429 | $ulsLabels['aria-label'], |
430 | $langButtonClass, |
431 | count( $this->getLanguagesCached() ), |
432 | $langData['html-items'] ?? '', |
433 | $langData['html-before-portal'] ?? '', |
434 | $langData['html-after-portal'] ?? '', |
435 | $title |
436 | ) : null, |
437 | 'data-search-box' => new VectorComponentSearchBox( |
438 | $parentData['data-search-box'], |
439 | true, |
440 | // is primary mode of search |
441 | true, |
442 | 'searchform', |
443 | true, |
444 | $config, |
445 | Constants::SEARCH_BOX_INPUT_LOCATION_MOVED, |
446 | $localizer |
447 | ), |
448 | 'data-main-menu' => new VectorComponentMainMenu( |
449 | $sidebar, |
450 | $portlets['data-languages'] ?? [], |
451 | $localizer, |
452 | $user, |
453 | $featureManager, |
454 | $this, |
455 | ), |
456 | 'data-main-menu-dropdown' => new VectorComponentDropdown( |
457 | VectorComponentMainMenu::ID . '-dropdown', |
458 | $this->msg( VectorComponentMainMenu::ID . '-label' )->text(), |
459 | VectorComponentMainMenu::ID . '-dropdown' . ' vector-button-flush-left vector-button-flush-right', |
460 | 'menu' |
461 | ), |
462 | 'data-page-tools' => new VectorComponentPageTools( |
463 | array_merge( [ $portlets['data-actions'] ?? [] ], $pageToolsMenu ), |
464 | $localizer, |
465 | $featureManager |
466 | ), |
467 | 'data-page-tools-dropdown' => new VectorComponentDropdown( |
468 | VectorComponentPageTools::ID . '-dropdown', |
469 | $this->msg( 'toolbox' )->text(), |
470 | VectorComponentPageTools::ID . '-dropdown', |
471 | ), |
472 | 'data-appearance' => $isAppearanceEnabled ? |
473 | new VectorComponentAppearance( $localizer, $featureManager ) : null, |
474 | 'data-appearance-dropdown' => $isAppearanceEnabled ? new VectorComponentDropdown( |
475 | 'vector-appearance-dropdown', |
476 | $this->msg( 'vector-appearance-label' )->text(), |
477 | '', |
478 | // @todo: Use new theme icon (T351142) |
479 | 'appearance' |
480 | ) : null, |
481 | 'data-vector-sticky-header' => $featureManager->isFeatureEnabled( |
482 | Constants::FEATURE_STICKY_HEADER |
483 | ) ? new VectorComponentStickyHeader( |
484 | $localizer, |
485 | new VectorComponentSearchBox( |
486 | $parentData['data-search-box'], |
487 | // Collapse inside search box is disabled. |
488 | false, |
489 | false, |
490 | 'vector-sticky-search-form', |
491 | false, |
492 | $config, |
493 | Constants::SEARCH_BOX_INPUT_LOCATION_MOVED, |
494 | $localizer |
495 | ), |
496 | // Show sticky ULS if the ULS extension is enabled and the ULS in header is not hidden |
497 | $this->isULSExtensionEnabled() && !$this->shouldHideLanguages() ? |
498 | new VectorComponentButton( |
499 | $ulsLabels[ 'label' ], |
500 | 'wikimedia-language', |
501 | 'p-lang-btn-sticky-header', |
502 | 'mw-interlanguage-selector', |
503 | [ |
504 | 'tabindex' => '-1', |
505 | 'data-event-name' => 'ui.dropdown-p-lang-btn-sticky-header' |
506 | ], |
507 | 'quiet', |
508 | ) : null, |
509 | $this->isVisualEditorTabPositionFirst( $portlets[ 'data-views' ] ) |
510 | ) : null, |
511 | 'data-vector-settings-button' => $featureManager->isFeatureEnabled( |
512 | Constants::FEATURE_APPEARANCE, |
513 | ) ? null : new VectorComponentButton( |
514 | $this->msg( 'vector-limited-width-toggle' ), |
515 | $featureManager->isFeatureEnabled( |
516 | Constants::FEATURE_LIMITED_WIDTH |
517 | ) ? 'fullScreen' : 'exitFullscreen', |
518 | '', |
519 | 'vector-limited-width-toggle', |
520 | [], |
521 | 'normal', |
522 | 'default', |
523 | true |
524 | ) |
525 | ]; |
526 | |
527 | foreach ( $components as $key => $component ) { |
528 | // Array of components or null values. |
529 | if ( $component ) { |
530 | $parentData[$key] = $component->getTemplateData(); |
531 | } |
532 | } |
533 | |
534 | return array_merge( $parentData, [ |
535 | 'is-language-in-content' => $this->isLanguagesInContent(), |
536 | 'has-buttons-in-content-top' => $this->isLanguagesInContentAt( 'top' ) || $hasAddTopicButton, |
537 | 'is-language-in-content-bottom' => $this->isLanguagesInContentAt( 'bottom' ), |
538 | // Cast empty string to null |
539 | 'html-subtitle' => $parentData['html-subtitle'] === '' ? null : $parentData['html-subtitle'], |
540 | ] ); |
541 | } |
542 | } |