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