2014-08-07 11:38:34 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Vector - Modern version of MonoBook with fresh look and many usability
|
|
|
|
* improvements.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @ingroup Skins
|
|
|
|
*/
|
|
|
|
|
2022-02-06 22:43:56 +00:00
|
|
|
namespace Vector;
|
|
|
|
|
|
|
|
use Action;
|
|
|
|
use ExtensionRegistry;
|
|
|
|
use Html;
|
|
|
|
use Linker;
|
2020-04-28 21:16:21 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2022-02-06 22:43:56 +00:00
|
|
|
use RuntimeException;
|
|
|
|
use SkinMustache;
|
|
|
|
use SkinTemplate;
|
|
|
|
use SpecialPage;
|
|
|
|
use Title;
|
2020-07-07 08:59:14 +00:00
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
/**
|
2021-09-09 22:13:48 +00:00
|
|
|
* Skin subclass for Vector that may be the new or old version of Vector.
|
|
|
|
*
|
2014-08-07 11:38:34 +00:00
|
|
|
* @ingroup Skins
|
2020-07-09 16:42:33 +00:00
|
|
|
* Skins extending SkinVector are not supported
|
2021-09-09 22:13:48 +00:00
|
|
|
*
|
2020-08-04 00:17:53 +00:00
|
|
|
* @package Vector
|
|
|
|
* @internal
|
2021-09-09 22:13:48 +00:00
|
|
|
*
|
|
|
|
* # Migration Plan (please remove stages when done)
|
|
|
|
*
|
|
|
|
* Stage 1:
|
|
|
|
* In future when we are ready to transition to two separate skins in this order:
|
|
|
|
* - Use $wgSkipSkins to hide vector-2022.
|
|
|
|
* - Remove skippable field from the `vector-2022` skin version. This will defer the code to the
|
|
|
|
* configuration option wgSkipSkins
|
|
|
|
* - Set $wgVectorSkinMigrationMode = true and unset the Vector entry in wgSkipSkins
|
|
|
|
* - for one wiki, to trial run. This will expose Vector in preferences. The new Vector will show
|
|
|
|
* as Vector (2022) to begin with and the skin version preference will be hidden.
|
|
|
|
* - Check VectorPrefDiffInstrumentation instrumentation is still working.
|
|
|
|
*
|
|
|
|
* Stage 2:
|
|
|
|
* - Set $wgVectorSkinMigrationMode = true for all wikis and update skin preference labels
|
|
|
|
* (See Iebe60b560069c8cfcdeed3f5986b8be35501dcbc). This will hide the skin version
|
|
|
|
* preference, and update the skin preference instead.
|
|
|
|
* - We will set $wgDefaultSkin = 'vector-2022'; for desktop improvements wikis.
|
|
|
|
* - Run script that updates prefs table, migrating any rows where skin=vector AND
|
|
|
|
* skinversion = 2 to skin=vector22, skinversion=2
|
|
|
|
*
|
|
|
|
* Stage 3:
|
|
|
|
* - Move all modern code into SkinVector22.
|
|
|
|
* - Move legacy skin code from SkinVector to SkinVectorLegacy.
|
|
|
|
* - Update skin.json `vector` key to point to SkinVectorLegacy.
|
|
|
|
* - SkinVector left as alias if necessary.
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
2020-07-09 16:42:33 +00:00
|
|
|
class SkinVector extends SkinMustache {
|
2021-02-10 19:40:47 +00:00
|
|
|
/** @var null|array for caching purposes */
|
|
|
|
private $languages;
|
2020-04-07 22:55:08 +00:00
|
|
|
/** @var int */
|
2020-04-03 20:05:22 +00:00
|
|
|
private const MENU_TYPE_DEFAULT = 0;
|
2020-04-07 22:55:08 +00:00
|
|
|
/** @var int */
|
2020-04-03 20:05:22 +00:00
|
|
|
private const MENU_TYPE_TABS = 1;
|
2020-04-07 22:55:08 +00:00
|
|
|
/** @var int */
|
2020-04-03 20:05:22 +00:00
|
|
|
private const MENU_TYPE_DROPDOWN = 2;
|
2020-04-07 23:21:20 +00:00
|
|
|
private const MENU_TYPE_PORTAL = 3;
|
2021-08-30 22:44:00 +00:00
|
|
|
private const NO_ICON = [
|
|
|
|
'icon' => 'none',
|
2021-09-07 20:22:19 +00:00
|
|
|
'is-quiet' => true,
|
2021-09-21 16:01:33 +00:00
|
|
|
'tabindex' => '-1',
|
2021-08-30 22:44:00 +00:00
|
|
|
'class' => 'sticky-header-icon'
|
|
|
|
];
|
2021-09-14 16:53:35 +00:00
|
|
|
private const TALK_ICON = [
|
|
|
|
'href' => '#',
|
|
|
|
'id' => 'ca-talk-sticky-header',
|
|
|
|
'event' => 'talk-sticky-header',
|
|
|
|
'icon' => 'wikimedia-speechBubbles',
|
|
|
|
'is-quiet' => true,
|
2021-09-21 16:01:33 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-14 16:53:35 +00:00
|
|
|
'class' => 'sticky-header-icon'
|
|
|
|
];
|
|
|
|
private const HISTORY_ICON = [
|
|
|
|
'href' => '#',
|
|
|
|
'id' => 'ca-history-sticky-header',
|
|
|
|
'event' => 'history-sticky-header',
|
|
|
|
'icon' => 'wikimedia-history',
|
|
|
|
'is-quiet' => true,
|
2021-09-21 16:01:33 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-14 16:53:35 +00:00
|
|
|
'class' => 'sticky-header-icon'
|
|
|
|
];
|
2021-11-15 23:09:51 +00:00
|
|
|
// 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'
|
|
|
|
];
|
2021-09-24 21:00:32 +00:00
|
|
|
private const EDIT_VE_ICON = [
|
|
|
|
'href' => '#',
|
|
|
|
'id' => 'ca-ve-edit-sticky-header',
|
|
|
|
'event' => 've-edit-sticky-header',
|
|
|
|
'icon' => 'wikimedia-edit',
|
|
|
|
'is-quiet' => true,
|
2021-10-12 22:02:52 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-24 21:00:32 +00:00
|
|
|
'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,
|
2021-10-12 22:02:52 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-24 21:00:32 +00:00
|
|
|
'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,
|
2021-10-12 22:02:52 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-24 21:00:32 +00:00
|
|
|
'class' => 'sticky-header-icon'
|
|
|
|
];
|
2021-12-16 03:13:56 +00:00
|
|
|
private const SEARCH_SHOW_THUMBNAIL_CLASS = 'vector-search-box-show-thumbnail';
|
|
|
|
private const SEARCH_AUTO_EXPAND_WIDTH_CLASS = 'vector-search-box-auto-expand-width';
|
2021-09-28 22:43:15 +00:00
|
|
|
private const STICKY_HEADER_ENABLED_CLASS = 'vector-sticky-header-enabled';
|
2022-01-11 19:52:26 +00:00
|
|
|
private const TABLE_OF_CONTENTS_ENABLED_CLASS = 'vector-toc-enabled';
|
2021-12-15 22:25:11 +00:00
|
|
|
private const CLASS_QUIET_BUTTON = 'mw-ui-button mw-ui-quiet';
|
|
|
|
private const CLASS_PROGRESSIVE = 'mw-ui-progressive';
|
|
|
|
private const CLASS_ICON_BUTTON = 'mw-ui-icon mw-ui-icon-element';
|
|
|
|
private const CLASS_ICON_LABEL = 'mw-ui-icon mw-ui-icon-before';
|
2020-04-03 20:05:22 +00:00
|
|
|
|
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions
This commit is singularly focused on adding a link to the sidebar for
Vector, logged-in users. It does the bare minimum to fulfill the
requirements of T243281.
Additionally, it will help to answer the question "Do we need to use
abstractions (other than maybe different templates) to separate Legacy
Vector from Vector" by intentionally leaving out any abstractions in
order to make it easier to compare with a follow-up patch
(Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions.
It is a good thing to question whether or not we need addtional
abstractions in VectorTemplate and if they will help us as unnecessary
abstractions can have the opposite effect and just lead to further
frustrations down the road.
Therefore, I urge you, the reviewer, to let me know your thoughts! If
abstractions are viewed as not making our lives any easier, the
follow-up patches may be completely discarded and that's totally okay
with me. :) I think it's a good think to talk about now though.
Important changes:
* The VectorTemplate constructor was changed to allow injecting the
config, templateParser, and isLegacy boolean (only the config was
allowed before this commit). According to MediaWiki's Stable Interface
Policy, "Constructor signatures are generally considered unstable unless
explicitly declared stable for calling" [3]. Given that VecorTemplate's
constructor is not marked as stable, it is justified to do this without
warning according to the policy.
* Due to the above, the 'setTemplate' method is no longer needed and was
marked as deprecated.
* VectorTemplateTest was made to adapt to the new VectorTemplate
constructor. Additionally, it now extends from
MediaWikiIntegrationTestCase which my intelliphense server can pick up.
I *think* MediaWikiTestCase is just an alias to
MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed
to MediaWikiIntegrationTestCase in [2], but I'm willing to change it
back if there is pushback to this.
Open questions:
* What are VectorTemplate's responsibilities? To me, it acts right now
as a controller (because it echos the full HTML string from the
template), a model (because SkinTemplate::prepareQuickTemplate sets data
on it which it later retrieves through `$this->get()`), a presenter
(because it adds data tailored for a web-centric view), and a view
(because it renders HTML strings instead of letting the view/template be
solely responsible for that). Arguably, some business logic might be
mixed in there as well (because it checks to see if a User is logged
in/has necessary permissions to show x which my changes here add to).
This might not be a problem if we keep VectorTemplate relatively small,
but will it remain this way as we progress further in Desktop
Improvements?
* How do we write tests for VectorTemplate without exposing unnecessary
public methods? For example, if I want to test the `getSkinData()`
method to see what state will be sent to the template, how should I do
this? One option might be to use `TestingAccessWrapper` to expose these
private methods which is what
`VectorTemplateTest::testbuildViewsProps()` does. Another option is to
accept this method as public. Is there a better way? Keep in mind that
even with access to this method, there might be many things to mock.
[1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64
[2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76
[3] https://www.mediawiki.org/wiki/Stable_interface_policy
Bug: T243281
Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
|
|
|
/**
|
|
|
|
* T243281: Code used to track clicks to opt-out link.
|
|
|
|
*
|
|
|
|
* The "vct" substring is used to describe the newest "Vector" (non-legacy)
|
|
|
|
* feature. The "w" describes the web platform. The "1" describes the version
|
|
|
|
* of the feature.
|
|
|
|
*
|
|
|
|
* @see https://wikitech.wikimedia.org/wiki/Provenance
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private const OPT_OUT_LINK_TRACKING_CODE = 'vctw1';
|
2014-08-07 11:38:34 +00:00
|
|
|
|
2021-12-15 22:25:11 +00:00
|
|
|
/**
|
|
|
|
* @param string $icon the name of the icon without wikimedia- prefix.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function iconClass( $icon ) {
|
|
|
|
if ( $icon ) {
|
|
|
|
return 'mw-ui-icon-wikimedia-' . $icon;
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2020-01-13 09:23:59 +00:00
|
|
|
/**
|
2022-02-06 22:48:51 +00:00
|
|
|
* Whether the legacy version of the skin is being used.
|
2020-04-28 21:16:21 +00:00
|
|
|
*
|
|
|
|
* @return bool
|
2020-01-13 09:23:59 +00:00
|
|
|
*/
|
2021-09-09 22:13:48 +00:00
|
|
|
protected function isLegacy(): bool {
|
|
|
|
$options = $this->getOptions();
|
|
|
|
if ( $options['name'] === Constants::SKIN_NAME_MODERN ) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-28 21:16:21 +00:00
|
|
|
$isLatestSkinFeatureEnabled = MediaWikiServices::getInstance()
|
|
|
|
->getService( Constants::SERVICE_FEATURE_MANAGER )
|
|
|
|
->isFeatureEnabled( Constants::FEATURE_LATEST_SKIN );
|
2020-01-16 21:11:54 +00:00
|
|
|
|
2020-04-28 21:16:21 +00:00
|
|
|
return !$isLatestSkinFeatureEnabled;
|
2020-04-28 20:58:19 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 19:40:47 +00:00
|
|
|
/**
|
|
|
|
* Calls getLanguages with caching.
|
|
|
|
* @return array
|
|
|
|
*/
|
2022-02-18 22:44:41 +00:00
|
|
|
protected function getLanguagesCached(): array {
|
2021-10-23 12:32:13 +00:00
|
|
|
if ( $this->languages === null ) {
|
|
|
|
$this->languages = $this->getLanguages();
|
2021-02-10 19:40:47 +00:00
|
|
|
}
|
|
|
|
return $this->languages;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This should be upstreamed to the Skin class in core once the logic is finalized.
|
2021-04-21 16:45:06 +00:00
|
|
|
* Returns false if the page is a special page without any languages, or if an action
|
2021-02-10 19:40:47 +00:00
|
|
|
* other than view is being used.
|
|
|
|
* @return bool
|
|
|
|
*/
|
2021-07-23 19:53:14 +00:00
|
|
|
private function canHaveLanguages(): bool {
|
2021-02-10 19:40:47 +00:00
|
|
|
$action = Action::getActionName( $this->getContext() );
|
|
|
|
if ( $action !== '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;
|
|
|
|
}
|
|
|
|
|
2020-08-18 21:30:25 +00:00
|
|
|
/**
|
2021-12-03 23:33:21 +00:00
|
|
|
* @param string $location Either 'top' or 'bottom' is accepted.
|
2020-08-18 21:30:25 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
2022-02-18 22:44:41 +00:00
|
|
|
protected function isLanguagesInContentAt( $location ) {
|
2021-12-03 23:33:21 +00:00
|
|
|
if ( !$this->canHaveLanguages() ) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-18 21:30:25 +00:00
|
|
|
$featureManager = VectorServices::getFeatureManager();
|
2021-12-03 23:33:21 +00:00
|
|
|
$inContent = $featureManager->isFeatureEnabled(
|
2020-08-18 21:30:25 +00:00
|
|
|
Constants::FEATURE_LANGUAGE_IN_HEADER
|
|
|
|
);
|
2021-12-03 23:33:21 +00:00
|
|
|
$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
|
|
|
|
*/
|
|
|
|
private function isLanguagesInContent() {
|
|
|
|
return $this->isLanguagesInContentAt( 'top' ) || $this->isLanguagesInContentAt( 'bottom' );
|
2020-08-18 21:30:25 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 11:58:55 +00:00
|
|
|
/**
|
2022-01-06 21:32:20 +00:00
|
|
|
* Show the ULS button if it's modern Vector, languages in header is enabled,
|
|
|
|
* and the language array isn't empty. Hide it otherwise.
|
2021-03-18 11:58:55 +00:00
|
|
|
* @return bool
|
|
|
|
*/
|
2022-02-18 22:44:41 +00:00
|
|
|
protected function shouldHideLanguages() {
|
2022-01-06 21:32:20 +00:00
|
|
|
// NOTE: T276950 - This should be revisited when an empty state for the language button is chosen.
|
|
|
|
return $this->isLegacy() || !$this->isLanguagesInContent() || empty( $this->getLanguagesCached() );
|
2021-03-18 11:58:55 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 21:37:01 +00:00
|
|
|
/**
|
2021-12-14 18:12:26 +00:00
|
|
|
* Returns HTML for the create account link inside the anon user links
|
2021-06-03 21:37:01 +00:00
|
|
|
* @param string[] $returnto array of query strings used to build the login link
|
2021-07-22 21:20:15 +00:00
|
|
|
* @param string[] $class array of CSS classes to add.
|
|
|
|
* @param bool $includeIcon Set true to include icon CSS classes.
|
2021-06-03 21:37:01 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2021-07-22 21:20:15 +00:00
|
|
|
private function getCreateAccountHTML( $returnto, $class, $includeIcon ) {
|
2021-06-03 21:37:01 +00:00
|
|
|
$createAccountData = $this->buildCreateAccountData( $returnto );
|
|
|
|
$createAccountData['single-id'] = 'pt-createaccount';
|
2021-12-14 18:12:26 +00:00
|
|
|
unset( $createAccountData['icon'] );
|
2021-07-22 21:20:15 +00:00
|
|
|
|
|
|
|
if ( $includeIcon ) {
|
|
|
|
$class = array_merge(
|
|
|
|
$class,
|
|
|
|
[
|
2021-12-15 22:25:11 +00:00
|
|
|
self::CLASS_ICON_LABEL,
|
2022-02-06 22:48:51 +00:00
|
|
|
$this->iconClass( $createAccountData[ 'icon' ] ?? '' )
|
2021-07-22 21:20:15 +00:00
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$createAccountData['class'] = $class;
|
2021-12-14 18:12:26 +00:00
|
|
|
return $this->makeLink( 'create-account', $createAccountData );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns HTML for the watchlist link inside user links
|
2022-01-12 23:44:18 +00:00
|
|
|
* @param array|null $watchlistMenuData (optional)
|
2021-12-14 18:12:26 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2022-01-12 23:44:18 +00:00
|
|
|
private function getWatchlistHTML( $watchlistMenuData = null ) {
|
|
|
|
return $watchlistMenuData ? $watchlistMenuData['html-items'] : '';
|
2021-06-03 21:37:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-07-22 21:20:15 +00:00
|
|
|
* Returns HTML for the create account button, login button and learn more link inside the anon user menu
|
2021-06-03 21:37:01 +00:00
|
|
|
* @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
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-07-22 21:20:15 +00:00
|
|
|
private function getAnonMenuBeforePortletHTML( $returnto, $useCombinedLoginLink ) {
|
2021-06-03 21:37:01 +00:00
|
|
|
// 'single-id' must be provided for `makeLink` to populate `title`, `accesskey` and other attributes
|
|
|
|
$loginData = $this->buildLoginData( $returnto, $useCombinedLoginLink );
|
|
|
|
$loginData['single-id'] = 'pt-login';
|
2021-06-22 15:18:06 +00:00
|
|
|
$loginData['class'] = [
|
|
|
|
'vector-menu-content-item',
|
2021-07-22 21:20:15 +00:00
|
|
|
'vector-menu-content-item-login',
|
2021-12-15 22:25:11 +00:00
|
|
|
self::CLASS_ICON_LABEL,
|
2022-02-06 22:48:51 +00:00
|
|
|
$this->iconClass( $loginData[ 'icon' ] ?? '' )
|
2021-06-22 15:18:06 +00:00
|
|
|
];
|
2021-06-03 21:37:01 +00:00
|
|
|
|
|
|
|
$learnMoreLinkData = [
|
|
|
|
'text' => $this->msg( 'vector-anon-user-menu-pages-learn' )->text(),
|
2021-09-24 17:04:05 +00:00
|
|
|
'href' => Title::newFromText( $this->msg( 'vector-intro-page' )->text() )->getLocalURL(),
|
2021-08-25 20:16:33 +00:00
|
|
|
'aria-label' => $this->msg( 'vector-anon-user-menu-pages-label' )->text(),
|
2021-06-03 21:37:01 +00:00
|
|
|
];
|
|
|
|
$learnMoreLink = $this->makeLink( '', $learnMoreLinkData );
|
|
|
|
|
|
|
|
$templateParser = $this->getTemplateParser();
|
|
|
|
return $templateParser->processTemplate( 'UserLinks__login', [
|
2021-07-22 21:20:15 +00:00
|
|
|
'htmlCreateAccount' => $this->getCreateAccountHTML( $returnto, [
|
|
|
|
'user-links-collapsible-item',
|
|
|
|
'vector-menu-content-item',
|
|
|
|
], true ),
|
2021-06-03 21:37:01 +00:00
|
|
|
'htmlLogin' => $this->makeLink( 'login', $loginData ),
|
|
|
|
'msgLearnMore' => $this->msg( 'vector-anon-user-menu-pages' ),
|
2021-08-25 20:16:33 +00:00
|
|
|
'htmlLearnMoreLink' => $learnMoreLink
|
2021-06-03 21:37:01 +00:00
|
|
|
] );
|
|
|
|
}
|
|
|
|
|
2021-06-04 12:47:39 +00:00
|
|
|
/**
|
|
|
|
* Returns HTML for the logout button that should be placed in the user (personal) menu
|
|
|
|
* after the menu itself.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function getLogoutHTML() {
|
|
|
|
$logoutLinkData = $this->buildLogoutLinkData();
|
|
|
|
$templateParser = $this->getTemplateParser();
|
|
|
|
$logoutLinkData['class'] = [
|
|
|
|
'vector-menu-content-item',
|
2021-07-22 21:20:15 +00:00
|
|
|
'vector-menu-content-item-logout',
|
2021-12-15 22:25:11 +00:00
|
|
|
self::CLASS_ICON_LABEL,
|
2022-02-06 22:48:51 +00:00
|
|
|
$this->iconClass( $logoutLinkData[ 'icon' ] ?? '' )
|
2021-06-04 12:47:39 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
return $templateParser->processTemplate( 'UserLinks__logout', [
|
2021-10-19 15:27:18 +00:00
|
|
|
'msg-tooltip-pt-logout' => $this->msg( 'tooltip-pt-logout' ),
|
2021-06-04 12:47:39 +00:00
|
|
|
'htmlLogout' => $this->makeLink( 'logout', $logoutLinkData )
|
|
|
|
] );
|
|
|
|
}
|
|
|
|
|
2021-06-03 21:37:01 +00:00
|
|
|
/**
|
|
|
|
* Returns template data for UserLinks.mustache
|
|
|
|
* @param array $menuData existing menu template data to be transformed and copied for UserLinks
|
|
|
|
* @param bool $isAnon if the user is logged out, used to conditionally provide data
|
2021-06-02 22:57:43 +00:00
|
|
|
* @param array $searchBoxData representing search box.
|
2021-06-03 21:37:01 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2021-07-23 19:53:14 +00:00
|
|
|
private function getUserLinksTemplateData( $menuData, $isAnon, $searchBoxData ): array {
|
2021-06-03 21:37:01 +00:00
|
|
|
$returnto = $this->getReturnToParam();
|
|
|
|
$useCombinedLoginLink = $this->useCombinedLoginLink();
|
2021-07-22 21:20:15 +00:00
|
|
|
$htmlCreateAccount = $this->getCreateAccountHTML( $returnto, [
|
2021-12-15 22:25:11 +00:00
|
|
|
self::CLASS_QUIET_BUTTON
|
2021-07-22 21:20:15 +00:00
|
|
|
], false );
|
2021-06-03 21:37:01 +00:00
|
|
|
|
2021-06-02 22:57:43 +00:00
|
|
|
$templateParser = $this->getTemplateParser();
|
2021-08-17 18:21:08 +00:00
|
|
|
// See T288428#7303233. The following conditional checks whether config is disabling account creation for
|
|
|
|
// anonymous users in modern Vector. This check excludes the use case of extensions using core and legacy hooks
|
|
|
|
// to remove the "Create account" link from the personal toolbar. Ideally this should be managed with a new hook
|
|
|
|
// that tracks account creation ability.
|
|
|
|
// Supporting removing items via hook involves unnecessary additional complexity we'd rather avoid at this time.
|
|
|
|
// (see https://gerrit.wikimedia.org/r/c/mediawiki/skins/Vector/+/713505/3)
|
|
|
|
// Account creation can be disabled by setting `$wgGroupPermissions['*']['createaccount'] = false;`
|
|
|
|
$isCreateAccountAllowed = $isAnon && $this->getAuthority()->isAllowed( 'createaccount' );
|
2021-06-02 22:57:43 +00:00
|
|
|
$userMoreHtmlItems = $templateParser->processTemplate( 'UserLinks__more', [
|
|
|
|
'is-anon' => $isAnon,
|
2021-08-17 18:21:08 +00:00
|
|
|
'is-create-account-allowed' => $isCreateAccountAllowed,
|
2021-06-02 22:57:43 +00:00
|
|
|
'html-create-account' => $htmlCreateAccount,
|
|
|
|
'data-user-interface-preferences' => $menuData[ 'data-user-interface-preferences' ],
|
|
|
|
'data-notifications' => $menuData[ 'data-notifications' ],
|
|
|
|
'data-user-page' => $menuData[ 'data-user-page' ],
|
2022-01-12 23:44:18 +00:00
|
|
|
'html-vector-watchlist' => $this->getWatchlistHTML( $menuData[ 'data-vector-user-menu-overflow' ] ?? null ),
|
2021-06-02 22:57:43 +00:00
|
|
|
] );
|
|
|
|
$userMoreData = [
|
2021-10-23 12:32:13 +00:00
|
|
|
'id' => 'p-personal-more',
|
|
|
|
'class' => 'mw-portlet mw-portlet-personal-more vector-menu vector-user-menu-more',
|
|
|
|
'html-items' => $userMoreHtmlItems,
|
|
|
|
'label' => $this->msg( 'vector-personal-more-label' ),
|
|
|
|
'heading-class' => 'vector-menu-heading',
|
|
|
|
'is-dropdown' => false,
|
2021-06-02 22:57:43 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
$userMenuData = $menuData[ 'data-user-menu' ];
|
2021-06-14 14:33:29 +00:00
|
|
|
if ( $isAnon ) {
|
2021-07-22 21:20:15 +00:00
|
|
|
$userMenuData[ 'html-before-portal' ] .= $this->getAnonMenuBeforePortletHTML(
|
|
|
|
$returnto,
|
|
|
|
$useCombinedLoginLink
|
|
|
|
);
|
2021-06-14 14:33:29 +00:00
|
|
|
} else {
|
2021-06-04 12:47:39 +00:00
|
|
|
// Appending as to not override data potentially set by the onSkinAfterPortlet hook.
|
|
|
|
$userMenuData[ 'html-after-portal' ] .= $this->getLogoutHTML();
|
|
|
|
}
|
|
|
|
|
2021-06-03 21:37:01 +00:00
|
|
|
return [
|
2021-06-02 22:57:43 +00:00
|
|
|
'data-user-more' => $userMoreData,
|
2021-06-03 21:37:01 +00:00
|
|
|
'data-user-menu' => $userMenuData
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-07-28 18:20:07 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
protected function runOnSkinTemplateNavigationHooks( SkinTemplate $skin, &$content_navigation ) {
|
|
|
|
parent::runOnSkinTemplateNavigationHooks( $skin, $content_navigation );
|
|
|
|
Hooks::onSkinTemplateNavigation( $skin, $content_navigation );
|
|
|
|
}
|
|
|
|
|
2021-09-21 02:12:48 +00:00
|
|
|
/**
|
|
|
|
* Updates modules for use in legacy Vector skin.
|
|
|
|
* Do not repeat this pattern. Will be addressed in T291098.
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function getDefaultModules() {
|
|
|
|
// FIXME: Do not repeat this pattern. Will be addressed in T291098.
|
|
|
|
if ( $this->isLegacy() ) {
|
2021-09-09 22:13:48 +00:00
|
|
|
$this->options['scripts'] = SkinVectorLegacy::getScriptsOption();
|
|
|
|
$this->options['styles'] = SkinVectorLegacy::getStylesOption();
|
|
|
|
} else {
|
|
|
|
$this->options['scripts'] = SkinVector22::getScriptsOption();
|
|
|
|
$this->options['styles'] = SkinVector22::getStylesOption();
|
2021-09-21 02:12:48 +00:00
|
|
|
}
|
|
|
|
return parent::getDefaultModules();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates HTML generation for use in legacy Vector skin.
|
|
|
|
* Do not repeat this pattern. Will be addressed in T291098.
|
|
|
|
*
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function generateHTML() {
|
|
|
|
if ( $this->isLegacy() ) {
|
2021-09-09 22:13:48 +00:00
|
|
|
$this->options['template'] = SkinVectorLegacy::getTemplateOption();
|
2021-09-21 02:12:48 +00:00
|
|
|
}
|
|
|
|
return parent::generateHTML();
|
|
|
|
}
|
|
|
|
|
2021-09-28 22:43:15 +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.
|
2022-01-13 18:57:48 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2021-09-28 22:43:15 +00:00
|
|
|
$original['class'] = implode( ' ', [ $original['class'] ?? '', self::STICKY_HEADER_ENABLED_CLASS ] );
|
|
|
|
}
|
|
|
|
|
2022-01-11 19:52:26 +00:00
|
|
|
if ( VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_TABLE_OF_CONTENTS ) ) {
|
|
|
|
$original['class'] = implode( ' ', [ $original['class'] ?? '', self::TABLE_OF_CONTENTS_ENABLED_CLASS ] );
|
|
|
|
}
|
|
|
|
|
2021-09-28 22:43:15 +00:00
|
|
|
return $original;
|
|
|
|
}
|
|
|
|
|
2021-08-30 22:44:00 +00:00
|
|
|
/**
|
|
|
|
* Generate data needed to generate the sticky header.
|
2021-09-16 19:27:10 +00:00
|
|
|
* @param array $searchBoxData
|
2021-10-26 21:34:05 +00:00
|
|
|
* @param bool $includeEditIcons
|
2021-08-30 22:44:00 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2021-10-26 21:34:05 +00:00
|
|
|
private function getStickyHeaderData( $searchBoxData, $includeEditIcons ): array {
|
|
|
|
$btns = [
|
|
|
|
self::TALK_ICON,
|
|
|
|
self::HISTORY_ICON,
|
2021-11-15 23:09:51 +00:00
|
|
|
self::WATCHSTAR_ICON,
|
2021-10-26 21:34:05 +00:00
|
|
|
];
|
|
|
|
if ( $includeEditIcons ) {
|
|
|
|
$btns[] = self::EDIT_WIKITEXT_ICON;
|
|
|
|
$btns[] = self::EDIT_PROTECTED_ICON;
|
|
|
|
$btns[] = self::EDIT_VE_ICON;
|
|
|
|
}
|
2022-01-06 21:32:20 +00:00
|
|
|
|
|
|
|
// Show sticky ULS if the ULS extension is enabled and the ULS in header is not hidden
|
|
|
|
$showStickyULS = ExtensionRegistry::getInstance()->isLoaded( 'UniversalLanguageSelector' )
|
|
|
|
&& !$this->shouldHideLanguages();
|
2021-08-30 22:44:00 +00:00
|
|
|
return [
|
2022-01-06 21:32:20 +00:00
|
|
|
'data-primary-action' => $showStickyULS ?
|
2021-12-09 20:07:56 +00:00
|
|
|
$this->getULSButtonData() : null,
|
2021-09-09 21:40:06 +00:00
|
|
|
'data-button-start' => [
|
|
|
|
'label' => $this->msg( 'search' ),
|
|
|
|
'icon' => 'wikimedia-search',
|
|
|
|
'is-quiet' => true,
|
2021-09-21 16:01:33 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-09 21:40:06 +00:00
|
|
|
'class' => 'vector-sticky-header-search-toggle',
|
2021-12-07 23:20:20 +00:00
|
|
|
'event' => 'ui.' . $searchBoxData['form-id'] . '.icon'
|
2021-09-09 21:40:06 +00:00
|
|
|
],
|
2021-09-16 19:27:10 +00:00
|
|
|
'data-search' => $searchBoxData,
|
2021-10-26 21:34:05 +00:00
|
|
|
'data-buttons' => $btns,
|
2021-08-30 22:44:00 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-12-07 20:28:58 +00:00
|
|
|
/**
|
|
|
|
* Generate data needed to create SidebarAction item.
|
|
|
|
* @param array $htmlData data to make a link or raw html
|
|
|
|
* @param array $headingOptions optional heading for the html
|
|
|
|
* @return array keyed data for the SidebarAction template
|
|
|
|
*/
|
|
|
|
private function makeSidebarActionData( array $htmlData = [], array $headingOptions = [] ): array {
|
|
|
|
$htmlContent = '';
|
|
|
|
// Populates the sidebar as a standalone link or custom html.
|
|
|
|
if ( array_key_exists( 'link', $htmlData ) ) {
|
|
|
|
$htmlContent = $this->makeLink( 'link', $htmlData['link'] );
|
|
|
|
} elseif ( array_key_exists( 'html-content', $htmlData ) ) {
|
|
|
|
$htmlContent = $htmlData['html-content'];
|
|
|
|
}
|
|
|
|
|
2021-12-10 16:38:51 +00:00
|
|
|
return $headingOptions + [
|
2021-12-07 20:28:58 +00:00
|
|
|
'html-content' => $htmlContent,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the language switching alert box should be in the sidebar.
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function shouldLanguageAlertBeInSidebar(): bool {
|
|
|
|
$featureManager = VectorServices::getFeatureManager();
|
2022-01-26 07:57:04 +00:00
|
|
|
$isMainPage = $this->getTitle() ? $this->getTitle()->isMainPage() : false;
|
2022-01-31 16:43:15 +00:00
|
|
|
$shouldShowOnMainPage = $isMainPage && !empty( $this->getLanguagesCached() ) &&
|
2022-01-26 07:57:04 +00:00
|
|
|
$featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_IN_MAIN_PAGE_HEADER );
|
2022-02-18 22:44:41 +00:00
|
|
|
return ( $this->isLanguagesInContentAt( 'top' ) && !$isMainPage && !$this->shouldHideLanguages() &&
|
2022-01-26 07:57:04 +00:00
|
|
|
$featureManager->isFeatureEnabled( Constants::FEATURE_LANGUAGE_ALERT_IN_SIDEBAR ) ) ||
|
|
|
|
$shouldShowOnMainPage;
|
2021-12-07 20:28:58 +00:00
|
|
|
}
|
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
/**
|
2020-04-28 21:16:21 +00:00
|
|
|
* @inheritDoc
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
2021-07-23 19:53:14 +00:00
|
|
|
public function getTemplateData(): array {
|
2020-04-28 21:16:21 +00:00
|
|
|
$skin = $this;
|
2020-04-28 17:58:55 +00:00
|
|
|
$out = $skin->getOutput();
|
2022-01-18 21:45:46 +00:00
|
|
|
$parentData = $this->decoratePortletsData( parent::getTemplateData() );
|
2022-01-24 22:44:11 +00:00
|
|
|
$featureManager = VectorServices::getFeatureManager();
|
2021-03-18 11:58:55 +00:00
|
|
|
|
2022-03-03 18:08:30 +00:00
|
|
|
// SkinVector sometimes serves new Vector as part of removing the
|
|
|
|
// skin version user preference. TCho avoid T302461 we need to unset it here.
|
|
|
|
// This shouldn't be run on SkinVector22.
|
|
|
|
if ( $this->getSkinName() === 'vector' ) {
|
|
|
|
unset( $parentData['data-toc'] );
|
|
|
|
}
|
|
|
|
|
2022-01-18 21:45:46 +00:00
|
|
|
//
|
2020-03-06 22:53:04 +00:00
|
|
|
// Naming conventions for Mustache parameters.
|
|
|
|
//
|
|
|
|
// Value type (first segment):
|
2020-04-14 22:44:33 +00:00
|
|
|
// - Prefix "is" or "has" for boolean values.
|
2020-03-06 22:53:04 +00:00
|
|
|
// - Prefix "msg-" for interface message text.
|
|
|
|
// - Prefix "html-" for raw HTML.
|
|
|
|
// - Prefix "data-" for an array of template parameters that should be passed directly
|
2020-02-17 19:49:43 +00:00
|
|
|
// to a template partial.
|
2020-03-06 22:53:04 +00:00
|
|
|
// - 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 '').
|
2020-09-29 17:41:39 +00:00
|
|
|
$commonSkinData = array_merge( $parentData, [
|
2021-05-18 18:27:33 +00:00
|
|
|
'is-legacy' => $this->isLegacy(),
|
|
|
|
|
2020-09-30 16:02:31 +00:00
|
|
|
'input-location' => $this->getSearchBoxInputLocation(),
|
[modern] A new version of Vector with a new logo
Changes to support feature:
* ResourceLoaderSkinModule logo features are dropped
* New layout provided given the fork in layout between legacy and new.
* Legacy sidebar styles now pulled out
* breakpoint styles are not carried over from legacy Vector
The new Vector layout for now has one breakpoint.
Changes to storybook:
* The storybook script now pulls down image assets so that the logos can
be shown in storybook. The script is adjusted to make use of a static folder to
serve these images.
Note:
* The legacy mode is not touched as part of this patchset.
* The personal menu is unaffected by this patch and is out of scope.
* The alignment issue is noted, but will be solved at a later date.
* Changes to portal are out of scope.
* Adding storybook for modern descoped, given its not possible to load
both legacy layout and modern layout inside a storybook at current time.
Sample config:
$wgLogos = [
'icon' => 'https://di-logo-sandbox.firebaseapp.com/img/globe.png',
'tagline' => [
'src' => 'https://di-logo-sandbox.firebaseapp.com/img/tagline/en-tagline-117-13.svg',
'width' => 117,
'height' => 13,
],
'1x' => 'https://en.wikipedia.org/static/images/project-logos/enwiki.png',
'wordmark' => [
'src' => 'https://en.wikipedia.org/static/images/mobile/copyright/wikipedia-wordmark-en.svg',
'width' => 116,
'height' => 18,
],
];
Coauthor: Aron Manning
Bug: T246170
Change-Id: Ibc4b055150761388a6b78f9127da342c451ce0e7
2020-03-09 05:51:00 +00:00
|
|
|
|
2020-07-07 08:59:14 +00:00
|
|
|
'sidebar-visible' => $this->isSidebarVisible(),
|
2021-01-26 23:48:20 +00:00
|
|
|
|
2021-12-03 23:33:21 +00:00
|
|
|
'is-language-in-content' => $this->isLanguagesInContent(),
|
|
|
|
'is-language-in-content-top' => $this->isLanguagesInContentAt( 'top' ),
|
|
|
|
'is-language-in-content-bottom' => $this->isLanguagesInContentAt( 'bottom' ),
|
2021-09-16 19:27:10 +00:00
|
|
|
'data-search-box' => $this->getSearchData(
|
|
|
|
$parentData['data-search-box'],
|
|
|
|
!$this->isLegacy(),
|
|
|
|
// is primary mode of search
|
|
|
|
true,
|
2021-12-16 03:13:56 +00:00
|
|
|
'searchform',
|
|
|
|
true
|
2021-09-16 19:27:10 +00:00
|
|
|
),
|
2022-01-24 22:44:11 +00:00
|
|
|
'data-vector-sticky-header' => $featureManager->isFeatureEnabled(
|
2021-08-30 22:44:00 +00:00
|
|
|
Constants::FEATURE_STICKY_HEADER
|
2021-09-16 19:27:10 +00:00
|
|
|
) ? $this->getStickyHeaderData(
|
|
|
|
$this->getSearchData(
|
|
|
|
$parentData['data-search-box'],
|
|
|
|
// Collapse inside search box is disabled.
|
|
|
|
false,
|
|
|
|
false,
|
2021-12-16 03:13:56 +00:00
|
|
|
'vector-sticky-search-form',
|
|
|
|
false
|
2021-10-26 21:34:05 +00:00
|
|
|
),
|
2022-01-24 22:44:11 +00:00
|
|
|
$featureManager->isFeatureEnabled(
|
2021-10-26 21:34:05 +00:00
|
|
|
Constants::FEATURE_STICKY_HEADER_EDIT
|
2021-09-16 19:27:10 +00:00
|
|
|
)
|
|
|
|
) : false,
|
2022-02-18 00:19:50 +00:00
|
|
|
'data-toc' => $this->getTocData( $parentData['data-toc'] ?? [] )
|
2020-09-04 19:18:57 +00:00
|
|
|
] );
|
2019-02-26 18:44:10 +00:00
|
|
|
|
2020-12-18 02:51:28 +00:00
|
|
|
if ( $skin->getUser()->isRegistered() ) {
|
2022-01-24 16:51:50 +00:00
|
|
|
$migrationMode = $this->getConfig()->get( 'VectorSkinMigrationMode' );
|
|
|
|
$query = $migrationMode ? 'useskin=vector&' : '';
|
2020-07-30 20:32:13 +00:00
|
|
|
// Note: This data is also passed to legacy template where it is unused.
|
2021-12-07 20:28:58 +00:00
|
|
|
$optOutUrl = [
|
|
|
|
'text' => $this->msg( 'vector-opt-out' )->text(),
|
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions
This commit is singularly focused on adding a link to the sidebar for
Vector, logged-in users. It does the bare minimum to fulfill the
requirements of T243281.
Additionally, it will help to answer the question "Do we need to use
abstractions (other than maybe different templates) to separate Legacy
Vector from Vector" by intentionally leaving out any abstractions in
order to make it easier to compare with a follow-up patch
(Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions.
It is a good thing to question whether or not we need addtional
abstractions in VectorTemplate and if they will help us as unnecessary
abstractions can have the opposite effect and just lead to further
frustrations down the road.
Therefore, I urge you, the reviewer, to let me know your thoughts! If
abstractions are viewed as not making our lives any easier, the
follow-up patches may be completely discarded and that's totally okay
with me. :) I think it's a good think to talk about now though.
Important changes:
* The VectorTemplate constructor was changed to allow injecting the
config, templateParser, and isLegacy boolean (only the config was
allowed before this commit). According to MediaWiki's Stable Interface
Policy, "Constructor signatures are generally considered unstable unless
explicitly declared stable for calling" [3]. Given that VecorTemplate's
constructor is not marked as stable, it is justified to do this without
warning according to the policy.
* Due to the above, the 'setTemplate' method is no longer needed and was
marked as deprecated.
* VectorTemplateTest was made to adapt to the new VectorTemplate
constructor. Additionally, it now extends from
MediaWikiIntegrationTestCase which my intelliphense server can pick up.
I *think* MediaWikiTestCase is just an alias to
MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed
to MediaWikiIntegrationTestCase in [2], but I'm willing to change it
back if there is pushback to this.
Open questions:
* What are VectorTemplate's responsibilities? To me, it acts right now
as a controller (because it echos the full HTML string from the
template), a model (because SkinTemplate::prepareQuickTemplate sets data
on it which it later retrieves through `$this->get()`), a presenter
(because it adds data tailored for a web-centric view), and a view
(because it renders HTML strings instead of letting the view/template be
solely responsible for that). Arguably, some business logic might be
mixed in there as well (because it checks to see if a User is logged
in/has necessary permissions to show x which my changes here add to).
This might not be a problem if we keep VectorTemplate relatively small,
but will it remain this way as we progress further in Desktop
Improvements?
* How do we write tests for VectorTemplate without exposing unnecessary
public methods? For example, if I want to test the `getSkinData()`
method to see what state will be sent to the template, how should I do
this? One option might be to use `TestingAccessWrapper` to expose these
private methods which is what
`VectorTemplateTest::testbuildViewsProps()` does. Another option is to
accept this method as public. Is there a better way? Keep in mind that
even with access to this method, there might be many things to mock.
[1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64
[2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76
[3] https://www.mediawiki.org/wiki/Stable_interface_policy
Bug: T243281
Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
|
|
|
'href' => SpecialPage::getTitleFor(
|
|
|
|
'Preferences',
|
|
|
|
false,
|
2022-02-02 18:35:36 +00:00
|
|
|
$migrationMode ? 'mw-prefsection-rendering-skin' : 'mw-prefsection-rendering-skin-skin-prefs'
|
2022-01-24 16:51:50 +00:00
|
|
|
)->getLinkURL( $query . 'wprov=' . self::OPT_OUT_LINK_TRACKING_CODE ),
|
2021-12-07 20:28:58 +00:00
|
|
|
'title' => $this->msg( 'vector-opt-out-tooltip' )->text(),
|
|
|
|
'active' => false,
|
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions
This commit is singularly focused on adding a link to the sidebar for
Vector, logged-in users. It does the bare minimum to fulfill the
requirements of T243281.
Additionally, it will help to answer the question "Do we need to use
abstractions (other than maybe different templates) to separate Legacy
Vector from Vector" by intentionally leaving out any abstractions in
order to make it easier to compare with a follow-up patch
(Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions.
It is a good thing to question whether or not we need addtional
abstractions in VectorTemplate and if they will help us as unnecessary
abstractions can have the opposite effect and just lead to further
frustrations down the road.
Therefore, I urge you, the reviewer, to let me know your thoughts! If
abstractions are viewed as not making our lives any easier, the
follow-up patches may be completely discarded and that's totally okay
with me. :) I think it's a good think to talk about now though.
Important changes:
* The VectorTemplate constructor was changed to allow injecting the
config, templateParser, and isLegacy boolean (only the config was
allowed before this commit). According to MediaWiki's Stable Interface
Policy, "Constructor signatures are generally considered unstable unless
explicitly declared stable for calling" [3]. Given that VecorTemplate's
constructor is not marked as stable, it is justified to do this without
warning according to the policy.
* Due to the above, the 'setTemplate' method is no longer needed and was
marked as deprecated.
* VectorTemplateTest was made to adapt to the new VectorTemplate
constructor. Additionally, it now extends from
MediaWikiIntegrationTestCase which my intelliphense server can pick up.
I *think* MediaWikiTestCase is just an alias to
MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed
to MediaWikiIntegrationTestCase in [2], but I'm willing to change it
back if there is pushback to this.
Open questions:
* What are VectorTemplate's responsibilities? To me, it acts right now
as a controller (because it echos the full HTML string from the
template), a model (because SkinTemplate::prepareQuickTemplate sets data
on it which it later retrieves through `$this->get()`), a presenter
(because it adds data tailored for a web-centric view), and a view
(because it renders HTML strings instead of letting the view/template be
solely responsible for that). Arguably, some business logic might be
mixed in there as well (because it checks to see if a User is logged
in/has necessary permissions to show x which my changes here add to).
This might not be a problem if we keep VectorTemplate relatively small,
but will it remain this way as we progress further in Desktop
Improvements?
* How do we write tests for VectorTemplate without exposing unnecessary
public methods? For example, if I want to test the `getSkinData()`
method to see what state will be sent to the template, how should I do
this? One option might be to use `TestingAccessWrapper` to expose these
private methods which is what
`VectorTemplateTest::testbuildViewsProps()` does. Another option is to
accept this method as public. Is there a better way? Keep in mind that
even with access to this method, there might be many things to mock.
[1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64
[2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76
[3] https://www.mediawiki.org/wiki/Stable_interface_policy
Bug: T243281
Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
|
|
|
];
|
2021-12-07 20:28:58 +00:00
|
|
|
$htmlData = [
|
|
|
|
'link' => $optOutUrl,
|
|
|
|
];
|
|
|
|
$commonSkinData['data-emphasized-sidebar-action'][] = $this->makeSidebarActionData(
|
|
|
|
$htmlData,
|
|
|
|
[]
|
|
|
|
);
|
2021-06-03 21:37:01 +00:00
|
|
|
}
|
2021-05-07 21:56:29 +00:00
|
|
|
|
2021-08-24 21:34:59 +00:00
|
|
|
if ( !$this->isLegacy() ) {
|
2021-06-03 21:37:01 +00:00
|
|
|
$commonSkinData['data-vector-user-links'] = $this->getUserLinksTemplateData(
|
|
|
|
$commonSkinData['data-portlets'],
|
2021-06-02 22:57:43 +00:00
|
|
|
$commonSkinData['is-anon'],
|
|
|
|
$commonSkinData['data-search-box']
|
2021-06-03 21:37:01 +00:00
|
|
|
);
|
2021-12-07 20:28:58 +00:00
|
|
|
|
|
|
|
// T295555 Add language switch alert message temporarily (to be removed).
|
2022-01-26 07:57:04 +00:00
|
|
|
if ( $this->shouldLanguageAlertBeInSidebar() ) {
|
2021-12-07 20:28:58 +00:00
|
|
|
$languageSwitchAlert = [
|
2022-01-27 22:25:30 +00:00
|
|
|
'html-content' => Html::noticeBox(
|
|
|
|
$this->msg( 'vector-language-redirect-to-top' )->parse(),
|
|
|
|
'vector-language-sidebar-alert'
|
2021-12-10 16:38:51 +00:00
|
|
|
),
|
2021-12-07 20:28:58 +00:00
|
|
|
];
|
|
|
|
$headingOptions = [
|
|
|
|
'heading' => $this->msg( 'vector-languages' )->plain(),
|
|
|
|
];
|
|
|
|
$commonSkinData['data-vector-language-switch-alert'][] = $this->makeSidebarActionData(
|
|
|
|
$languageSwitchAlert,
|
|
|
|
$headingOptions
|
|
|
|
);
|
|
|
|
}
|
Add opt-out link to Sidebar for Vector/Logged-in Users Without Abstractions
This commit is singularly focused on adding a link to the sidebar for
Vector, logged-in users. It does the bare minimum to fulfill the
requirements of T243281.
Additionally, it will help to answer the question "Do we need to use
abstractions (other than maybe different templates) to separate Legacy
Vector from Vector" by intentionally leaving out any abstractions in
order to make it easier to compare with a follow-up patch
(Ib2ef15180df73360cc1de25b893e49d415d23e1a) which does use abstractions.
It is a good thing to question whether or not we need addtional
abstractions in VectorTemplate and if they will help us as unnecessary
abstractions can have the opposite effect and just lead to further
frustrations down the road.
Therefore, I urge you, the reviewer, to let me know your thoughts! If
abstractions are viewed as not making our lives any easier, the
follow-up patches may be completely discarded and that's totally okay
with me. :) I think it's a good think to talk about now though.
Important changes:
* The VectorTemplate constructor was changed to allow injecting the
config, templateParser, and isLegacy boolean (only the config was
allowed before this commit). According to MediaWiki's Stable Interface
Policy, "Constructor signatures are generally considered unstable unless
explicitly declared stable for calling" [3]. Given that VecorTemplate's
constructor is not marked as stable, it is justified to do this without
warning according to the policy.
* Due to the above, the 'setTemplate' method is no longer needed and was
marked as deprecated.
* VectorTemplateTest was made to adapt to the new VectorTemplate
constructor. Additionally, it now extends from
MediaWikiIntegrationTestCase which my intelliphense server can pick up.
I *think* MediaWikiTestCase is just an alias to
MediaWikiIntegrationTestCase [1] and MediaWikiTestCase file was renamed
to MediaWikiIntegrationTestCase in [2], but I'm willing to change it
back if there is pushback to this.
Open questions:
* What are VectorTemplate's responsibilities? To me, it acts right now
as a controller (because it echos the full HTML string from the
template), a model (because SkinTemplate::prepareQuickTemplate sets data
on it which it later retrieves through `$this->get()`), a presenter
(because it adds data tailored for a web-centric view), and a view
(because it renders HTML strings instead of letting the view/template be
solely responsible for that). Arguably, some business logic might be
mixed in there as well (because it checks to see if a User is logged
in/has necessary permissions to show x which my changes here add to).
This might not be a problem if we keep VectorTemplate relatively small,
but will it remain this way as we progress further in Desktop
Improvements?
* How do we write tests for VectorTemplate without exposing unnecessary
public methods? For example, if I want to test the `getSkinData()`
method to see what state will be sent to the template, how should I do
this? One option might be to use `TestingAccessWrapper` to expose these
private methods which is what
`VectorTemplateTest::testbuildViewsProps()` does. Another option is to
accept this method as public. Is there a better way? Keep in mind that
even with access to this method, there might be many things to mock.
[1] https://github.com/wikimedia/mediawiki/blob/0030cb525be6cabc1d63de80586b2017d4bbe354/tests/common/TestsAutoLoader.php#L64
[2] Ie717b0ecf4fcfd089d46248f14853c80b7ef4a76
[3] https://www.mediawiki.org/wiki/Stable_interface_policy
Bug: T243281
Change-Id: I0571b041bcd7f19bec9f103fa7bccdd093f6394d
2020-03-17 20:21:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $commonSkinData;
|
|
|
|
}
|
|
|
|
|
2022-02-18 00:19:50 +00:00
|
|
|
/**
|
|
|
|
* Annotates table of contents data with Vector-specific information.
|
|
|
|
*
|
|
|
|
* @param array $tocData
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getTocData( array $tocData ): array {
|
2022-03-17 19:03:48 +00:00
|
|
|
// If the table of contents has no items, we won't output it.
|
|
|
|
// empty array is interpreted by Mustache as falsey.
|
|
|
|
if ( empty( $tocData ) || empty( $tocData[ 'array-sections' ] ) ) {
|
2022-02-18 00:19:50 +00:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_merge( $tocData, [
|
|
|
|
'vector-is-collapse-sections-enabled' =>
|
|
|
|
$tocData[ 'number-section-count'] >= $this->getConfig()->get(
|
|
|
|
'VectorTableOfContentsCollapseAtCount'
|
|
|
|
)
|
|
|
|
] );
|
|
|
|
}
|
|
|
|
|
2021-06-02 22:57:43 +00:00
|
|
|
/**
|
|
|
|
* Annotates search box with Vector-specific information
|
|
|
|
*
|
|
|
|
* @param array $searchBoxData
|
2021-08-24 21:34:59 +00:00
|
|
|
* @param bool $isCollapsible
|
2021-09-16 18:00:05 +00:00
|
|
|
* @param bool $isPrimary
|
|
|
|
* @param string $formId
|
2021-12-16 03:13:56 +00:00
|
|
|
* @param bool $autoExpandWidth
|
2021-06-02 22:57:43 +00:00
|
|
|
* @return array modified version of $searchBoxData
|
|
|
|
*/
|
2021-12-16 03:13:56 +00:00
|
|
|
private function getSearchData(
|
|
|
|
array $searchBoxData,
|
|
|
|
bool $isCollapsible,
|
|
|
|
bool $isPrimary,
|
|
|
|
string $formId,
|
|
|
|
bool $autoExpandWidth
|
|
|
|
) {
|
2021-09-09 19:11:23 +00:00
|
|
|
$searchClass = 'vector-search-box-vue ';
|
2021-06-02 22:57:43 +00:00
|
|
|
|
2021-08-24 21:34:59 +00:00
|
|
|
if ( $isCollapsible ) {
|
2021-09-16 18:00:05 +00:00
|
|
|
$searchClass .= ' vector-search-box-collapses ';
|
2021-06-02 22:57:43 +00:00
|
|
|
}
|
|
|
|
|
2021-12-16 03:13:56 +00:00
|
|
|
if ( $this->doesSearchHaveThumbnails() ) {
|
|
|
|
$searchClass .= ' ' . self::SEARCH_SHOW_THUMBNAIL_CLASS .
|
|
|
|
( $autoExpandWidth ? ' ' . self::SEARCH_AUTO_EXPAND_WIDTH_CLASS : '' );
|
2021-06-02 22:57:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Annotate search box with a component class.
|
2021-09-16 18:00:05 +00:00
|
|
|
$searchBoxData['class'] = trim( $searchClass );
|
2021-08-24 21:34:59 +00:00
|
|
|
$searchBoxData['is-collapsible'] = $isCollapsible;
|
2021-09-16 18:00:05 +00:00
|
|
|
$searchBoxData['is-primary'] = $isPrimary;
|
|
|
|
$searchBoxData['form-id'] = $formId;
|
2021-06-02 22:57:43 +00:00
|
|
|
|
|
|
|
// At lower resolutions the search input is hidden search and only the submit button is shown.
|
|
|
|
// It should behave like a form submit link (e.g. submit the form with no input value).
|
|
|
|
// We'll wire this up in a later task T284242.
|
2021-09-03 19:00:45 +00:00
|
|
|
$searchBoxData['data-collapse-icon'] = [
|
|
|
|
'href' => Title::newFromText( $searchBoxData['page-title'] )->getLocalUrl(),
|
|
|
|
'label' => $this->msg( 'search' ),
|
|
|
|
'icon' => 'wikimedia-search',
|
|
|
|
'is-quiet' => true,
|
|
|
|
'class' => 'search-toggle',
|
|
|
|
];
|
2021-06-02 22:57:43 +00:00
|
|
|
|
|
|
|
return $searchBoxData;
|
|
|
|
}
|
|
|
|
|
2020-09-24 12:46:48 +00:00
|
|
|
/**
|
|
|
|
* 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`
|
|
|
|
*/
|
2021-07-23 19:53:14 +00:00
|
|
|
private function getSearchBoxInputLocation(): string {
|
2020-09-24 12:46:48 +00:00
|
|
|
if ( $this->isLegacy() ) {
|
|
|
|
return Constants::SEARCH_BOX_INPUT_LOCATION_DEFAULT;
|
|
|
|
}
|
|
|
|
|
2020-09-30 16:02:31 +00:00
|
|
|
return Constants::SEARCH_BOX_INPUT_LOCATION_MOVED;
|
2020-09-24 12:46:48 +00:00
|
|
|
}
|
|
|
|
|
2021-08-13 18:21:52 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function isResponsive() {
|
|
|
|
// Check it's enabled by user preference and configuration
|
|
|
|
$responsive = parent::isResponsive() && $this->getConfig()->get( 'VectorResponsive' );
|
|
|
|
// For historic reasons, the viewport is added when Vector is loaded on the mobile
|
|
|
|
// domain. This is only possible for 3rd parties or by useskin parameter as there is
|
|
|
|
// no preference for changing mobile skin. Only need to check if $responsive is falsey.
|
|
|
|
if ( !$responsive && ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) ) {
|
|
|
|
$mobFrontContext = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' );
|
|
|
|
if ( $mobFrontContext->shouldDisplayMobileView() ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $responsive;
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:48:20 +00:00
|
|
|
/**
|
2021-12-16 03:13:56 +00:00
|
|
|
* Returns `true` if WVUI is enabled to show thumbnails and `false` otherwise.
|
|
|
|
* Note this is only relevant for WVUI search (not legacy search).
|
2021-01-26 23:48:20 +00:00
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
2021-12-16 03:13:56 +00:00
|
|
|
private function doesSearchHaveThumbnails(): bool {
|
2021-09-09 19:11:23 +00:00
|
|
|
return $this->getConfig()->get( 'VectorWvuiSearchOptions' )['showThumbnail'];
|
2021-01-26 23:48:20 +00:00
|
|
|
}
|
|
|
|
|
2020-07-07 08:59:14 +00:00
|
|
|
/**
|
|
|
|
* Determines wheather the initial state of sidebar is visible on not
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function isSidebarVisible() {
|
|
|
|
$skin = $this->getSkin();
|
2020-12-18 02:51:28 +00:00
|
|
|
if ( $skin->getUser()->isRegistered() ) {
|
2021-11-23 13:40:35 +00:00
|
|
|
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
|
|
|
|
$userPrefSidebarState = $userOptionsLookup->getOption(
|
|
|
|
$skin->getUser(),
|
2020-06-30 22:39:09 +00:00
|
|
|
Constants::PREF_KEY_SIDEBAR_VISIBLE
|
|
|
|
);
|
|
|
|
|
|
|
|
$defaultLoggedinSidebarState = $this->getConfig()->get(
|
2020-07-07 08:59:14 +00:00
|
|
|
Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_AUTHORISED_USER
|
|
|
|
);
|
2020-06-30 22:39:09 +00:00
|
|
|
|
|
|
|
// 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;
|
2020-07-07 08:59:14 +00:00
|
|
|
}
|
|
|
|
return $this->getConfig()->get(
|
|
|
|
Constants::CONFIG_KEY_DEFAULT_SIDEBAR_VISIBLE_FOR_ANONYMOUS_USER
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-18 11:58:55 +00:00
|
|
|
/**
|
2021-09-14 22:56:54 +00:00
|
|
|
* Returns ULS button label within the context of the translated message taking a placeholder.
|
2021-03-18 11:58:55 +00:00
|
|
|
*
|
2021-09-14 22:56:54 +00:00
|
|
|
* @param string $message
|
2021-09-07 20:22:19 +00:00
|
|
|
* @return string
|
2021-03-18 11:58:55 +00:00
|
|
|
*/
|
2021-09-14 22:56:54 +00:00
|
|
|
private function getULSLabel( string $message ) {
|
2022-02-06 22:48:51 +00:00
|
|
|
return $this->msg( $message )
|
2021-06-08 16:14:27 +00:00
|
|
|
->numParams( count( $this->getLanguagesCached() ) )
|
|
|
|
->escaped();
|
2021-09-07 20:22:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates button data for the ULS button in the sticky header
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getULSButtonData() {
|
|
|
|
return [
|
|
|
|
'id' => 'p-lang-btn-sticky-header',
|
|
|
|
'class' => 'mw-interlanguage-selector',
|
|
|
|
'is-quiet' => true,
|
2021-09-21 16:01:33 +00:00
|
|
|
'tabindex' => '-1',
|
2021-09-14 22:56:54 +00:00
|
|
|
'label' => $this->getULSLabel( 'vector-language-button-label' ),
|
2021-09-07 20:22:19 +00:00
|
|
|
'html-vector-button-icon' => Hooks::makeIcon( 'wikimedia-language' ),
|
2021-11-19 16:38:13 +00:00
|
|
|
'event' => 'ui.dropdown-p-lang-btn-sticky-header'
|
2021-09-07 20:22:19 +00:00
|
|
|
];
|
|
|
|
}
|
2021-06-08 16:14:27 +00:00
|
|
|
|
2021-09-07 20:22:19 +00:00
|
|
|
/**
|
|
|
|
* Creates portlet data for the ULS button in the header
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getULSPortletData() {
|
2021-03-18 11:58:55 +00:00
|
|
|
$languageButtonData = [
|
|
|
|
'id' => 'p-lang-btn',
|
2021-09-14 22:56:54 +00:00
|
|
|
'label' => $this->getULSLabel( 'vector-language-button-label' ),
|
|
|
|
'aria-label' => $this->getULSLabel( 'vector-language-button-aria-label' ),
|
2021-08-13 20:56:51 +00:00
|
|
|
// ext.uls.interface attaches click handler to this selector.
|
|
|
|
'checkbox-class' => ' mw-interlanguage-selector ',
|
2021-12-01 19:49:12 +00:00
|
|
|
'html-vector-heading-icon' => Hooks::makeIcon( 'wikimedia-language-progressive' ),
|
2021-03-18 11:58:55 +00:00
|
|
|
'heading-class' =>
|
|
|
|
' vector-menu-heading ' .
|
2021-12-15 22:25:11 +00:00
|
|
|
self::CLASS_QUIET_BUTTON . ' ' .
|
|
|
|
self::CLASS_PROGRESSIVE
|
2021-03-18 11:58:55 +00:00
|
|
|
];
|
2021-07-26 21:00:38 +00:00
|
|
|
|
|
|
|
// Adds class to hide language button
|
|
|
|
// Temporary solution to T287206, can be removed when ULS dialog includes interwiki links
|
|
|
|
if ( $this->shouldHideLanguages() ) {
|
|
|
|
$languageButtonData['class'] = ' mw-portlet-empty';
|
|
|
|
}
|
|
|
|
|
2021-09-07 20:22:19 +00:00
|
|
|
return $languageButtonData;
|
2021-03-18 11:58:55 +00:00
|
|
|
}
|
|
|
|
|
2014-08-07 11:38:34 +00:00
|
|
|
/**
|
2020-09-04 19:18:57 +00:00
|
|
|
* helper for applying Vector menu classes to portlets
|
2021-08-26 19:47:02 +00:00
|
|
|
*
|
2020-09-04 19:18:57 +00:00
|
|
|
* @param array $portletData returned by SkinMustache to decorate
|
|
|
|
* @param int $type representing one of the menu types (see MENU_TYPE_* constants)
|
|
|
|
* @return array modified version of portletData input
|
2014-08-07 11:38:34 +00:00
|
|
|
*/
|
2020-09-04 19:18:57 +00:00
|
|
|
private function decoratePortletClass(
|
|
|
|
array $portletData,
|
|
|
|
int $type = self::MENU_TYPE_DEFAULT
|
|
|
|
) {
|
|
|
|
$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',
|
2020-01-10 15:00:54 +00:00
|
|
|
];
|
2021-02-04 02:23:19 +00:00
|
|
|
$portletData['heading-class'] = 'vector-menu-heading';
|
2021-04-30 02:54:54 +00:00
|
|
|
// Add target class to apply different icon to personal menu dropdown for logged in users.
|
2021-05-14 15:25:48 +00:00
|
|
|
if ( $portletData['id'] === 'p-personal' ) {
|
2021-08-24 21:34:59 +00:00
|
|
|
if ( $this->isLegacy() ) {
|
|
|
|
$portletData['class'] .= ' vector-user-menu-legacy';
|
|
|
|
} else {
|
2021-05-14 15:25:48 +00:00
|
|
|
$portletData['class'] .= ' vector-user-menu';
|
2021-06-24 19:13:20 +00:00
|
|
|
$portletData['class'] .= $this->loggedin ?
|
|
|
|
' vector-user-menu-logged-in' :
|
|
|
|
' vector-user-menu-logged-out';
|
2021-12-15 22:25:11 +00:00
|
|
|
$portletData['heading-class'] .= ' ' . self::CLASS_QUIET_BUTTON . ' ' .
|
|
|
|
self::CLASS_ICON_BUTTON . ' ';
|
2021-06-24 19:13:20 +00:00
|
|
|
$portletData['heading-class'] .= $this->loggedin ?
|
2022-02-06 22:48:51 +00:00
|
|
|
$this->iconClass( 'userAvatar' ) :
|
|
|
|
$this->iconClass( 'ellipsis' );
|
2021-05-14 15:25:48 +00:00
|
|
|
}
|
2021-04-30 02:54:54 +00:00
|
|
|
}
|
2021-08-26 19:47:02 +00:00
|
|
|
switch ( $portletData['id'] ) {
|
|
|
|
case 'p-variants':
|
|
|
|
case 'p-cactions':
|
|
|
|
$portletData['class'] .= ' vector-menu-dropdown-noicon';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-12-03 23:33:21 +00:00
|
|
|
if ( $portletData['id'] === 'p-lang' && $this->isLanguagesInContent() ) {
|
2021-09-07 20:22:19 +00:00
|
|
|
$portletData = array_merge( $portletData, $this->getULSPortletData() );
|
2021-02-01 22:51:28 +00:00
|
|
|
}
|
2020-09-04 19:18:57 +00:00
|
|
|
$class = $portletData['class'];
|
|
|
|
$portletData['class'] = trim( "$class $extraClasses[$type]" );
|
|
|
|
return $portletData;
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
|
|
|
|
2020-01-14 18:47:30 +00:00
|
|
|
/**
|
2022-01-18 21:45:46 +00:00
|
|
|
* Performs updates to all portlets.
|
|
|
|
*
|
|
|
|
* @param array $data
|
2020-02-14 20:33:54 +00:00
|
|
|
* @return array
|
2020-01-14 18:47:30 +00:00
|
|
|
*/
|
2022-01-18 21:45:46 +00:00
|
|
|
private function decoratePortletsData( array $data ) {
|
|
|
|
foreach ( $data['data-portlets'] as $key => $pData ) {
|
|
|
|
$data['data-portlets'][$key] = $this->decoratePortletData(
|
|
|
|
$key,
|
|
|
|
$pData
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$sidebar = $data['data-portlets-sidebar'];
|
|
|
|
$sidebar['data-portlets-first'] = $this->decoratePortletData(
|
|
|
|
'navigation', $sidebar['data-portlets-first']
|
|
|
|
);
|
|
|
|
$rest = $sidebar['array-portlets-rest'];
|
|
|
|
foreach ( $rest as $key => $pData ) {
|
|
|
|
$rest[$key] = $this->decoratePortletData(
|
|
|
|
$pData['id'], $pData
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$sidebar['array-portlets-rest'] = $rest;
|
|
|
|
$data['data-portlets-sidebar'] = $sidebar;
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs the following updates to portlet data:
|
|
|
|
* - Adds concept of menu types
|
|
|
|
* - Marks the selected variant in the variant portlet
|
|
|
|
* - modifies tooltips of personal and user-menu portlets
|
|
|
|
* @param string $key
|
|
|
|
* @param array $portletData
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function decoratePortletData(
|
|
|
|
string $key,
|
|
|
|
array $portletData
|
2021-07-23 19:53:14 +00:00
|
|
|
): array {
|
2022-01-18 21:45:46 +00:00
|
|
|
switch ( $key ) {
|
|
|
|
case 'data-user-menu':
|
|
|
|
case 'data-actions':
|
|
|
|
case 'data-variants':
|
2020-09-04 19:18:57 +00:00
|
|
|
$type = self::MENU_TYPE_DROPDOWN;
|
|
|
|
break;
|
2022-01-18 21:45:46 +00:00
|
|
|
case 'data-views':
|
|
|
|
case 'data-namespaces':
|
2020-09-04 19:18:57 +00:00
|
|
|
$type = self::MENU_TYPE_TABS;
|
|
|
|
break;
|
2022-01-18 21:45:46 +00:00
|
|
|
case 'data-notifications':
|
|
|
|
case 'data-personal':
|
|
|
|
case 'data-user-page':
|
2020-09-04 19:18:57 +00:00
|
|
|
$type = self::MENU_TYPE_DEFAULT;
|
|
|
|
break;
|
2022-01-18 21:45:46 +00:00
|
|
|
case 'data-languages':
|
2021-12-03 23:33:21 +00:00
|
|
|
$type = $this->isLanguagesInContent() ?
|
2020-08-18 21:30:25 +00:00
|
|
|
self::MENU_TYPE_DROPDOWN : self::MENU_TYPE_PORTAL;
|
|
|
|
break;
|
2020-09-04 19:18:57 +00:00
|
|
|
default:
|
|
|
|
$type = self::MENU_TYPE_PORTAL;
|
|
|
|
break;
|
|
|
|
}
|
2020-04-03 20:05:22 +00:00
|
|
|
|
2020-09-04 19:18:57 +00:00
|
|
|
$portletData = $this->decoratePortletClass(
|
2022-01-18 21:45:46 +00:00
|
|
|
$portletData,
|
2020-09-04 19:18:57 +00:00
|
|
|
$type
|
|
|
|
);
|
2019-11-18 20:23:48 +00:00
|
|
|
|
2020-09-23 22:29:30 +00:00
|
|
|
// Special casing for Variant to change label to selected.
|
|
|
|
// Hopefully we can revisit and possibly remove this code when the language switcher is moved.
|
2022-01-18 21:45:46 +00:00
|
|
|
if ( $key === 'data-variants' ) {
|
|
|
|
$languageConverterFactory = MediaWikiServices::getInstance()->getLanguageConverterFactory();
|
|
|
|
$pageLang = $this->getTitle()->getPageLanguage();
|
|
|
|
$converter = $languageConverterFactory->getLanguageConverter( $pageLang );
|
|
|
|
$portletData['label'] = $pageLang->getVariantname(
|
|
|
|
$converter->getPreferredVariant()
|
|
|
|
);
|
2021-09-14 22:56:54 +00:00
|
|
|
// T289523 Add aria-label data to the language variant switcher.
|
|
|
|
$portletData['aria-label'] = $this->msg( 'vector-language-variant-switcher-label' );
|
2020-04-03 20:05:22 +00:00
|
|
|
}
|
2020-04-07 23:21:20 +00:00
|
|
|
|
2021-08-25 15:24:58 +00:00
|
|
|
// T287494 We use tooltip messages to provide title attributes on hover over certain menu icons. For modern
|
|
|
|
// Vector, the "tooltip-p-personal" key is set to "User menu" which is appropriate for the user icon (dropdown
|
|
|
|
// indicator for user links menu) for logged-in users. This overrides the tooltip for the user links menu icon
|
|
|
|
// which is an ellipsis for anonymous users.
|
2022-01-18 21:45:46 +00:00
|
|
|
if ( $key === 'data-user-menu' && !$this->isLegacy() && !$this->loggedin ) {
|
2021-08-25 15:24:58 +00:00
|
|
|
$portletData['html-tooltip'] = Linker::tooltip( 'vector-anon-user-menu-title' );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set tooltip to empty string for the personal menu for both logged-in and logged-out users to avoid showing
|
|
|
|
// the tooltip for legacy version.
|
2022-01-18 21:45:46 +00:00
|
|
|
if ( $key === 'data-personal' && $this->isLegacy() ) {
|
2021-08-25 15:24:58 +00:00
|
|
|
$portletData['html-tooltip'] = '';
|
|
|
|
}
|
|
|
|
|
2020-09-04 19:18:57 +00:00
|
|
|
return $portletData + [
|
|
|
|
'is-dropdown' => $type === self::MENU_TYPE_DROPDOWN,
|
2020-04-03 20:05:22 +00:00
|
|
|
];
|
2019-11-18 20:23:48 +00:00
|
|
|
}
|
2014-08-07 11:38:34 +00:00
|
|
|
}
|
2022-02-06 22:43:56 +00:00
|
|
|
|
|
|
|
class_alias( SkinVector::class, 'SkinVector' );
|