2017-07-12 15:12:40 +00:00
|
|
|
<?php
|
|
|
|
/**
|
2018-04-15 23:21:12 +00:00
|
|
|
* 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
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2018-04-15 23:21:12 +00:00
|
|
|
|
2022-03-04 16:40:54 +00:00
|
|
|
namespace MediaWiki\Minerva\Skins;
|
|
|
|
|
|
|
|
use ExtensionRegistry;
|
|
|
|
use Html;
|
|
|
|
use Language;
|
2022-12-12 22:13:05 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Controller\NotificationController;
|
2023-08-20 00:48:10 +00:00
|
|
|
use MediaWiki\Html\TemplateParser;
|
2020-03-04 02:53:53 +00:00
|
|
|
use MediaWiki\Linker\LinkTarget;
|
2017-07-12 15:12:40 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2019-08-02 09:00:18 +00:00
|
|
|
use MediaWiki\Minerva\Menu\Main\MainMenuDirector;
|
2022-03-04 16:40:54 +00:00
|
|
|
use MediaWiki\Minerva\Menu\PageActions\PageActionsDirector;
|
|
|
|
use MediaWiki\Minerva\Menu\User\UserMenuDirector;
|
2019-05-24 00:07:27 +00:00
|
|
|
use MediaWiki\Minerva\Permissions\IMinervaPagePermissions;
|
2019-04-08 17:08:57 +00:00
|
|
|
use MediaWiki\Minerva\SkinOptions;
|
2023-08-19 04:23:00 +00:00
|
|
|
use MediaWiki\Title\Title;
|
2022-03-04 16:40:54 +00:00
|
|
|
use MWTimestamp;
|
|
|
|
use RuntimeException;
|
|
|
|
use SkinMustache;
|
|
|
|
use SkinTemplate;
|
|
|
|
use SpecialMobileHistory;
|
|
|
|
use SpecialPage;
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Minerva: Born from the godhead of Jupiter with weapons!
|
|
|
|
* A skin that works on both desktop and mobile
|
|
|
|
* @ingroup Skins
|
|
|
|
*/
|
2020-10-02 20:03:14 +00:00
|
|
|
class SkinMinerva extends SkinMustache {
|
2017-08-22 14:12:32 +00:00
|
|
|
/** @const LEAD_SECTION_NUMBER integer which corresponds to the lead section
|
2020-01-26 19:26:31 +00:00
|
|
|
* in editing mode
|
|
|
|
*/
|
2020-05-19 22:43:34 +00:00
|
|
|
public const LEAD_SECTION_NUMBER = 0;
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2020-11-05 02:02:15 +00:00
|
|
|
/** @var string Name of this skin */
|
2017-07-12 15:12:40 +00:00
|
|
|
public $skinname = 'minerva';
|
2020-11-05 02:02:15 +00:00
|
|
|
/** @var string Name of this used template */
|
2017-07-12 15:12:40 +00:00
|
|
|
public $template = 'MinervaTemplate';
|
2019-04-10 12:52:34 +00:00
|
|
|
|
2020-01-26 19:26:31 +00:00
|
|
|
/** @var SkinOptions */
|
2019-04-10 21:43:50 +00:00
|
|
|
private $skinOptions;
|
|
|
|
|
2022-01-13 00:38:16 +00:00
|
|
|
/** @var array|null */
|
|
|
|
private $sidebarCachedResult;
|
|
|
|
|
2022-02-04 17:17:01 +00:00
|
|
|
/** @var array|null */
|
|
|
|
private $contentNavigationUrls;
|
|
|
|
|
2023-08-09 02:56:03 +00:00
|
|
|
/** @var TemplateParser|null */
|
|
|
|
private $templateParser;
|
|
|
|
|
2019-05-24 00:07:27 +00:00
|
|
|
/**
|
|
|
|
* This variable is lazy loaded, please use getPermissions() getter
|
|
|
|
* @see SkinMinerva::getPermissions()
|
|
|
|
* @var IMinervaPagePermissions
|
|
|
|
*/
|
|
|
|
private $permissions;
|
|
|
|
|
2019-04-10 21:43:50 +00:00
|
|
|
/**
|
2021-09-23 19:47:46 +00:00
|
|
|
* @return SkinOptions
|
|
|
|
*/
|
|
|
|
private function getSkinOptions() {
|
|
|
|
if ( !$this->skinOptions ) {
|
|
|
|
$this->skinOptions = MediaWikiServices::getInstance()->getService( 'Minerva.SkinOptions' );
|
|
|
|
}
|
|
|
|
return $this->skinOptions;
|
2019-04-10 21:43:50 +00:00
|
|
|
}
|
|
|
|
|
2021-10-19 18:16:43 +00:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasPageActions() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
return !$title->isSpecialPage() && !$title->isMainPage() &&
|
2022-04-15 21:05:22 +00:00
|
|
|
$this->getContext()->getActionName() === 'view';
|
2021-10-19 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2021-10-20 16:40:25 +00:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasSecondaryActions() {
|
|
|
|
return !$this->getUserPageHelper()->isUserPage();
|
|
|
|
}
|
|
|
|
|
2021-10-19 18:16:43 +00:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function isFallbackEditor() {
|
2023-04-17 16:54:48 +00:00
|
|
|
$action = $this->getContext()->getActionName();
|
2021-10-19 18:16:43 +00:00
|
|
|
return $action === 'edit';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-10-19 18:49:54 +00:00
|
|
|
* Returns available page actions if the page has any.
|
|
|
|
*
|
2021-11-12 19:51:08 +00:00
|
|
|
* @param array $nav result of SkinTemplate::buildContentNavigationUrls
|
2021-10-19 18:49:54 +00:00
|
|
|
* @return array|null
|
2021-10-19 18:16:43 +00:00
|
|
|
*/
|
2021-11-12 19:51:08 +00:00
|
|
|
private function getPageActions( array $nav ) {
|
|
|
|
if ( $this->isFallbackEditor() || !$this->hasPageActions() ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$services = MediaWikiServices::getInstance();
|
2022-03-04 16:40:54 +00:00
|
|
|
/** @var PageActionsDirector $pageActionsDirector */
|
2021-11-12 19:51:08 +00:00
|
|
|
$pageActionsDirector = $services->getService( 'Minerva.Menu.PageActionsDirector' );
|
2022-02-08 03:34:37 +00:00
|
|
|
$sidebar = $this->buildSidebar();
|
2021-11-12 19:51:08 +00:00
|
|
|
$actions = $nav['actions'] ?? [];
|
2023-09-27 00:17:21 +00:00
|
|
|
$views = $nav['views'] ?? [];
|
|
|
|
return $pageActionsDirector->buildMenu( $sidebar['TOOLBOX'], $actions, $views );
|
2021-10-19 18:16:43 +00:00
|
|
|
}
|
|
|
|
|
2022-02-08 16:41:14 +00:00
|
|
|
/**
|
|
|
|
* A notification icon that links to Special:Mytalk when Echo is not installed.
|
|
|
|
* Consider upstreaming this to core or removing at a future date.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getNotificationFallbackButton() {
|
|
|
|
return [
|
2023-09-05 17:07:13 +00:00
|
|
|
'icon' => 'bellOutline-base20',
|
2023-08-01 00:12:59 +00:00
|
|
|
'href' => SpecialPage::getTitleFor( 'Mytalk' )->getLocalURL(
|
|
|
|
[ 'returnto' => $this->getTitle()->getPrefixedText() ]
|
|
|
|
),
|
2022-02-08 16:41:14 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2022-12-12 22:13:05 +00:00
|
|
|
/**
|
|
|
|
* @param array $alert
|
|
|
|
* @param array $notice
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getCombinedNotificationButton( array $alert, array $notice ) {
|
|
|
|
// Sum the notifications from the two original buttons
|
|
|
|
$notifCount = ( $alert['data']['counter-num'] ?? 0 ) + ( $notice['data']['counter-num'] ?? 0 );
|
|
|
|
$alert['data']['counter-num'] = $notifCount;
|
|
|
|
// @phan-suppress-next-line PhanUndeclaredClassReference
|
|
|
|
if ( class_exists( NotificationController::class ) ) {
|
|
|
|
// @phan-suppress-next-line PhanUndeclaredClassMethod
|
|
|
|
$alert['data']['counter-text'] = NotificationController::formatNotificationCount( $notifCount );
|
|
|
|
} else {
|
|
|
|
$alert['data']['counter-text'] = $notifCount;
|
|
|
|
}
|
|
|
|
|
2022-12-13 01:58:09 +00:00
|
|
|
$linkClassAlert = $alert['link-class'] ?? [];
|
|
|
|
$hasUnseenAlerts = is_array( $linkClassAlert ) && in_array( 'mw-echo-unseen-notifications', $linkClassAlert );
|
|
|
|
// The circle should only appear if there are unseen notifications.
|
|
|
|
// Once the notifications are seen (by opening the notification drawer)
|
|
|
|
// then the icon reverts to a gray circle, but on page refresh
|
|
|
|
// it should revert back to a bell icon.
|
|
|
|
// If you try and change this behaviour, at time of writing
|
|
|
|
// (December 2022) JavaScript will correct it.
|
|
|
|
if ( $notifCount > 0 && $hasUnseenAlerts ) {
|
|
|
|
$linkClass = $notice['link-class'] ?? [];
|
|
|
|
$hasUnseenNotices = is_array( $linkClass ) && in_array( 'mw-echo-unseen-notifications', $linkClass );
|
|
|
|
return $this->getNotificationCircleButton( $alert, $hasUnseenNotices );
|
2022-12-12 22:13:05 +00:00
|
|
|
} else {
|
|
|
|
return $this->getNotificationButton( $alert );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 16:41:14 +00:00
|
|
|
/**
|
|
|
|
* Minerva differs from other skins in that for users with unread notifications
|
|
|
|
* instead of a bell with a small square indicating the number of notifications
|
|
|
|
* it shows a red circle with a number inside. Ideally Vector and Minerva would
|
|
|
|
* be treated the same but we'd need to talk to a designer about consolidating these
|
|
|
|
* before making such a decision.
|
|
|
|
*
|
|
|
|
* @param array $alert
|
2022-12-13 01:58:09 +00:00
|
|
|
* @param bool $hasUnseenNotices does the user have unseen notices?
|
2022-02-08 16:41:14 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2022-12-13 01:58:09 +00:00
|
|
|
private function getNotificationCircleButton( array $alert, bool $hasUnseenNotices ) {
|
2022-02-08 16:41:14 +00:00
|
|
|
$alertCount = $alert['data']['counter-num'] ?? 0;
|
2022-12-13 01:58:09 +00:00
|
|
|
$linkClass = $alert['link-class'] ?? [];
|
|
|
|
$hasSeenAlerts = is_array( $linkClass ) && in_array( 'mw-echo-unseen-notifications', $linkClass );
|
2022-06-13 18:42:27 +00:00
|
|
|
$alertText = $alert['data']['counter-text'] ?? $alertCount;
|
2023-08-01 00:12:59 +00:00
|
|
|
$alert['icon'] = 'circle';
|
2022-12-13 01:58:09 +00:00
|
|
|
$alert['class'] = 'notification-count';
|
|
|
|
if ( $hasSeenAlerts || $hasUnseenNotices ) {
|
|
|
|
$alert['class'] .= ' notification-unseen mw-echo-unseen-notifications';
|
|
|
|
}
|
2022-02-08 16:41:14 +00:00
|
|
|
return $alert;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the OOUI icon class and adds Minerva notification classes.
|
|
|
|
*
|
|
|
|
* @param array $alert
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getNotificationButton( array $alert ) {
|
|
|
|
$linkClass = $alert['link-class'];
|
|
|
|
$alert['link-class'] = array_filter(
|
|
|
|
$linkClass,
|
|
|
|
static function ( $class ) {
|
|
|
|
return $class !== 'oo-ui-icon-bellOutline';
|
|
|
|
}
|
|
|
|
);
|
2023-09-05 17:07:13 +00:00
|
|
|
$alert['icon'] = 'bellOutline-base20';
|
2022-02-08 16:41:14 +00:00
|
|
|
return $alert;
|
|
|
|
}
|
|
|
|
|
2022-02-04 17:17:01 +00:00
|
|
|
/**
|
|
|
|
* Caches content navigation urls locally for use inside getTemplateData
|
|
|
|
*
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
protected function runOnSkinTemplateNavigationHooks( SkinTemplate $skin, &$contentNavigationUrls ) {
|
|
|
|
parent::runOnSkinTemplateNavigationHooks( $skin, $contentNavigationUrls );
|
|
|
|
// There are some SkinTemplate modifications that occur after the execution of this hook
|
|
|
|
// to add rel attributes and ID attributes.
|
|
|
|
// The only one Minerva needs is this one so we manually add it.
|
2022-09-23 16:10:21 +00:00
|
|
|
foreach ( array_keys( $contentNavigationUrls['associated-pages'] ) as $id ) {
|
2022-02-04 17:17:01 +00:00
|
|
|
if ( in_array( $id, [ 'user_talk', 'talk' ] ) ) {
|
2022-09-23 16:10:21 +00:00
|
|
|
$contentNavigationUrls['associated-pages'][ $id ]['rel'] = 'discussion';
|
2022-02-04 17:17:01 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-13 01:45:57 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2022-02-04 17:17:01 +00:00
|
|
|
$this->contentNavigationUrls = $contentNavigationUrls;
|
2023-08-01 00:12:59 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Echo Technical debt!!
|
|
|
|
// * Convert the Echo button into a single button
|
|
|
|
// * Switch out the icon.
|
|
|
|
//
|
2022-02-08 16:41:14 +00:00
|
|
|
if ( $this->getUser()->isRegistered() ) {
|
|
|
|
if ( count( $contentNavigationUrls['notifications'] ) === 0 ) {
|
2022-12-12 22:13:05 +00:00
|
|
|
// Shown to logged in users when Echo is not installed:
|
2022-02-08 16:41:14 +00:00
|
|
|
$contentNavigationUrls['notifications']['mytalks'] = $this->getNotificationFallbackButton();
|
2022-12-13 01:45:57 +00:00
|
|
|
} elseif ( $skinOptions->get( SkinOptions::SINGLE_ECHO_BUTTON ) ) {
|
2022-12-12 22:13:05 +00:00
|
|
|
// Combine notification icons. Minerva only shows one entry point to notifications.
|
|
|
|
// This can be reconsidered with a solution to https://phabricator.wikimedia.org/T142981
|
2022-02-08 16:41:14 +00:00
|
|
|
$alert = $contentNavigationUrls['notifications']['notifications-alert'] ?? null;
|
2022-12-12 22:13:05 +00:00
|
|
|
$notice = $contentNavigationUrls['notifications']['notifications-notice'] ?? null;
|
|
|
|
if ( $alert && $notice ) {
|
|
|
|
unset( $contentNavigationUrls['notifications']['notifications-notice'] );
|
|
|
|
$contentNavigationUrls['notifications']['notifications-alert'] =
|
|
|
|
$this->getCombinedNotificationButton( $alert, $notice );
|
2022-02-08 16:41:14 +00:00
|
|
|
}
|
2022-12-13 01:45:57 +00:00
|
|
|
} else {
|
|
|
|
// Show desktop alert icon.
|
|
|
|
$alert = $contentNavigationUrls['notifications']['notifications-alert'] ?? null;
|
|
|
|
if ( $alert ) {
|
|
|
|
// Correct the icon to be the bell filled rather than the outline to match
|
|
|
|
// Echo's badge.
|
|
|
|
$linkClass = $alert['link-class'] ?? [];
|
2023-08-01 00:12:59 +00:00
|
|
|
$alert['link-class'] = array_filter( $linkClass, static function ( $class ) {
|
|
|
|
return $class !== 'oo-ui-icon-bellOutline';
|
|
|
|
} );
|
2022-12-13 01:45:57 +00:00
|
|
|
$contentNavigationUrls['notifications']['notifications-alert'] = $alert;
|
|
|
|
}
|
2022-02-08 16:41:14 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-04 17:17:01 +00:00
|
|
|
}
|
|
|
|
|
2020-10-02 20:03:14 +00:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2021-11-12 19:51:08 +00:00
|
|
|
public function getTemplateData(): array {
|
2020-10-02 20:03:14 +00:00
|
|
|
$data = parent::getTemplateData();
|
2022-12-13 01:45:57 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2021-11-12 19:51:08 +00:00
|
|
|
// FIXME: Can we use $data instead of calling buildContentNavigationUrls ?
|
2022-02-04 17:17:01 +00:00
|
|
|
$nav = $this->contentNavigationUrls;
|
|
|
|
if ( $nav === null ) {
|
|
|
|
throw new RuntimeException( 'contentNavigationUrls was not set as expected.' );
|
|
|
|
}
|
2021-04-28 21:58:51 +00:00
|
|
|
if ( !$this->hasCategoryLinks() ) {
|
|
|
|
unset( $data['html-categories'] );
|
|
|
|
}
|
|
|
|
|
2021-11-05 18:21:09 +00:00
|
|
|
// Special handling for certain pages.
|
|
|
|
// This is technical debt that should be upstreamed to core.
|
|
|
|
$isUserPage = $this->getUserPageHelper()->isUserPage();
|
|
|
|
$isUserPageAccessible = $this->getUserPageHelper()->isUserPageAccessibleToCurrentUser();
|
|
|
|
if ( $isUserPage && $isUserPageAccessible ) {
|
|
|
|
$data['html-title-heading'] = $this->getUserPageHeadingHtml( $data['html-title-heading' ] );
|
|
|
|
}
|
|
|
|
|
2022-02-02 23:19:57 +00:00
|
|
|
$usermessage = $data['html-user-message'] ?? '';
|
|
|
|
if ( $usermessage ) {
|
|
|
|
$data['html-user-message'] = Html::warningBox(
|
2023-09-05 17:07:13 +00:00
|
|
|
'<span class="minerva-icon minerva-icon--userTalk-warning"></span> '
|
2022-02-02 23:19:57 +00:00
|
|
|
. $usermessage,
|
|
|
|
'minerva-anon-talk-message'
|
|
|
|
);
|
|
|
|
}
|
2023-01-12 17:17:15 +00:00
|
|
|
$allLanguages = $data['data-portlets']['data-languages']['array-items'] ?? [];
|
|
|
|
$allVariants = $data['data-portlets']['data-variants']['array-items'] ?? [];
|
2023-08-01 00:12:59 +00:00
|
|
|
$notifications = $data['data-portlets']['data-notifications']['array-items'] ?? [];
|
2023-01-12 17:17:15 +00:00
|
|
|
|
2021-11-10 23:58:07 +00:00
|
|
|
return $data + [
|
2023-01-12 17:17:15 +00:00
|
|
|
'has-minerva-languages' => !empty( $allLanguages ) || !empty( $allVariants ),
|
2021-11-10 23:58:07 +00:00
|
|
|
'array-minerva-banners' => $this->prepareBanners( $data['html-site-notice'] ),
|
2023-07-24 22:12:24 +00:00
|
|
|
'data-minerva-search-box' => $data['data-search-box'] + [
|
|
|
|
'data-btn' => [
|
|
|
|
'data-icon' => [
|
2023-09-05 17:07:13 +00:00
|
|
|
'icon' => 'search-base20',
|
2023-07-24 22:12:24 +00:00
|
|
|
],
|
|
|
|
'label' => $this->msg( 'searchbutton' )->escaped(),
|
|
|
|
'classes' => 'skin-minerva-search-trigger',
|
2023-08-01 00:12:59 +00:00
|
|
|
'array-attributes' => [
|
2023-07-24 22:12:24 +00:00
|
|
|
[
|
|
|
|
'key' => 'id',
|
|
|
|
'value' => 'searchIcon',
|
|
|
|
]
|
|
|
|
]
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'data-minerva-main-menu-btn' => [
|
|
|
|
'data-icon' => [
|
2023-09-05 17:07:13 +00:00
|
|
|
'icon' => 'menu-base20',
|
2023-07-24 22:12:24 +00:00
|
|
|
],
|
|
|
|
'tag-name' => 'label',
|
2023-08-09 21:49:19 +00:00
|
|
|
'classes' => 'toggle-list__toggle',
|
2023-08-01 00:12:59 +00:00
|
|
|
'array-attributes' => [
|
2023-07-24 22:12:24 +00:00
|
|
|
[
|
|
|
|
'key' => 'for',
|
|
|
|
'value' => 'main-menu-input',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'id',
|
|
|
|
'value' => 'mw-mf-main-menu-button',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'aria-hidden',
|
|
|
|
'value' => 'true',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'data-event-name',
|
|
|
|
'value' => 'ui.mainmenu',
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'text' => $this->msg( 'mobile-frontend-main-menu-button-tooltip' )->escaped(),
|
|
|
|
],
|
2022-01-13 00:38:16 +00:00
|
|
|
'data-minerva-main-menu' => $this->getMainMenu()->getMenuData(
|
|
|
|
$nav,
|
2022-02-08 03:34:37 +00:00
|
|
|
$this->buildSidebar()
|
2022-01-13 00:38:16 +00:00
|
|
|
)['items'],
|
2021-11-10 23:58:07 +00:00
|
|
|
'html-minerva-tagline' => $this->getTaglineHtml(),
|
2022-01-13 00:38:16 +00:00
|
|
|
'html-minerva-user-menu' => $this->getPersonalToolsMenu( $nav['user-menu'] ),
|
2021-11-10 23:58:07 +00:00
|
|
|
'is-minerva-beta' => $this->getSkinOptions()->get( SkinOptions::BETA_MODE ),
|
2023-08-01 00:12:59 +00:00
|
|
|
'data-minerva-notifications' => $notifications ? [
|
|
|
|
'array-buttons' => $this->getNotificationButtons( $notifications ),
|
|
|
|
] : null,
|
2021-11-12 19:51:08 +00:00
|
|
|
'data-minerva-tabs' => $this->getTabsData( $nav ),
|
|
|
|
'data-minerva-page-actions' => $this->getPageActions( $nav ),
|
|
|
|
'data-minerva-secondary-actions' => $this->getSecondaryActions( $nav ),
|
2021-10-04 22:03:29 +00:00
|
|
|
'html-minerva-subject-link' => $this->getSubjectPage(),
|
2021-10-29 19:47:02 +00:00
|
|
|
'data-minerva-history-link' => $this->getHistoryLink( $this->getTitle() ),
|
2021-10-04 22:03:29 +00:00
|
|
|
];
|
2020-10-02 20:03:14 +00:00
|
|
|
}
|
|
|
|
|
2023-08-01 00:12:59 +00:00
|
|
|
/**
|
|
|
|
* Prepares the notification badges for the Button template.
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
* @param array $notifications
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function getNotificationButtons( array $notifications ) {
|
|
|
|
$btns = [];
|
|
|
|
|
|
|
|
foreach ( $notifications as $notification ) {
|
|
|
|
$linkData = $notification['array-links'][ 0 ] ?? [];
|
|
|
|
$icon = $linkData['icon'] ?? null;
|
|
|
|
if ( !$icon ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$id = $notification['id'] ?? null;
|
|
|
|
$classes = '';
|
|
|
|
$attributes = [];
|
|
|
|
|
|
|
|
// We don't want to output multiple attributes.
|
|
|
|
// Iterate through the attributes and pull out ID and class which
|
|
|
|
// will be defined separately.
|
|
|
|
foreach ( $linkData[ 'array-attributes' ] as $keyValuePair ) {
|
|
|
|
if ( $keyValuePair['key'] === 'class' ) {
|
|
|
|
$classes = $keyValuePair['value'];
|
|
|
|
} elseif ( $keyValuePair['key'] === 'id' ) {
|
|
|
|
// ignore. We want to use the LI `id` instead.
|
|
|
|
} else {
|
|
|
|
$attributes[] = $keyValuePair;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// add LI ID to end for use on the button.
|
|
|
|
if ( $id ) {
|
|
|
|
$attributes[] = [
|
|
|
|
'key' => 'id',
|
|
|
|
'value' => $id,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
$btns[] = [
|
|
|
|
'tag-name' => 'a',
|
|
|
|
// FIXME: Move preg_replace when Echo no longer provides this class.
|
|
|
|
'classes' => preg_replace( '/oo-ui-icon-(bellOutline|tray)/', '', $classes ),
|
|
|
|
'array-attributes' => $attributes,
|
|
|
|
'data-icon' => [
|
|
|
|
'icon' => $icon,
|
|
|
|
],
|
|
|
|
'label' => $linkData['text'] ?? '',
|
|
|
|
];
|
|
|
|
}
|
|
|
|
return $btns;
|
|
|
|
}
|
|
|
|
|
2021-10-19 18:16:43 +00:00
|
|
|
/**
|
|
|
|
* Tabs are available if a page has page actions but is not the talk page of
|
|
|
|
* the main page.
|
|
|
|
*
|
|
|
|
* Special pages have tabs if SkinOptions::TABS_ON_SPECIALS is enabled.
|
|
|
|
* This is used by Extension:GrowthExperiments
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasPageTabs() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
$skinOptions = $this->getSkinOptions();
|
|
|
|
$isSpecialPage = $title->isSpecialPage();
|
|
|
|
$subjectPage = MediaWikiServices::getInstance()->getNamespaceInfo()
|
|
|
|
->getSubjectPage( $title );
|
|
|
|
$isMainPageTalk = Title::newFromLinkTarget( $subjectPage )->isMainPage();
|
|
|
|
return (
|
2022-04-08 19:21:50 +00:00
|
|
|
$this->hasPageActions() && !$isMainPageTalk &&
|
|
|
|
$skinOptions->get( SkinOptions::TALK_AT_TOP )
|
2021-10-19 18:16:43 +00:00
|
|
|
) || (
|
|
|
|
$isSpecialPage &&
|
|
|
|
$skinOptions->get( SkinOptions::TABS_ON_SPECIALS )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-12 19:51:08 +00:00
|
|
|
* @param array $contentNavigationUrls
|
2021-10-19 18:16:43 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2021-11-12 19:51:08 +00:00
|
|
|
private function getTabsData( array $contentNavigationUrls ) {
|
2022-04-08 19:21:50 +00:00
|
|
|
$hasPageTabs = $this->hasPageTabs();
|
|
|
|
if ( !$hasPageTabs ) {
|
2021-10-19 18:16:43 +00:00
|
|
|
return [];
|
|
|
|
}
|
2021-11-12 19:51:08 +00:00
|
|
|
return $contentNavigationUrls ? [
|
2022-09-23 16:10:21 +00:00
|
|
|
'items' => array_values( $contentNavigationUrls['associated-pages'] ),
|
2021-10-19 18:16:43 +00:00
|
|
|
] : [];
|
|
|
|
}
|
|
|
|
|
2019-05-24 00:07:27 +00:00
|
|
|
/**
|
|
|
|
* Lazy load the permissions object. We don't want to initialize it as it requires many
|
|
|
|
* dependencies, sometimes some of those dependencies cannot be fulfilled (like missing Title
|
|
|
|
* object)
|
|
|
|
* @return IMinervaPagePermissions
|
|
|
|
*/
|
2019-06-21 16:52:22 +00:00
|
|
|
private function getPermissions(): IMinervaPagePermissions {
|
2019-05-24 00:07:27 +00:00
|
|
|
if ( $this->permissions === null ) {
|
|
|
|
$this->permissions = MediaWikiServices::getInstance()
|
2019-10-08 17:22:31 +00:00
|
|
|
->getService( 'Minerva.Permissions' )
|
|
|
|
->setContext( $this->getContext() );
|
2019-05-24 00:07:27 +00:00
|
|
|
}
|
|
|
|
return $this->permissions;
|
|
|
|
}
|
|
|
|
|
2019-04-08 17:08:57 +00:00
|
|
|
/**
|
|
|
|
* Initalized main menu. Please use getter.
|
2019-07-14 14:44:29 +00:00
|
|
|
* @var MainMenuDirector
|
2019-04-08 17:08:57 +00:00
|
|
|
*/
|
|
|
|
private $mainMenu;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build the Main Menu Director by passing the skin options
|
|
|
|
*
|
|
|
|
* @return MainMenuDirector
|
|
|
|
*/
|
2019-06-21 16:52:22 +00:00
|
|
|
protected function getMainMenu(): MainMenuDirector {
|
2019-04-08 17:08:57 +00:00
|
|
|
if ( !$this->mainMenu ) {
|
|
|
|
$this->mainMenu = MediaWikiServices::getInstance()->getService( 'Minerva.Menu.MainDirector' );
|
|
|
|
}
|
|
|
|
return $this->mainMenu;
|
|
|
|
}
|
|
|
|
|
2019-08-02 14:12:26 +00:00
|
|
|
/**
|
|
|
|
* Prepare all Minerva menus
|
2021-11-12 19:51:08 +00:00
|
|
|
*
|
2022-01-13 00:38:16 +00:00
|
|
|
* @param array $personalUrls result of SkinTemplate::buildPersonalUrls
|
2021-11-10 23:58:07 +00:00
|
|
|
* @return string|null
|
2019-08-02 14:12:26 +00:00
|
|
|
*/
|
2022-01-13 00:38:16 +00:00
|
|
|
private function getPersonalToolsMenu( array $personalUrls ) {
|
2019-08-02 14:12:26 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
2022-03-04 16:40:54 +00:00
|
|
|
/** @var UserMenuDirector $userMenuDirector */
|
2019-08-02 14:12:26 +00:00
|
|
|
$userMenuDirector = $services->getService( 'Minerva.Menu.UserMenuDirector' );
|
2022-01-13 00:38:16 +00:00
|
|
|
return $userMenuDirector->renderMenuData( $personalUrls );
|
2019-08-02 14:12:26 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
2021-10-04 22:03:29 +00:00
|
|
|
* @return string
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2021-10-04 22:03:29 +00:00
|
|
|
protected function getSubjectPage() {
|
2019-08-02 15:02:28 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
2017-07-12 15:12:40 +00:00
|
|
|
$title = $this->getTitle();
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// If it's a talk page, add a link to the main namespace page
|
2019-02-26 18:24:42 +00:00
|
|
|
// In AMC we do not need to do this as there is an easy way back to the article page
|
|
|
|
// via the talk/article tabs.
|
2021-09-23 19:47:46 +00:00
|
|
|
if ( $title->isTalkPage() && !$skinOptions->get( SkinOptions::TALK_AT_TOP ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
// if it's a talk page for which we have a special message, use it
|
|
|
|
switch ( $title->getNamespace() ) {
|
|
|
|
case NS_USER_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-userpage';
|
|
|
|
break;
|
|
|
|
case NS_PROJECT_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-projectpage';
|
|
|
|
break;
|
|
|
|
case NS_FILE_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-filepage';
|
|
|
|
break;
|
|
|
|
default: // generic (all other NS)
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-page';
|
|
|
|
}
|
2019-08-02 15:02:28 +00:00
|
|
|
$subjectPage = $services->getNamespaceInfo()->getSubjectPage( $title );
|
|
|
|
|
2021-10-04 22:03:29 +00:00
|
|
|
return MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
|
2019-08-02 15:02:28 +00:00
|
|
|
$subjectPage,
|
2017-07-12 15:12:40 +00:00
|
|
|
$this->msg( $msg, $title->getText() )->text(),
|
2021-11-05 00:52:30 +00:00
|
|
|
[
|
|
|
|
'data-event-name' => 'talk.returnto',
|
|
|
|
'class' => 'return-link'
|
|
|
|
]
|
2021-10-04 22:03:29 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return '';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides Skin::doEditSectionLink
|
2020-03-20 21:49:22 +00:00
|
|
|
* @param Title $nt The title being linked to (may not be the same as
|
|
|
|
* the current page, if the section is included from a template)
|
2017-07-12 15:12:40 +00:00
|
|
|
* @param string $section
|
|
|
|
* @param string|null $tooltip
|
2018-07-23 20:12:42 +00:00
|
|
|
* @param Language $lang
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-07-23 20:12:42 +00:00
|
|
|
public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
|
2019-08-27 20:27:57 +00:00
|
|
|
if ( $this->getPermissions()->isAllowed( IMinervaPagePermissions::EDIT_OR_CREATE ) &&
|
2019-05-24 00:07:27 +00:00
|
|
|
!$nt->isMainPage() ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$message = $this->msg( 'mobile-frontend-editor-edit' )->inLanguage( $lang )->text();
|
2018-09-21 01:38:10 +00:00
|
|
|
$html = Html::openElement( 'span', [ 'class' => 'mw-editsection' ] );
|
2023-08-09 02:56:03 +00:00
|
|
|
if ( !$this->templateParser ) {
|
|
|
|
$this->templateParser = new TemplateParser( __DIR__ );
|
|
|
|
}
|
|
|
|
$templateParser = $this->templateParser;
|
|
|
|
$html .= $templateParser->processTemplate( 'Button', [
|
|
|
|
'tag-name' => 'a',
|
|
|
|
'data-icon' => [
|
2023-09-05 17:07:13 +00:00
|
|
|
'icon' => 'edit-base20'
|
2023-08-09 02:56:03 +00:00
|
|
|
],
|
|
|
|
'array-attributes' => [
|
|
|
|
[
|
|
|
|
'key' => 'href',
|
|
|
|
'value' => $nt->getLocalURL( [ 'action' => 'edit', 'section' => $section ] ),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'title',
|
|
|
|
'value' => $this->msg( 'editsectionhint', $tooltip )->inLanguage( $lang )->text(),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'data-section',
|
|
|
|
'value' => $section,
|
|
|
|
],
|
|
|
|
],
|
|
|
|
'label' => $message,
|
2017-07-12 15:12:40 +00:00
|
|
|
// Note visibility of the edit section link button is controlled by .edit-page in ui.less so
|
|
|
|
// we default to enabled even though this may not be true.
|
2023-08-09 21:49:19 +00:00
|
|
|
'classes' => 'edit-page',
|
2023-08-09 02:56:03 +00:00
|
|
|
] );
|
2017-07-12 15:12:40 +00:00
|
|
|
$html .= Html::closeElement( 'span' );
|
|
|
|
return $html;
|
|
|
|
}
|
2018-06-18 19:33:03 +00:00
|
|
|
return '';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a title and returns classes to apply to the body tag
|
|
|
|
* @param Title $title
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getPageClasses( $title ) {
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2017-07-12 15:12:40 +00:00
|
|
|
$className = parent::getPageClasses( $title );
|
2021-09-23 19:47:46 +00:00
|
|
|
$className .= ' ' . ( $skinOptions->get( SkinOptions::BETA_MODE )
|
2019-04-10 21:43:50 +00:00
|
|
|
? 'beta' : 'stable' );
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
if ( $title->isMainPage() ) {
|
|
|
|
$className .= ' page-Main_Page ';
|
|
|
|
}
|
|
|
|
|
2020-12-18 03:09:15 +00:00
|
|
|
if ( $this->getUser()->isRegistered() ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$className .= ' is-authenticated';
|
|
|
|
}
|
2018-11-12 20:32:28 +00:00
|
|
|
// The new treatment should only apply to the main namespace
|
|
|
|
if (
|
|
|
|
$title->getNamespace() === NS_MAIN &&
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions->get( SkinOptions::PAGE_ISSUES )
|
2018-11-12 20:32:28 +00:00
|
|
|
) {
|
|
|
|
$className .= ' issues-group-B';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
return $className;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the output page contains category links and the category feature is enabled.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasCategoryLinks() {
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
|
|
|
if ( !$skinOptions->get( SkinOptions::CATEGORIES ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$categoryLinks = $this->getOutput()->getCategoryLinks();
|
|
|
|
|
|
|
|
if ( !count( $categoryLinks ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !empty( $categoryLinks['normal'] ) || !empty( $categoryLinks['hidden'] );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return SkinUserPageHelper
|
|
|
|
*/
|
|
|
|
public function getUserPageHelper() {
|
2019-04-10 12:52:34 +00:00
|
|
|
return MediaWikiServices::getInstance()->getService( 'Minerva.SkinUserPageHelper' );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
2017-11-22 18:55:24 +00:00
|
|
|
/**
|
|
|
|
* Get a history link which describes author and relative time of last edit
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
2020-01-04 12:24:24 +00:00
|
|
|
* @param string $timestamp
|
2017-11-22 18:55:24 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getRelativeHistoryLink( Title $title, $timestamp ) {
|
|
|
|
$user = $this->getUser();
|
2019-05-14 10:40:56 +00:00
|
|
|
$userDate = $this->getLanguage()->userDate( $timestamp, $user );
|
2017-11-22 18:55:24 +00:00
|
|
|
$text = $this->msg(
|
2019-05-14 10:40:56 +00:00
|
|
|
'minerva-last-modified-date', $userDate,
|
2017-11-22 18:55:24 +00:00
|
|
|
$this->getLanguage()->userTime( $timestamp, $user )
|
|
|
|
)->parse();
|
|
|
|
return [
|
|
|
|
// Use $edit['timestamp'] (Unix format) instead of $timestamp (MW format)
|
|
|
|
'data-timestamp' => wfTimestamp( TS_UNIX, $timestamp ),
|
|
|
|
'href' => $this->getHistoryUrl( $title ),
|
|
|
|
'text' => $text,
|
|
|
|
] + $this->getRevisionEditorData( $title );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a history link which makes no reference to user or last edited time
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getGenericHistoryLink( Title $title ) {
|
|
|
|
$text = $this->msg( 'mobile-frontend-history' )->plain();
|
|
|
|
return [
|
|
|
|
'href' => $this->getHistoryUrl( $title ),
|
|
|
|
'text' => $text,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the URL for the history page for the given title using Special:History
|
|
|
|
* when available.
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getHistoryUrl( Title $title ) {
|
2019-04-04 16:03:48 +00:00
|
|
|
return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) &&
|
|
|
|
SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->getUser() ) ?
|
2017-11-22 18:55:24 +00:00
|
|
|
SpecialPage::getTitleFor( 'History', $title )->getLocalURL() :
|
|
|
|
$title->getLocalURL( [ 'action' => 'history' ] );
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Prepare the content for the 'last edited' message, e.g. 'Last edited on 30 August
|
|
|
|
* 2013, at 23:31'. This message is different for the main page since main page
|
2019-01-11 12:13:49 +00:00
|
|
|
* content is typically transcluded rather than edited directly.
|
2019-08-02 13:38:18 +00:00
|
|
|
*
|
|
|
|
* The relative time is only rendered on the latest revision.
|
|
|
|
* For older revisions the last modified information will not render with a relative time
|
|
|
|
* nor will it show the name of the editor.
|
2017-07-12 15:12:40 +00:00
|
|
|
* @param Title $title The Title object of the page being viewed
|
2021-10-29 19:47:02 +00:00
|
|
|
* @return array|null
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2017-11-22 18:55:24 +00:00
|
|
|
protected function getHistoryLink( Title $title ) {
|
2022-03-31 09:01:04 +00:00
|
|
|
if ( !$title->exists() ||
|
2022-04-15 21:05:22 +00:00
|
|
|
$this->getContext()->getActionName() !== 'view'
|
2022-03-31 09:01:04 +00:00
|
|
|
) {
|
|
|
|
return null;
|
2017-11-16 23:21:05 +00:00
|
|
|
}
|
2021-11-29 20:34:09 +00:00
|
|
|
|
2022-03-31 09:01:04 +00:00
|
|
|
$out = $this->getOutput();
|
2021-11-29 20:34:09 +00:00
|
|
|
|
2022-06-28 16:10:35 +00:00
|
|
|
if ( !$out->getRevisionId() || !$out->isRevisionCurrent() || $title->isMainPage() ) {
|
2022-03-31 09:01:04 +00:00
|
|
|
$historyLink = $this->getGenericHistoryLink( $title );
|
|
|
|
} else {
|
|
|
|
// Get rev_timestamp of current revision (preloaded by MediaWiki core)
|
|
|
|
$timestamp = $out->getRevisionTimestamp();
|
|
|
|
if ( !$timestamp ) {
|
|
|
|
# No cached timestamp, load it from the database
|
|
|
|
$revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup();
|
|
|
|
$timestamp = $revisionLookup->getTimestampFromId( $out->getRevisionId() );
|
|
|
|
}
|
|
|
|
$historyLink = $this->getRelativeHistoryLink( $title, $timestamp );
|
2021-11-29 20:34:09 +00:00
|
|
|
}
|
2021-10-29 19:47:02 +00:00
|
|
|
|
2022-03-31 09:01:04 +00:00
|
|
|
return $historyLink + [
|
2023-07-19 11:00:13 +00:00
|
|
|
'historyIcon' => [
|
2023-09-21 22:11:03 +00:00
|
|
|
'icon' => 'modified-history',
|
2023-08-09 21:49:19 +00:00
|
|
|
'size' => 'medium'
|
2023-07-19 11:00:13 +00:00
|
|
|
],
|
|
|
|
'arrowIcon' => [
|
|
|
|
'icon' => 'expand',
|
2023-08-09 21:49:19 +00:00
|
|
|
'size' => 'small'
|
2023-07-19 11:00:13 +00:00
|
|
|
]
|
2022-03-31 09:01:04 +00:00
|
|
|
];
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2017-09-06 15:20:16 +00:00
|
|
|
|
|
|
|
/**
|
2017-11-22 18:55:24 +00:00
|
|
|
* Returns data attributes representing the editor for the current revision.
|
2020-03-04 02:53:53 +00:00
|
|
|
* @param LinkTarget $title The Title object of the page being viewed
|
2017-11-22 18:55:24 +00:00
|
|
|
* @return array representing user with name and gender fields. Empty if the editor no longer
|
|
|
|
* exists in the database or is hidden from public view.
|
2017-09-06 15:20:16 +00:00
|
|
|
*/
|
2020-03-04 02:53:53 +00:00
|
|
|
private function getRevisionEditorData( LinkTarget $title ) {
|
2021-11-23 09:18:46 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
$rev = $services->getRevisionLookup()
|
2020-03-04 02:53:53 +00:00
|
|
|
->getRevisionByTitle( $title );
|
2017-11-22 18:55:24 +00:00
|
|
|
$result = [];
|
|
|
|
if ( $rev ) {
|
2020-03-04 02:53:53 +00:00
|
|
|
$revUser = $rev->getUser();
|
2017-11-22 18:55:24 +00:00
|
|
|
// Note the user will only be returned if that information is public
|
2020-03-04 02:53:53 +00:00
|
|
|
if ( $revUser ) {
|
2017-11-22 18:55:24 +00:00
|
|
|
$editorName = $revUser->getName();
|
2021-11-23 09:18:46 +00:00
|
|
|
$editorGender = $services->getGenderCache()->getGenderOf( $revUser, __METHOD__ );
|
2017-11-22 18:55:24 +00:00
|
|
|
$result += [
|
|
|
|
'data-user-name' => $editorName,
|
|
|
|
'data-user-gender' => $editorGender,
|
|
|
|
];
|
|
|
|
}
|
2017-09-06 15:20:16 +00:00
|
|
|
}
|
2017-11-22 18:55:24 +00:00
|
|
|
return $result;
|
2017-09-06 15:20:16 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Returns the HTML representing the tagline
|
|
|
|
* @return string HTML for tagline
|
|
|
|
*/
|
|
|
|
protected function getTaglineHtml() {
|
2017-11-28 21:55:58 +00:00
|
|
|
$tagline = '';
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
if ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
$pageUser = $this->getUserPageHelper()->getPageUser();
|
|
|
|
$fromDate = $pageUser->getRegistration();
|
2021-04-29 20:24:13 +00:00
|
|
|
|
|
|
|
if ( $this->getUserPageHelper()->isUserPageAccessibleToCurrentUser() && is_string( $fromDate ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$fromDateTs = wfTimestamp( TS_UNIX, $fromDate );
|
2021-11-23 09:18:46 +00:00
|
|
|
$genderCache = MediaWikiServices::getInstance()->getGenderCache();
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// This is shown when js is disabled. js enhancement made due to caching
|
|
|
|
$tagline = $this->msg( 'mobile-frontend-user-page-member-since',
|
|
|
|
$this->getLanguage()->userDate( new MWTimestamp( $fromDateTs ), $this->getUser() ),
|
2018-09-04 05:58:45 +00:00
|
|
|
$pageUser )->text();
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// Define html attributes for usage with js enhancement (unix timestamp, gender)
|
|
|
|
$attrs = [ 'id' => 'tagline-userpage',
|
|
|
|
'data-userpage-registration-date' => $fromDateTs,
|
2021-11-23 09:18:46 +00:00
|
|
|
'data-userpage-gender' => $genderCache->getGenderOf( $pageUser, __METHOD__ ) ];
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
if ( $title ) {
|
2018-10-31 21:37:17 +00:00
|
|
|
$out = $this->getOutput();
|
|
|
|
$tagline = $out->getProperty( 'wgMFDescription' );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$attrs[ 'class' ] = 'tagline';
|
2017-11-28 21:55:58 +00:00
|
|
|
return Html::element( 'div', $attrs, $tagline );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2019-10-11 19:04:00 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Returns the HTML representing the heading.
|
2021-11-05 18:21:09 +00:00
|
|
|
*
|
|
|
|
* @param string $heading The heading suggested by core.
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return string HTML for header
|
|
|
|
*/
|
2021-11-05 18:21:09 +00:00
|
|
|
private function getUserPageHeadingHtml( $heading ) {
|
|
|
|
// The heading is just the username without namespace
|
|
|
|
// This is escaped as a precaution (user name should be safe).
|
|
|
|
return Html::rawElement( 'h1',
|
|
|
|
// These IDs and classes should match Skin::getTemplateData
|
|
|
|
[
|
|
|
|
'id' => 'firstHeading',
|
|
|
|
'class' => 'firstHeading mw-first-heading mw-minerva-user-heading',
|
|
|
|
],
|
|
|
|
htmlspecialchars(
|
|
|
|
$this->getUserPageHelper()->getPageUser()->getName()
|
|
|
|
)
|
|
|
|
);
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2019-06-21 20:07:11 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Load internal banner content to show in pre content in template
|
|
|
|
* Beware of HTML caching when using this function.
|
|
|
|
* Content set as "internalbanner"
|
2021-11-10 23:58:07 +00:00
|
|
|
* @param string $siteNotice HTML fragment
|
|
|
|
* @return array
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2021-11-10 23:58:07 +00:00
|
|
|
protected function prepareBanners( $siteNotice ) {
|
2021-12-10 11:11:35 +00:00
|
|
|
$banners = [];
|
|
|
|
if ( $siteNotice && $this->getConfig()->get( 'MinervaEnableSiteNotice' ) ) {
|
|
|
|
$banners[] = '<div id="siteNotice">' . $siteNotice . '</div>';
|
|
|
|
} else {
|
|
|
|
$banners[] = '<div id="siteNotice"></div>';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2021-11-10 23:58:07 +00:00
|
|
|
return $banners;
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with details for a language button.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getLanguageButton() {
|
|
|
|
return [
|
2023-08-01 00:12:59 +00:00
|
|
|
'array-attributes' => [
|
2023-07-24 22:12:24 +00:00
|
|
|
[
|
|
|
|
'key' => 'href',
|
|
|
|
'value' => '#p-lang'
|
|
|
|
]
|
2017-07-12 15:12:40 +00:00
|
|
|
],
|
2023-07-24 22:12:24 +00:00
|
|
|
'tag-name' => 'a',
|
|
|
|
'classes' => 'language-selector button',
|
2017-07-12 15:12:40 +00:00
|
|
|
'label' => $this->msg( 'mobile-frontend-language-article-heading' )->text()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with details for a talk button.
|
|
|
|
* @param Title $talkTitle Title object of the talk page
|
2019-10-17 22:46:07 +00:00
|
|
|
* @param string $label Button label
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2022-10-02 12:44:18 +00:00
|
|
|
protected function getTalkButton( $talkTitle, $label ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
return [
|
2023-08-01 00:12:59 +00:00
|
|
|
'array-attributes' => [
|
2023-07-24 22:12:24 +00:00
|
|
|
[
|
|
|
|
'key' => 'href',
|
|
|
|
'value' => $talkTitle->getLinkURL(),
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'key' => 'data-title',
|
|
|
|
'value' => $talkTitle->getFullText(),
|
|
|
|
]
|
2017-07-12 15:12:40 +00:00
|
|
|
],
|
2023-07-24 22:12:24 +00:00
|
|
|
'tag-name' => 'a',
|
|
|
|
'classes' => 'talk button',
|
2019-10-17 22:46:07 +00:00
|
|
|
'label' => $label,
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of links for page secondary actions
|
2021-11-12 19:51:08 +00:00
|
|
|
* @param array $contentNavigationUrls
|
2021-10-26 16:02:42 +00:00
|
|
|
* @return array|null
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2021-11-12 19:51:08 +00:00
|
|
|
protected function getSecondaryActions( array $contentNavigationUrls ) {
|
2021-10-26 16:02:42 +00:00
|
|
|
if ( $this->isFallbackEditor() || !$this->hasSecondaryActions() ) {
|
|
|
|
return null;
|
2021-10-20 16:40:25 +00:00
|
|
|
}
|
|
|
|
|
2019-08-02 15:02:28 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2019-08-02 15:02:28 +00:00
|
|
|
$namespaceInfo = $services->getNamespaceInfo();
|
2019-07-09 16:10:31 +00:00
|
|
|
/** @var \MediaWiki\Minerva\LanguagesHelper $languagesHelper */
|
2019-08-02 15:02:28 +00:00
|
|
|
$languagesHelper = $services->getService( 'Minerva.LanguagesHelper' );
|
2017-07-12 15:12:40 +00:00
|
|
|
$buttons = [];
|
|
|
|
// always add a button to link to the talk page
|
2019-10-17 22:46:07 +00:00
|
|
|
// it will link to the wikitext talk page
|
2017-07-12 15:12:40 +00:00
|
|
|
$title = $this->getTitle();
|
2019-08-02 15:02:28 +00:00
|
|
|
$subjectPage = Title::newFromLinkTarget( $namespaceInfo->getSubjectPage( $title ) );
|
2021-09-23 19:47:46 +00:00
|
|
|
$talkAtBottom = !$skinOptions->get( SkinOptions::TALK_AT_TOP ) ||
|
2019-10-17 22:46:07 +00:00
|
|
|
$subjectPage->isMainPage();
|
|
|
|
if ( !$this->getUserPageHelper()->isUserPage() &&
|
|
|
|
$this->getPermissions()->isTalkAllowed() && $talkAtBottom &&
|
2021-10-18 21:04:03 +00:00
|
|
|
// When showing talk at the bottom we restrict this so it is not shown to anons
|
|
|
|
// https://phabricator.wikimedia.org/T54165
|
|
|
|
// This whole code block can be removed when SkinOptions::TALK_AT_TOP is always true
|
2022-10-02 12:44:18 +00:00
|
|
|
$this->getUser()->isRegistered()
|
2019-01-10 22:59:56 +00:00
|
|
|
) {
|
2022-09-23 16:10:21 +00:00
|
|
|
$namespaces = $contentNavigationUrls['associated-pages'];
|
2017-07-12 15:12:40 +00:00
|
|
|
// FIXME [core]: This seems unnecessary..
|
|
|
|
$subjectId = $title->getNamespaceKey( '' );
|
|
|
|
$talkId = $subjectId === 'main' ? 'talk' : "{$subjectId}_talk";
|
2017-09-28 20:06:39 +00:00
|
|
|
|
|
|
|
if ( isset( $namespaces[$talkId] ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$talkButton = $namespaces[$talkId];
|
2019-08-02 15:02:28 +00:00
|
|
|
$talkTitle = Title::newFromLinkTarget( $namespaceInfo->getTalkPage( $title ) );
|
|
|
|
|
2019-10-17 22:46:07 +00:00
|
|
|
$buttons['talk'] = $this->getTalkButton( $talkTitle, $talkButton['text'] );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 16:10:31 +00:00
|
|
|
if ( $languagesHelper->doesTitleHasLanguagesOrVariants( $title ) && $title->isMainPage() ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$buttons['language'] = $this->getLanguageButton();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $buttons;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-04-21 22:44:18 +00:00
|
|
|
* @inheritDoc
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2021-07-23 00:57:37 +00:00
|
|
|
protected function getJsConfigVars(): array {
|
2019-09-18 23:07:21 +00:00
|
|
|
$title = $this->getTitle();
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2023-08-24 08:39:33 +00:00
|
|
|
$permissions = $this->getPermissions();
|
2019-09-18 23:07:21 +00:00
|
|
|
|
2020-04-21 22:44:18 +00:00
|
|
|
return array_merge( parent::getJsConfigVars(), [
|
2019-09-18 23:07:21 +00:00
|
|
|
'wgMinervaPermissions' => [
|
2023-08-24 08:39:33 +00:00
|
|
|
'watchable' => $permissions->isAllowed( IMinervaPagePermissions::WATCHABLE ),
|
|
|
|
'watch' => $permissions->isAllowed( IMinervaPagePermissions::WATCH ),
|
2019-09-18 23:07:21 +00:00
|
|
|
],
|
2021-09-23 19:47:46 +00:00
|
|
|
'wgMinervaFeatures' => $skinOptions->getAll(),
|
2017-11-28 21:17:27 +00:00
|
|
|
'wgMinervaDownloadNamespaces' => $this->getConfig()->get( 'MinervaDownloadNamespaces' ),
|
2020-04-21 22:44:18 +00:00
|
|
|
] );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the javascript entry modules to load. Only modules that need to
|
|
|
|
* be overriden or added conditionally should be placed here.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getDefaultModules() {
|
|
|
|
$modules = parent::getDefaultModules();
|
2019-05-14 09:01:04 +00:00
|
|
|
|
|
|
|
// FIXME: T223204: Dequeue default content modules except for the history
|
2023-03-30 17:22:36 +00:00
|
|
|
// action. Allow default content modules on history action in order to
|
|
|
|
// enable toggling of the filters.
|
|
|
|
// Long term this won't be necessary when T111565 is resolved and a
|
2019-05-14 09:01:04 +00:00
|
|
|
// more general solution can be used.
|
2022-04-15 21:05:22 +00:00
|
|
|
if ( $this->getContext()->getActionName() !== 'history' ) {
|
2023-03-30 17:22:36 +00:00
|
|
|
// dequeue default content modules (toc, collapsible, etc.)
|
2020-07-14 12:59:40 +00:00
|
|
|
$modules['content'] = array_diff( $modules['content'], [
|
|
|
|
// T111565
|
|
|
|
'jquery.makeCollapsible',
|
|
|
|
// Minerva provides its own implementation. Loading this will break display.
|
|
|
|
'mediawiki.toc'
|
|
|
|
] );
|
2019-05-14 09:01:04 +00:00
|
|
|
// dequeue styles associated with `content` key.
|
2020-07-14 12:59:40 +00:00
|
|
|
$modules['styles']['content'] = array_diff( $modules['styles']['content'], [
|
|
|
|
// T111565
|
|
|
|
'jquery.makeCollapsible.styles',
|
|
|
|
] );
|
2019-05-14 09:01:04 +00:00
|
|
|
}
|
2018-05-04 23:40:59 +00:00
|
|
|
$modules['styles']['core'] = $this->getSkinStyles();
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2021-04-28 21:58:51 +00:00
|
|
|
$modules['minerva'] = [
|
|
|
|
'skins.minerva.scripts'
|
|
|
|
];
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
return $modules;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-08-06 15:28:47 +00:00
|
|
|
* Provide styles required to present the server rendered page in this skin. Additional styles
|
|
|
|
* may be loaded dynamically by the client.
|
|
|
|
*
|
|
|
|
* Any styles returned by this method are loaded on the critical rendering path as linked
|
|
|
|
* stylesheets. I.e., they are required to load on the client before first paint.
|
|
|
|
*
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2019-08-06 15:28:47 +00:00
|
|
|
protected function getSkinStyles(): array {
|
2017-07-12 15:12:40 +00:00
|
|
|
$title = $this->getTitle();
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions = $this->getSkinOptions();
|
2023-02-11 19:48:00 +00:00
|
|
|
$request = $this->getRequest();
|
2023-04-17 16:54:48 +00:00
|
|
|
$requestAction = $this->getContext()->getActionName();
|
|
|
|
$viewAction = $requestAction === 'view';
|
2017-07-12 15:12:40 +00:00
|
|
|
$styles = [
|
|
|
|
'skins.minerva.base.styles',
|
2018-06-14 16:51:50 +00:00
|
|
|
'skins.minerva.content.styles.images',
|
2017-06-01 23:09:24 +00:00
|
|
|
'mediawiki.hlist',
|
2023-08-09 21:49:19 +00:00
|
|
|
'codex-search-styles',
|
2019-07-09 19:12:51 +00:00
|
|
|
'skins.minerva.icons.wikimedia',
|
2019-12-18 19:23:09 +00:00
|
|
|
'skins.minerva.mainMenu.icons',
|
|
|
|
'skins.minerva.mainMenu.styles',
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
2023-02-11 19:48:00 +00:00
|
|
|
|
|
|
|
// Warning box styles are needed when reviewing old revisions
|
|
|
|
// and inside the fallback editor styles to action=edit page.
|
|
|
|
if (
|
|
|
|
$title->getNamespace() !== NS_MAIN ||
|
|
|
|
$request->getText( 'oldid' ) ||
|
|
|
|
!$viewAction
|
|
|
|
) {
|
|
|
|
$styles[] = 'skins.minerva.messageBox.styles';
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
if ( $title->isMainPage() ) {
|
|
|
|
$styles[] = 'skins.minerva.mainPage.styles';
|
2018-02-14 00:14:54 +00:00
|
|
|
} elseif ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
$styles[] = 'skins.minerva.userpage.styles';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2019-10-17 22:46:07 +00:00
|
|
|
|
2021-04-28 21:58:51 +00:00
|
|
|
if ( $this->hasCategoryLinks() ) {
|
|
|
|
$styles[] = 'skins.minerva.categories.styles';
|
|
|
|
}
|
|
|
|
|
2020-12-18 03:09:15 +00:00
|
|
|
if ( $this->getUser()->isRegistered() ) {
|
2017-12-13 01:25:10 +00:00
|
|
|
$styles[] = 'skins.minerva.loggedin.styles';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2019-08-22 20:31:31 +00:00
|
|
|
// When any of these features are enabled in production
|
|
|
|
// remove the if condition
|
|
|
|
// and move the associated LESS file inside `skins.minerva.amc.styles`
|
|
|
|
// into a more appropriate module.
|
|
|
|
if (
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions->get( SkinOptions::PERSONAL_MENU ) ||
|
|
|
|
$skinOptions->get( SkinOptions::TALK_AT_TOP ) ||
|
|
|
|
$skinOptions->get( SkinOptions::HISTORY_IN_PAGE_ACTIONS ) ||
|
|
|
|
$skinOptions->get( SkinOptions::TOOLBAR_SUBMENU )
|
2019-08-22 20:31:31 +00:00
|
|
|
) {
|
2021-10-19 18:45:43 +00:00
|
|
|
// SkinOptions::PERSONAL_MENU + SkinOptions::TOOLBAR_SUBMENU uses ToggleList
|
2019-08-22 20:31:31 +00:00
|
|
|
// SkinOptions::TALK_AT_TOP uses tabs.less
|
|
|
|
// SkinOptions::HISTORY_IN_PAGE_ACTIONS + SkinOptions::TOOLBAR_SUBMENU uses pageactions.less
|
2019-01-10 22:59:56 +00:00
|
|
|
$styles[] = 'skins.minerva.amc.styles';
|
2019-08-22 20:31:31 +00:00
|
|
|
}
|
|
|
|
|
2021-09-23 19:47:46 +00:00
|
|
|
if ( $skinOptions->get( SkinOptions::PERSONAL_MENU ) ) {
|
2019-08-22 22:13:57 +00:00
|
|
|
// If ever enabled as the default, please remove the duplicate icons
|
|
|
|
// inside skins.minerva.mainMenu.icons. See comment for MAIN_MENU_EXPANDED
|
|
|
|
$styles[] = 'skins.minerva.personalMenu.icons';
|
|
|
|
}
|
|
|
|
|
2020-03-25 02:02:02 +00:00
|
|
|
if (
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions->get( SkinOptions::MAIN_MENU_EXPANDED )
|
2020-03-25 02:02:02 +00:00
|
|
|
) {
|
2019-08-22 22:13:57 +00:00
|
|
|
// If ever enabled as the default, please review skins.minerva.mainMenu.icons
|
|
|
|
// and remove any unneeded icons
|
|
|
|
$styles[] = 'skins.minerva.mainMenu.advanced.icons';
|
|
|
|
}
|
2019-08-22 20:31:31 +00:00
|
|
|
if (
|
2021-09-23 19:47:46 +00:00
|
|
|
$skinOptions->get( SkinOptions::PERSONAL_MENU ) ||
|
|
|
|
$skinOptions->get( SkinOptions::TOOLBAR_SUBMENU )
|
2019-08-22 20:31:31 +00:00
|
|
|
) {
|
|
|
|
// SkinOptions::PERSONAL_MENU requires the `userTalk` icon.
|
2020-03-25 02:02:02 +00:00
|
|
|
// SkinOptions::TOOLBAR_SUBMENU requires the rest of the icons including `overflow`.
|
2020-04-01 23:25:04 +00:00
|
|
|
// Note `skins.minerva.overflow.icons` is pulled down by skins.minerva.scripts but the menu can
|
2019-08-22 20:31:31 +00:00
|
|
|
// work without JS.
|
2020-04-01 23:25:04 +00:00
|
|
|
$styles[] = 'skins.minerva.overflow.icons';
|
2019-01-10 22:59:56 +00:00
|
|
|
}
|
2019-08-02 14:23:28 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
return $styles;
|
|
|
|
}
|
|
|
|
}
|