isLanguagesInContent() || !$this->isULSExtensionEnabled(); } /** * Determines if the language switching alert box should be in the sidebar. * * @return bool */ private function shouldLanguageAlertBeInSidebar(): bool { $featureManager = VectorServices::getFeatureManager(); $isMainPage = $this->getTitle() ? $this->getTitle()->isMainPage() : false; $shouldShowOnMainPage = $isMainPage && !empty( $this->getLanguagesCached() ) && $featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER ); return ( $this->isLanguagesInContentAt( 'top' ) && !$isMainPage && !$this->shouldHideLanguages() && $featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_ALERT_IN_SIDEBAR ) ) || $shouldShowOnMainPage; } /** * @return array */ private function getTocPageTitleData(): array { return Hooks::updateDropdownMenuData( [ 'id' => 'vector-page-titlebar-toc', 'class' => 'vector-page-titlebar-toc mw-ui-icon-flush-left', 'is-pinned' => true, 'button' => true, 'text-hidden' => true, 'icon' => 'listBullet' ] ); } /** * Merges the `view-overflow` menu into the `action` menu. * This ensures that the previous state of the menu e.g. emptyPortlet class * is preserved. * @param array $data * @return array */ private function mergeViewOverflowIntoActions( $data ) { $portlets = $data['data-portlets']; $actions = $portlets['data-actions']; $overflow = $portlets['data-views-overflow']; // if the views overflow menu is not empty, then signal that the more menu despite // being initially empty now has collapsible items. if ( !$overflow['is-empty'] ) { $data['data-portlets']['data-actions']['class'] .= ' vector-has-collapsible-items'; } $data['data-portlets']['data-actions']['html-items'] = $overflow['html-items'] . $actions['html-items']; return $data; } /** * @inheritDoc */ public function getHtmlElementAttributes() { $original = parent::getHtmlElementAttributes(); if ( VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER ) ) { // T290518: Add scroll padding to root element when the sticky header is // enabled. This class needs to be server rendered instead of added from // JS in order to correctly handle situations where the sticky header // isn't visible yet but we still need scroll padding applied (e.g. when // the user navigates to a page with a hash fragment in the URI). For this // reason, we can't rely on the `vector-sticky-header-visible` class as it // is added too late. // // Please note that this class applies scroll padding which does not work // when applied to the body tag in Chrome, Safari, and Firefox (and // possibly others). It must instead be applied to the html tag. $original['class'] = implode( ' ', [ $original['class'] ?? '', self::STICKY_HEADER_ENABLED_CLASS ] ); } return $original; } /** * Determines wheather the initial state of sidebar is visible on not * * @return bool */ private function isMainMenuVisible() { $skin = $this->getSkin(); if ( $skin->getUser()->isRegistered() ) { $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); $userPrefSidebarState = $userOptionsLookup->getOption( $skin->getUser(), Constants::PREF_KEY_SIDEBAR_VISIBLE ); $defaultLoggedinSidebarState = $this->getConfig()->get( Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_AUTHORISED_USER ); // If the sidebar user preference has been set, return that value, // if not, then the default sidebar state for logged-in users. return ( $userPrefSidebarState !== null ) ? (bool)$userPrefSidebarState : $defaultLoggedinSidebarState; } return $this->getConfig()->get( Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_ANONYMOUS_USER ); } /** * @return array */ public function getTemplateData(): array { $featureManager = VectorServices::getFeatureManager(); $parentData = parent::getTemplateData(); $stickyHeader = new VectorComponentStickyHeader(); $parentData = $this->mergeViewOverflowIntoActions( $parentData ); // FIXME: Move to component (T322089) $parentData['data-vector-user-links'] = $this->getUserLinksTemplateData( $parentData['data-portlets']['data-user-menu'], $parentData['data-portlets'][ 'data-vector-user-menu-overflow' ], $this->getUser() ); $parentData['data-portlets']['data-variants'] = $this->updateVariantsMenuLabel( $parentData['data-portlets']['data-variants'] ); $langData = $parentData['data-portlets']['data-languages'] ?? null; if ( $langData ) { $parentData['data-lang-btn'] = $this->getULSPortletData( $langData, count( $this->getLanguagesCached() ), $this->isLanguagesInContentAt( 'top' ) ); } $toc = new VectorComponentTableOfContents( $parentData['data-toc'], $this->getContext(), $this->getConfig() ); $config = $this->getConfig(); $searchBox = new VectorComponentSearchBox( $parentData['data-search-box'], true, // is primary mode of search true, 'searchform', true, $config, Constants::SEARCH_BOX_INPUT_LOCATION_MOVED, $this->getContext() ); $isPageToolsEnabled = $featureManager->isFeatureEnabled( Constants::FEATURE_PAGE_TOOLS ); $sidebar = $parentData[ 'data-portlets-sidebar' ]; $pageToolsMenus = []; $restPortlets = $parentData[ 'data-portlets-sidebar' ][ 'array-portlets-rest' ]; if ( $isPageToolsEnabled ) { $toolboxMenuIndex = array_search( VectorComponentPageTools::TOOLBOX_ID, array_column( $restPortlets, 'id' ) ); if ( $toolboxMenuIndex !== false ) { // Splice removes the toolbox menu from the $restPortlets array // and current returns the first value of array_splice, i.e. the $toolbox menu data. $pageToolsMenus = array_splice( $restPortlets, $toolboxMenuIndex ); $parentData[ 'data-portlets-sidebar' ]['array-portlets-rest'] = $restPortlets; $sidebar = $parentData[ 'data-portlets-sidebar' ]; } } $mainMenu = new VectorComponentMainMenu( $sidebar, $this->shouldLanguageAlertBeInSidebar(), $parentData['data-portlets']['data-languages'] ?? [], $this->getContext(), $this->getUser(), VectorServices::getFeatureManager(), $this, ); $mainMenuDropdown = new VectorComponentDropdown( $mainMenu::ID . '-dropdown', $this->msg( $mainMenu::ID . '-label' )->text(), $mainMenu::ID . '-dropdown' . ' mw-ui-icon-flush-left mw-ui-icon-flush-right', 'menu' ); $pageTools = new VectorComponentPageTools( array_merge( [ $parentData['data-portlets']['data-actions'] ?? [] ], $pageToolsMenus ), $this->getContext(), $this->getUser(), $featureManager ); $pageToolsDropdown = new VectorComponentDropdown( $pageTools::ID . '-dropdown', $this->msg( 'toolbox' )->text(), $pageTools::ID . '-dropdown', ); $components = [ 'data-toc' => $toc, 'data-search-box' => $searchBox, 'data-main-menu' => $mainMenu, 'data-main-menu-dropdown' => $mainMenuDropdown, 'data-page-tools' => $isPageToolsEnabled ? $pageTools : null, 'data-page-tools-dropdown' => $isPageToolsEnabled ? $pageToolsDropdown : null, ]; foreach ( $components as $key => $component ) { // Array of components or null values. if ( $component ) { $parentData[$key] = $component->getTemplateData(); } } $searchStickyHeader = new VectorComponentSearchBox( $parentData['data-search-box'], // Collapse inside search box is disabled. false, false, 'vector-sticky-search-form', false, $config, Constants::SEARCH_BOX_INPUT_LOCATION_MOVED, $this->getContext() ); return array_merge( $parentData, [ 'is-language-in-content' => $this->isLanguagesInContent(), 'is-language-in-content-top' => $this->isLanguagesInContentAt( 'top' ), 'is-language-in-content-bottom' => $this->isLanguagesInContentAt( 'bottom' ), 'is-main-menu-visible' => $this->isMainMenuVisible(), // Cast empty string to null 'html-subtitle' => $parentData['html-subtitle'] === '' ? null : $parentData['html-subtitle'], 'data-page-titlebar-toc' => $this->getTocPageTitleData(), 'data-vector-sticky-header' => $featureManager->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER ) ? $stickyHeader->getTemplateData() + $this->getStickyHeaderData( $searchStickyHeader->getTemplateData(), $featureManager->isFeatureEnabled( Constants::FEATURE_STICKY_HEADER_EDIT ) ) : false, 'is-page-tools-enabled' => $isPageToolsEnabled ] ); } }