'#', 'id' => 'ca-talk-sticky-header', 'event' => 'talk-sticky-header', 'icon' => 'wikimedia-speechBubbles', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; private const SUBJECT_ICON = [ 'href' => '#', 'id' => 'ca-subject-sticky-header', 'event' => 'subject-sticky-header', 'icon' => 'wikimedia-article', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; private const HISTORY_ICON = [ 'href' => '#', 'id' => 'ca-history-sticky-header', 'event' => 'history-sticky-header', 'icon' => 'wikimedia-history', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; // Event and icon will be updated depending on watchstar state private const WATCHSTAR_ICON = [ 'href' => '#', 'id' => 'ca-watchstar-sticky-header', 'event' => 'watch-sticky-header', 'icon' => 'wikimedia-star', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon mw-watchlink' ]; private const EDIT_VE_ICON = [ 'href' => '#', 'id' => 'ca-ve-edit-sticky-header', 'event' => 've-edit-sticky-header', 'icon' => 'wikimedia-edit', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; private const EDIT_WIKITEXT_ICON = [ 'href' => '#', 'id' => 'ca-edit-sticky-header', 'event' => 'wikitext-edit-sticky-header', 'icon' => 'wikimedia-wikiText', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; private const EDIT_PROTECTED_ICON = [ 'href' => '#', 'id' => 'ca-viewsource-sticky-header', 'event' => 've-edit-protected-sticky-header', 'icon' => 'wikimedia-editLock', 'is-quiet' => true, 'tabindex' => '-1', 'class' => 'sticky-header-icon' ]; /** * Calls getLanguages with caching. * @return array */ protected function getLanguagesCached(): array { if ( $this->languages === null ) { $this->languages = $this->getLanguages(); } return $this->languages; } /** * This should be upstreamed to the Skin class in core once the logic is finalized. * Returns false if the page is a special page without any languages, or if an action * other than view is being used. * @return bool */ private function canHaveLanguages(): bool { if ( $this->getContext()->getActionName() !== 'view' ) { return false; } $title = $this->getTitle(); // Defensive programming - if a special page has added languages explicitly, best to show it. if ( $title && $title->isSpecialPage() && empty( $this->getLanguagesCached() ) ) { return false; } return true; } /** * @param string $location Either 'top' or 'bottom' is accepted. * @return bool */ protected function isLanguagesInContentAt( $location ) { if ( !$this->canHaveLanguages() ) { return false; } $featureManager = VectorServices::getFeatureManager(); $inContent = $featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_IN_HEADER ); $isMainPage = $this->getTitle() ? $this->getTitle()->isMainPage() : false; switch ( $location ) { case 'top': return $isMainPage ? $inContent && $featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER ) : $inContent; case 'bottom': return $inContent && $isMainPage && !$featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER ); default: throw new RuntimeException( 'unknown language button location' ); } } /** * Whether or not the languages are out of the sidebar and in the content either at * the top or the bottom. * @return bool */ final protected function isLanguagesInContent() { return $this->isLanguagesInContentAt( 'top' ) || $this->isLanguagesInContentAt( 'bottom' ); } /** * Whether languages should be hidden. * FIXME: Function should be removed as part of T319355 * * @return bool */ abstract protected function shouldHideLanguages(): bool; /** * Returns HTML for the create account link inside the anon user links * @param string[] $returnto array of query strings used to build the login link * @return string */ private function getCreateAccountHTML( $returnto ) { $createAccountData = $this->buildCreateAccountData( $returnto ); $createAccountData = array_merge( $createAccountData, [ 'class' => [ 'vector-menu-content-item', ], 'collapsible' => true, 'icon' => $createAccountData['icon'], 'button' => false ] ); $createAccountData = Hooks::updateLinkData( $createAccountData ); return $this->makeLink( 'create-account', $createAccountData ); } /** * Returns HTML for the create account button, login button and learn more link inside the anon user menu * @param string[] $returnto array of query strings used to build the login link * @param bool $useCombinedLoginLink if a combined login/signup link will be used * @param bool $isTempUser * @param bool $includeLearnMoreLink Pass `true` to include the learn more * link in the menu for anon users. This param will be inert for temp users. * @return array */ private function getAnonMenuBeforePortletData( $returnto, $useCombinedLoginLink, $isTempUser, $includeLearnMoreLink ): array { $templateParser = $this->getTemplateParser(); $loginLinkData = array_merge( $this->buildLoginData( $returnto, $useCombinedLoginLink ), [ 'class' => [ 'vector-menu-content-item', 'vector-menu-content-item-login' ], ] ); $loginLinkData = Hooks::updateLinkData( $loginLinkData ); $templateData = [ 'htmlCreateAccount' => $this->getCreateAccountHTML( $returnto ), 'htmlLogin' => $this->makeLink( 'login', $loginLinkData ), 'data-anon-editor' => [] ]; if ( !$isTempUser && $includeLearnMoreLink ) { $learnMoreLinkData = [ 'text' => $this->msg( 'vector-anon-user-menu-pages-learn' )->text(), 'href' => Title::newFromText( $this->msg( 'vector-intro-page' )->text() )->getLocalURL(), 'aria-label' => $this->msg( 'vector-anon-user-menu-pages-label' )->text(), ]; $templateData['data-anon-editor'] = [ 'htmlLearnMoreLink' => $this->makeLink( '', $learnMoreLinkData ), 'msgLearnMore' => $this->msg( 'vector-anon-user-menu-pages' ) ]; } return $templateData; } /** * Returns HTML for the logout button that should be placed in the user (personal) menu * after the menu itself. * @return string */ private function getLogoutHTML(): string { $logoutLinkData = array_merge( $this->buildLogoutLinkData(), [ 'class' => [ 'vector-menu-content-item', 'vector-menu-content-item-logout' ], ] ); return $this->makeLink( 'logout', Hooks::updateLinkData( $logoutLinkData ) ); } /** * Returns template data for UserLinks.mustache * FIXME: Move to VectorComponentUserLinks (T322089) * * @param array $userMenuData existing menu template data to be transformed and copied for UserLinks * @param array $overflowMenuData existing menu template data to be transformed and copied for UserLinks * @param User $user the context user * @return array */ final protected function getUserLinksTemplateData( array $userMenuData, array $overflowMenuData, User $user ): array { $isAnon = !$user->isRegistered(); $isTempUser = $user->isTemp(); $returnto = $this->getReturnToParam(); $useCombinedLoginLink = $this->useCombinedLoginLink(); $userMenuOverflowData = Hooks::updateDropdownMenuData( $overflowMenuData ); $userMenuDropdown = $this->getUserMenuDropdown( $userMenuData ); unset( $userMenuOverflowData[ 'label' ] ); if ( $isAnon || $isTempUser ) { $additionalData = $this->getAnonMenuBeforePortletData( $returnto, $useCombinedLoginLink, $isTempUser, // T317789: The `anontalk` and `anoncontribs` links will not be added to // the menu if `$wgGroupPermissions['*']['edit']` === false which can // leave the menu empty due to our removal of other user menu items in // `Hooks::updateUserLinksDropdownItems`. In this case, we do not want // to render the anon "learn more" link. !$userMenuData['is-empty'] ); } else { $additionalData = []; } $userLinks = new VectorComponentUserLinks(); $moreItems = substr_count( $userMenuOverflowData['html-items'], '