getService( Constants::SERVICE_FEATURE_MANAGER ) ->isFeatureEnabled( Constants::FEATURE_LATEST_SKIN ); return !$isLatestSkinFeatureEnabled; } /** * Overrides template, styles and scripts module when skin operates * in legacy mode. * * @inheritDoc * @param array|null $options Note; this param is only optional for internal purpose. * Do not instantiate Vector, use SkinFactory to create the object instead. * If you absolutely must to, this paramater is required; you have to provide the * skinname with the `name` key. That's do it with `new SkinVector( ['name' => 'vector'] )`. * Failure to do that, will lead to fatal exception. */ public function __construct( $options = [] ) { if ( $this->isLegacy() ) { $options['scripts'] = [ 'skins.vector.legacy.js' ]; $options['styles'] = [ 'skins.vector.styles.legacy' ]; $options['template'] = 'skin-legacy'; } $options['templateDirectory'] = __DIR__ . '/templates'; parent::__construct( $options ); } /** * @inheritDoc */ public function getTemplateData() : array { $contentNavigation = $this->buildContentNavigationUrls(); $skin = $this; $out = $skin->getOutput(); $title = $out->getTitle(); $featureManager = VectorServices::getFeatureManager(); // Naming conventions for Mustache parameters. // // Value type (first segment): // - Prefix "is" or "has" for boolean values. // - Prefix "msg-" for interface message text. // - Prefix "html-" for raw HTML. // - Prefix "data-" for an array of template parameters that should be passed directly // to a template partial. // - Prefix "array-" for lists of any values. // // Source of value (first or second segment) // - Segment "page-" for data relating to the current page (e.g. Title, WikiPage, or OutputPage). // - Segment "hook-" for any thing generated from a hook. // It should be followed by the name of the hook in hyphenated lowercase. // // Conditionally used values must use null to indicate absence (not false or ''). $parentData = parent::getTemplateData(); $commonSkinData = array_merge( $parentData, [ 'page-langcode' => $title->getPageViewLanguage()->getHtmlCode(), 'page-isarticle' => (bool)$out->isArticle(), // Remember that the string '0' is a valid title. // From OutputPage::getPageTitle, via ::setPageTitle(). 'html-title' => $out->getPageTitle(), 'html-categories' => $skin->getCategories(), 'input-location' => $this->getSearchBoxInputLocation(), 'data-sidebar' => $this->getTemplateDataSidebar(), 'sidebar-visible' => $this->isSidebarVisible(), ], $this->getMenuProps() ); if ( $skin->getUser()->isLoggedIn() ) { // Note: This data is also passed to legacy template where it is unused. $commonSkinData['data-emphasized-sidebar-action'] = [ 'href' => SpecialPage::getTitleFor( 'Preferences', false, 'mw-prefsection-rendering-skin-skin-prefs' )->getLinkURL( 'wprov=' . self::OPT_OUT_LINK_TRACKING_CODE ), ]; } return $commonSkinData; } /** * Gets the value of the "input-location" parameter for the SearchBox Mustache template. * * @return string Either `Constants::SEARCH_BOX_INPUT_LOCATION_DEFAULT` or * `Constants::SEARCH_BOX_INPUT_LOCATION_MOVED` */ private function getSearchBoxInputLocation() : string { if ( $this->isLegacy() ) { return Constants::SEARCH_BOX_INPUT_LOCATION_DEFAULT; } return Constants::SEARCH_BOX_INPUT_LOCATION_MOVED; } /** * Determines wheather the initial state of sidebar is visible on not * * @return bool */ private function isSidebarVisible() { $skin = $this->getSkin(); if ( $skin->getUser()->isLoggedIn() ) { $userPrefSidebarState = $skin->getUser()->getOption( 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 ); } /** * Render a series of portals * * @return array */ private function getTemplateDataSidebar() { $skin = $this; $portals = $this->buildSidebar(); $props = []; $languages = null; // Render portals foreach ( $portals as $name => $content ) { if ( $content === false ) { continue; } // Numeric strings gets an integer when set as key, cast back - T73639 $name = (string)$name; switch ( $name ) { case 'SEARCH': break; case 'TOOLBOX': $props[] = $this->getMenuData( 'tb', $content, self::MENU_TYPE_PORTAL ); break; case 'LANGUAGES': $portal = $this->getMenuData( 'lang', $content, self::MENU_TYPE_PORTAL ); // The language portal will be added provided either // languages exist or there is a value in html-after-portal // for example to show the add language wikidata link (T252800) if ( count( $content ) || $portal['html-after-portal'] ) { $languages = $portal; } break; default: $props[] = $this->getMenuData( $name, $content, self::MENU_TYPE_PORTAL ); break; } } $firstPortal = $props[0] ?? null; if ( $firstPortal ) { $firstPortal[ 'class' ] .= ' portal-first'; } return [ 'html-logo-attributes' => Xml::expandAttributes( Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) + [ 'class' => 'mw-wiki-logo', 'href' => Skin::makeMainPageUrl(), ] ), 'array-portals-rest' => array_slice( $props, 1 ), 'data-portals-first' => $firstPortal, 'data-portals-languages' => $languages, ]; } /** * @param string $label to be used to derive the id and human readable label of the menu * Note certain keys are special cased for historic reasons in core. * @param array $urls to convert to list items stored as string in html-items key * @param int $type of menu (optional) - a plain list (MENU_TYPE_DEFAULT), * a tab (MENU_TYPE_TABS) or a dropdown (MENU_TYPE_DROPDOWN) * @param bool $setLabelToSelected (optional) the menu label will take the value of the * selected item if found. * @return array */ private function getMenuData( string $label, array $urls = [], int $type = self::MENU_TYPE_DEFAULT, bool $setLabelToSelected = false ) : array { $portletData = $this->getPortletData( $label, $urls ); $extraClasses = [ self::MENU_TYPE_DROPDOWN => 'vector-menu vector-menu-dropdown', self::MENU_TYPE_TABS => 'vector-menu vector-menu-tabs', self::MENU_TYPE_PORTAL => 'vector-menu vector-menu-portal portal', self::MENU_TYPE_DEFAULT => 'vector-menu', ]; $isPortal = $type === self::MENU_TYPE_PORTAL; $props = $portletData + [ 'label-id' => "p-{$label}-label", 'is-dropdown' => $type === self::MENU_TYPE_DROPDOWN, ]; // Special casing for Variant to change label to selected. // Hopefully we can revisit and possibly remove this code when the language switcher is moved. foreach ( $urls as $key => $item ) { if ( $setLabelToSelected ) { if ( isset( $item['class'] ) && stripos( $item['class'], 'selected' ) !== false ) { $props['label'] = $item['text']; } } } // Mark the portal as empty if it has no content $class = $props['class']; $props['class'] = trim( "$class $extraClasses[$type]" ); return $props; } /** * @return array */ private function getMenuProps() : array { $contentNavigation = $this->buildContentNavigationUrls(); $personalTools = self::getPersonalToolsForMakeListItem( $this->buildPersonalUrls() ); $ptools = $this->getMenuData( 'personal', $personalTools ); return [ 'data-personal-menu' => $ptools, 'data-namespace-tabs' => $this->getMenuData( 'namespaces', $contentNavigation[ 'namespaces' ] ?? [], self::MENU_TYPE_TABS ), 'data-variants' => $this->getMenuData( 'variants', $contentNavigation[ 'variants' ] ?? [], self::MENU_TYPE_DROPDOWN, true ), 'data-page-actions' => $this->getMenuData( 'views', $contentNavigation[ 'views' ] ?? [], self::MENU_TYPE_TABS ), 'data-page-actions-more' => $this->getMenuData( 'cactions', $contentNavigation[ 'actions' ] ?? [], self::MENU_TYPE_DROPDOWN ), ]; } }