2021-09-09 22:13:48 +00:00
|
|
|
<?php
|
2022-02-19 00:53:42 +00:00
|
|
|
|
2022-05-23 07:32:40 +00:00
|
|
|
namespace MediaWiki\Skins\Vector;
|
2022-02-19 00:53:42 +00:00
|
|
|
|
2022-10-27 16:09:12 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2022-12-07 01:05:22 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentDropdown;
|
2023-01-06 01:45:38 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentIconLink;
|
2022-12-16 18:03:07 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentLanguageDropdown;
|
2022-10-27 23:52:23 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentMainMenu;
|
2023-01-06 01:45:38 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentMenu;
|
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentMenuListItem;
|
2023-01-07 01:08:39 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentMenuVariants;
|
2022-12-02 18:09:52 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentPageTools;
|
2022-10-27 16:11:32 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentSearchBox;
|
2022-12-07 00:42:31 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentStickyHeader;
|
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentTableOfContents;
|
2023-01-06 01:45:38 +00:00
|
|
|
use MediaWiki\Skins\Vector\Components\VectorComponentUserLinks;
|
2022-10-27 16:09:12 +00:00
|
|
|
|
2021-09-09 22:13:48 +00:00
|
|
|
/**
|
|
|
|
* @ingroup Skins
|
|
|
|
* @package Vector
|
|
|
|
* @internal
|
|
|
|
*/
|
|
|
|
class SkinVector22 extends SkinVector {
|
2022-07-11 15:10:26 +00:00
|
|
|
private const STICKY_HEADER_ENABLED_CLASS = 'vector-sticky-header-enabled';
|
2022-03-17 23:02:39 +00:00
|
|
|
|
2022-10-27 21:18:53 +00:00
|
|
|
/**
|
|
|
|
* Show the ULS button if it's modern Vector, languages in header is enabled,
|
|
|
|
* and the ULS extension is enabled. Hide it otherwise.
|
|
|
|
* There is no point in showing the language button if ULS extension is unavailable
|
|
|
|
* as there is no ways to add languages without it.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function shouldHideLanguages(): bool {
|
|
|
|
return !$this->isLanguagesInContent() || !$this->isULSExtensionEnabled();
|
|
|
|
}
|
|
|
|
|
2022-10-27 19:36:08 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2022-11-21 22:16:07 +00:00
|
|
|
/**
|
|
|
|
* @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'
|
|
|
|
] );
|
|
|
|
}
|
|
|
|
|
2022-04-27 19:32:19 +00:00
|
|
|
/**
|
|
|
|
* 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'];
|
2022-04-29 18:16:08 +00:00
|
|
|
// 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';
|
|
|
|
}
|
2022-04-27 19:32:19 +00:00
|
|
|
$data['data-portlets']['data-actions']['html-items'] = $overflow['html-items'] . $actions['html-items'];
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2022-07-11 15:10:26 +00:00
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
|
|
|
|
2022-10-27 16:09:12 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-05 20:02:40 +00:00
|
|
|
/**
|
|
|
|
* Pulls the page tools menu out of $sidebar into $pageToolsMenu
|
|
|
|
*
|
|
|
|
* @param array &$sidebar
|
|
|
|
* @param array &$pageToolsMenu
|
|
|
|
*/
|
|
|
|
private static function extractPageToolsFromSidebar( &$sidebar, &$pageToolsMenu ) {
|
|
|
|
$restPortlets = $sidebar[ 'array-portlets-rest' ] ?? [];
|
|
|
|
$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.
|
|
|
|
$pageToolsMenu = array_splice( $restPortlets, $toolboxMenuIndex );
|
|
|
|
$sidebar['array-portlets-rest'] = $restPortlets;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-19 00:53:42 +00:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getTemplateData(): array {
|
2022-04-01 15:53:03 +00:00
|
|
|
$featureManager = VectorServices::getFeatureManager();
|
|
|
|
$parentData = parent::getTemplateData();
|
2022-12-07 00:42:31 +00:00
|
|
|
$stickyHeader = new VectorComponentStickyHeader();
|
2022-04-27 19:32:19 +00:00
|
|
|
$parentData = $this->mergeViewOverflowIntoActions( $parentData );
|
2023-01-06 01:45:38 +00:00
|
|
|
$portlets = $parentData['data-portlets'];
|
2022-04-27 19:32:19 +00:00
|
|
|
|
2022-10-27 23:46:19 +00:00
|
|
|
$langData = $parentData['data-portlets']['data-languages'] ?? null;
|
2022-12-13 20:28:22 +00:00
|
|
|
$config = $this->getConfig();
|
|
|
|
|
2022-11-08 19:55:23 +00:00
|
|
|
$isPageToolsEnabled = $featureManager->isFeatureEnabled( Constants::FEATURE_PAGE_TOOLS );
|
2022-12-02 18:09:52 +00:00
|
|
|
$sidebar = $parentData[ 'data-portlets-sidebar' ];
|
2023-01-05 20:02:40 +00:00
|
|
|
$pageToolsMenu = [];
|
2022-11-08 19:55:23 +00:00
|
|
|
if ( $isPageToolsEnabled ) {
|
2023-01-05 20:02:40 +00:00
|
|
|
self::extractPageToolsFromSidebar( $sidebar, $pageToolsMenu );
|
2022-12-02 18:09:52 +00:00
|
|
|
}
|
2022-12-13 20:28:22 +00:00
|
|
|
|
2022-12-16 18:03:07 +00:00
|
|
|
$langButtonClass = $langData['class'] ?? '';
|
|
|
|
$ulsLabels = $this->getULSLabels();
|
2023-01-06 01:45:38 +00:00
|
|
|
$returnto = $this->getReturnToParam();
|
|
|
|
$user = $this->getUser();
|
|
|
|
$logoutData = $this->buildLogoutLinkData();
|
|
|
|
$loginLinkData = $this->buildLoginData( $returnto, $this->useCombinedLoginLink() );
|
|
|
|
$createAccountData = $this->buildCreateAccountData( $returnto );
|
2022-10-27 19:36:08 +00:00
|
|
|
$components = [
|
2023-01-07 01:08:39 +00:00
|
|
|
'data-vector-variants' => new VectorComponentMenuVariants(
|
|
|
|
$parentData['data-portlets']['data-variants'],
|
|
|
|
$this->getTitle()->getPageLanguage(),
|
|
|
|
$this->msg( 'vector-language-variant-switcher-label' )
|
|
|
|
),
|
2023-01-06 01:45:38 +00:00
|
|
|
'data-vector-user-links' => new VectorComponentUserLinks(
|
|
|
|
$this->getContext(),
|
|
|
|
$user,
|
|
|
|
new VectorComponentMenu(
|
|
|
|
$portlets['data-user-menu']
|
|
|
|
),
|
|
|
|
new VectorComponentMenu( [
|
|
|
|
// this menu should have no label
|
|
|
|
'label' => '',
|
|
|
|
] + $portlets[ 'data-vector-user-menu-overflow' ] ),
|
|
|
|
new VectorComponentMenu(
|
|
|
|
[],
|
|
|
|
$user->isRegistered() ? [
|
|
|
|
new VectorComponentMenuListItem(
|
|
|
|
new VectorComponentIconLink(
|
|
|
|
$logoutData[ 'href'],
|
|
|
|
$logoutData[ 'text' ],
|
|
|
|
$logoutData[ 'icon' ],
|
|
|
|
$this,
|
|
|
|
'pt-logout'
|
|
|
|
),
|
|
|
|
'vector-user-menu-logout',
|
|
|
|
'pt-logout'
|
|
|
|
)
|
|
|
|
] : [
|
|
|
|
new VectorComponentMenuListItem(
|
|
|
|
new VectorComponentIconLink(
|
|
|
|
$createAccountData['href'] ?? null,
|
|
|
|
$createAccountData['text'] ?? null,
|
|
|
|
$createAccountData['icon'] ?? null,
|
|
|
|
$this,
|
|
|
|
'create-account'
|
|
|
|
),
|
|
|
|
'vector-user-menu-create-account user-links-collapsible-item'
|
|
|
|
),
|
|
|
|
new VectorComponentMenuListItem(
|
|
|
|
new VectorComponentIconLink(
|
|
|
|
$loginLinkData['href'] ?? null,
|
|
|
|
$loginLinkData['text'] ?? null,
|
|
|
|
$loginLinkData['icon'] ?? null,
|
|
|
|
$this,
|
|
|
|
'login'
|
|
|
|
),
|
|
|
|
'vector-user-menu-login'
|
|
|
|
)
|
|
|
|
]
|
|
|
|
),
|
|
|
|
),
|
2022-12-16 18:03:07 +00:00
|
|
|
'data-lang-btn' => $langData ? new VectorComponentLanguageDropdown(
|
|
|
|
$ulsLabels['label'],
|
|
|
|
$ulsLabels['aria-label'],
|
|
|
|
$this->isLanguagesInContentAt( 'top' ) ?
|
|
|
|
$langButtonClass . ' mw-ui-icon-flush-right' : $langButtonClass,
|
|
|
|
count( $this->getLanguagesCached() ),
|
|
|
|
$langData['html-items'] ?? '',
|
|
|
|
$langData['html-before-portal'] ?? '',
|
2022-12-19 15:57:48 +00:00
|
|
|
$langData['html-after-portal'] ?? '',
|
|
|
|
$this->getTitle()
|
2022-12-16 18:03:07 +00:00
|
|
|
) : null,
|
2023-01-05 20:02:40 +00:00
|
|
|
'data-toc' => new VectorComponentTableOfContents(
|
|
|
|
$parentData['data-toc'],
|
|
|
|
$this->getContext(),
|
|
|
|
$this->getConfig()
|
|
|
|
),
|
|
|
|
'data-search-box' => new VectorComponentSearchBox(
|
|
|
|
$parentData['data-search-box'],
|
|
|
|
true,
|
|
|
|
// is primary mode of search
|
|
|
|
true,
|
|
|
|
'searchform',
|
|
|
|
true,
|
|
|
|
$config,
|
|
|
|
Constants::SEARCH_BOX_INPUT_LOCATION_MOVED,
|
|
|
|
$this->getContext()
|
|
|
|
),
|
|
|
|
'data-main-menu' => new VectorComponentMainMenu(
|
|
|
|
$sidebar,
|
|
|
|
$this->shouldLanguageAlertBeInSidebar(),
|
|
|
|
$parentData['data-portlets']['data-languages'] ?? [],
|
|
|
|
$this->getContext(),
|
|
|
|
$this->getUser(),
|
|
|
|
VectorServices::getFeatureManager(),
|
|
|
|
$this,
|
|
|
|
),
|
|
|
|
'data-main-menu-dropdown' => new VectorComponentDropdown(
|
|
|
|
VectorComponentMainMenu::ID . '-dropdown',
|
|
|
|
$this->msg( VectorComponentMainMenu::ID . '-label' )->text(),
|
|
|
|
VectorComponentMainMenu::ID . '-dropdown' . ' mw-ui-icon-flush-left mw-ui-icon-flush-right',
|
|
|
|
'menu'
|
|
|
|
),
|
|
|
|
'data-page-tools' => $isPageToolsEnabled ? new VectorComponentPageTools(
|
|
|
|
array_merge( [ $parentData['data-portlets']['data-actions'] ?? [] ], $pageToolsMenu ),
|
|
|
|
$this->getContext(),
|
|
|
|
$this->getUser(),
|
|
|
|
$featureManager
|
|
|
|
) : null,
|
|
|
|
'data-page-tools-dropdown' => $isPageToolsEnabled ? new VectorComponentDropdown(
|
|
|
|
VectorComponentPageTools::ID . '-dropdown',
|
|
|
|
$this->msg( 'toolbox' )->text(),
|
|
|
|
VectorComponentPageTools::ID . '-dropdown',
|
|
|
|
) : null,
|
2022-10-27 19:36:08 +00:00
|
|
|
];
|
|
|
|
foreach ( $components as $key => $component ) {
|
|
|
|
// Array of components or null values.
|
|
|
|
if ( $component ) {
|
|
|
|
$parentData[$key] = $component->getTemplateData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 16:11:32 +00:00
|
|
|
$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()
|
|
|
|
);
|
|
|
|
|
2022-07-25 20:13:58 +00:00
|
|
|
return array_merge( $parentData, [
|
2022-10-27 21:18:53 +00:00
|
|
|
'is-language-in-content' => $this->isLanguagesInContent(),
|
|
|
|
'is-language-in-content-top' => $this->isLanguagesInContentAt( 'top' ),
|
|
|
|
'is-language-in-content-bottom' => $this->isLanguagesInContentAt( 'bottom' ),
|
2022-10-27 16:09:12 +00:00
|
|
|
'is-main-menu-visible' => $this->isMainMenuVisible(),
|
2022-07-25 20:13:58 +00:00
|
|
|
// Cast empty string to null
|
|
|
|
'html-subtitle' => $parentData['html-subtitle'] === '' ? null : $parentData['html-subtitle'],
|
2022-11-21 22:16:07 +00:00
|
|
|
'data-page-titlebar-toc' => $this->getTocPageTitleData(),
|
2022-04-01 15:53:03 +00:00
|
|
|
'data-vector-sticky-header' => $featureManager->isFeatureEnabled(
|
|
|
|
Constants::FEATURE_STICKY_HEADER
|
2022-12-07 00:42:31 +00:00
|
|
|
) ? $stickyHeader->getTemplateData() + $this->getStickyHeaderData(
|
2022-10-27 16:11:32 +00:00
|
|
|
$searchStickyHeader->getTemplateData(),
|
2022-04-01 15:53:03 +00:00
|
|
|
$featureManager->isFeatureEnabled(
|
|
|
|
Constants::FEATURE_STICKY_HEADER_EDIT
|
|
|
|
)
|
|
|
|
) : false,
|
2022-11-08 19:55:23 +00:00
|
|
|
'is-page-tools-enabled' => $isPageToolsEnabled
|
2022-07-25 20:13:58 +00:00
|
|
|
] );
|
2022-02-19 00:53:42 +00:00
|
|
|
}
|
2021-09-09 22:13:48 +00:00
|
|
|
}
|