PageActions menu should use Builder pattern

The PageActions menu shouldn't be built inside SkinMinerva class.
All menus should be build in similar way so it's easier to understand
how different parts of the system work. This will allow us to easily
track different menu elements/move elements between different menus.

Additionally we should allow extensions/3rd party to modify both the
toolbar and overflow menus.

Changes:
 - Removed PageActions logic from SkinMinerva class
 - introduced new PageActions/Director to build page actions menu
 - introduced Builders for toolbar, and different types of overflow
 menu
 - because Overflow menu elements require the
 BaseTemplate::data['nav_urls] array, instead building all links,
 pass the array when building PageActions menu. Code for getting
 menu entries had to be rewritten (use $navUrls array instead of
 $this->tpl['nav_urls'];
 - ServiceWirings file contains logic on what to pass to
 PageActions/Director class (which builders)
 - PageActionsMenuEntry setTitle() and setNodeID() returns $this
 so we can use method chaining. Only a syntax sugar.
 - if AMC is not available/Overflow menu is disabled via config,
 system will pass EmptyOverflowBuilder. System will not add
 "show more" icon to toolbar menu when $overflowBuilder returns
 empty set of options.
 - both ToolbarBulder and OverflowMenuBuilders (except
 EmptyOverflowBuilder) will run 'MobileMenu' hook. Extensions should
 listen to hook, and inject it's own menu entries at their leisure.

Required follow-ups:
 - SkinMinerva provides isAllowedAction() method which is used
 both in the skin code and in Toolbar builder. We should extract
 that method into separate service
 - SkinMinerva provides getHistoryUrl() method which is used
 both in the skin code and in Toolbar builder. We should provide
 MinervaUrls service that knows how to build custom mobile URLs.

Bug: T221792
Change-Id: Ie08c4b61cea60c3a42fbf796a39360feea22bc33
This commit is contained in:
Piotr Miazga 2019-05-24 00:34:10 +02:00
parent 9d1217e13e
commit 209becf63d
9 changed files with 636 additions and 266 deletions

View File

@ -21,14 +21,13 @@
use MediaWiki\MediaWikiServices;
use MediaWiki\Minerva\Menu\Definitions;
use MediaWiki\Minerva\Menu\Main\AdvancedBuilder;
use MediaWiki\Minerva\Menu\Main\DefaultBuilder;
use MediaWiki\Minerva\Menu\Main\Director;
use MediaWiki\Minerva\Menu\Main as MainMenu;
use MediaWiki\Minerva\Menu\PageActions as PageActionsMenu;
use MediaWiki\Minerva\SkinOptions;
use MediaWiki\Minerva\SkinUserPageHelper;
return [
'Minerva.ContentHandler' => function ( MediaWikiServices $services ) {
'Minerva.ContentHandler' => function () {
return ContentHandler::getForTitle( RequestContext::getMain()->getTitle() );
},
'Minerva.Menu.MainDirector' => function ( MediaWikiServices $services ) {
@ -39,15 +38,51 @@ return [
$user = $context->getUser();
$definitions = new Definitions( $context, $services->getSpecialPageFactory() );
$builder = $options->get( SkinOptions::OPTION_AMC ) ?
new AdvancedBuilder( $showMobileOptions, $user, $definitions ) :
new DefaultBuilder( $showMobileOptions, $user, $definitions );
new MainMenu\AdvancedBuilder( $showMobileOptions, $user, $definitions ) :
new MainMenu\DefaultBuilder( $showMobileOptions, $user, $definitions );
return new Director( $builder );
return new MainMenu\Director( $builder );
},
'Minerva.SkinUserPageHelper' => function ( MediaWikiServices $services ) {
'Minerva.Menu.PageActionsDirector' => function ( MediaWikiServices $services ) {
/**
* @var SkinOptions $skinOptions
* @var SkinMinerva $skin
* @var SkinUserPageHelper $userPageHelper
*/
$skinOptions = $services->getService( 'Minerva.SkinOptions' );
$context = RequestContext::getMain();
$skin = $context->getSkin();
$userPageHelper = $services->getService( 'Minerva.SkinUserPageHelper' );
$toolbarBuilder = new PageActionsMenu\ToolbarBuilder(
$skin,
$context->getTitle(),
$context->getUser(),
$context
);
if ( $skinOptions->get( SkinOptions::OPTION_OVERFLOW_SUBMENU ) ) {
$overflowBuilder = $userPageHelper->isUserPage() ?
new PageActionsMenu\UserNamespaceOverflowBuilder(
$context,
$userPageHelper
) :
new PageActionsMenu\DefaultOverflowBuilder(
$context
);
} else {
$overflowBuilder = new PageActionsMenu\EmptyOverflowBuilder();
}
return new PageActionsMenu\PageActionsDirector(
$toolbarBuilder,
$overflowBuilder,
$context
);
},
'Minerva.SkinUserPageHelper' => function () {
return new SkinUserPageHelper( RequestContext::getMain()->getTitle() );
},
'Minerva.SkinOptions' => function ( MediaWikiServices $services ) {
'Minerva.SkinOptions' => function () {
return new SkinOptions();
}
];

View File

@ -0,0 +1,81 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use Hooks;
use MediaWiki\Minerva\Menu\Group;
use MessageLocalizer;
use MinervaUI;
class DefaultOverflowBuilder implements IOverflowBuilder {
/**
* @var MessageLocalizer
*/
private $messageLocalizer;
/**
* Initialize Default overflow menu Group
*
* @param MessageLocalizer $messageLocalizer
*/
public function __construct( MessageLocalizer $messageLocalizer ) {
$this->messageLocalizer = $messageLocalizer;
}
/**
* @inheritDoc
*/
public function getGroup( array $navUrls ) {
$group = new Group();
$possibleEntries = array_filter( [
$this->buildEntry( 'info', 'info', 'info', $navUrls ),
$this->buildEntry( 'permalink', 'link', 'permalink', $navUrls ),
$this->buildEntry( 'backlinks', 'articleRedirect', 'whatlinkshere', $navUrls ),
$this->buildEntry( 'cite', 'quotes', 'citethispage', $navUrls )
] );
foreach ( $possibleEntries as $menuEntry ) {
$group->insertEntry( $menuEntry );
}
Hooks::run( 'MobileMenu', [ 'pageactions.overflow', &$group ] );
return $group;
}
/**
* @param string $name
* @param string $icon Wikimedia UI icon name.
* @param string $navUrlKey
* @param array $navUrls A set of navigation urls build by SkinTemplate::buildNavUrls()
* @return PageActionMenuEntry|null
*/
private function buildEntry( $name, $icon, $navUrlKey, array $navUrls ) {
$href = $navUrls[$navUrlKey]['href'] ?? null;
return $href ?
new PageActionMenuEntry(
'page-actions-overflow-' . $name,
$href,
MinervaUI::iconClass( '', 'before', 'wikimedia-ui-' . $icon . '-base20' ),
$this->messageLocalizer->msg( 'minerva-page-actions-' . $name )
) : null;
}
}

View File

@ -0,0 +1,33 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use MediaWiki\Minerva\Menu\Group;
class EmptyOverflowBuilder implements IOverflowBuilder {
/**
* @inheritDoc
*/
public function getGroup( array $navUrls ) {
return new Group();
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use MediaWiki\Minerva\Menu\Group;
interface IOverflowBuilder {
/**
* @param array $navUrls A set of navigation urls build by SkinTemplate::buildNavUrls()
* @return Group
*/
public function getGroup( array $navUrls );
}

View File

@ -93,7 +93,7 @@ class PageActionMenuEntry implements IMenuEntry {
* @param Message $message Title message
* @return $this
*/
public function setTitle( \Message $message ) {
public function setTitle( Message $message ) {
$this->component['title'] = $message->escaped();
return $this;
}

View File

@ -0,0 +1,86 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use MessageLocalizer;
use MinervaUI;
use MWException;
/**
* Director responsible for building Page Actions menu.
* This class is stateless.
*/
final class PageActionsDirector {
/**
* @var ToolbarBuilder
*/
private $toolbarBuilder;
/**
* @var IOverflowBuilder
*/
private $overflowBuilder;
/**
* @var MessageLocalizers
*/
private $messageLocalizer;
/**
* Director responsible for Page Actions menu building
*
* @param ToolbarBuilder $toolbarBuilder The toolbar builder
* @param IOverflowBuilder $overflowBuilder The overflow menu builder
* @param MessageLocalizer $messageLocalizer Message localizer used to translate texts
*/
public function __construct( ToolbarBuilder $toolbarBuilder, IOverflowBuilder $overflowBuilder,
MessageLocalizer $messageLocalizer ) {
$this->toolbarBuilder = $toolbarBuilder;
$this->overflowBuilder = $overflowBuilder;
$this->messageLocalizer = $messageLocalizer;
}
/**
* Build the menu data array that can be passed to views/javascript
* @param array $navUrls A set of navigation urls build by SkinTemplate::buildNavUrls()
* @param bool $doesHaveLangUrls Whether the page is also available in other languages or variants
* @return array
* @throws MWException
*/
public function buildMenu( array $navUrls, $doesHaveLangUrls ): array {
$toolbar = $this->toolbarBuilder->getGroup( $doesHaveLangUrls );
$overflowMenu = $this->overflowBuilder->getGroup( $navUrls );
$menu = [
'toolbar' => $toolbar->getEntries()
];
if ( $overflowMenu->hasEntries() ) {
$menu[ 'overflowMenu' ] = [
'item-id' => 'page-actions-overflow',
'class' => MinervaUI::iconClass( 'page-actions-overflow' ),
'text' => $this->messageLocalizer->msg( 'minerva-page-actions-overflow' ),
'pageActions' => $overflowMenu->getEntries()
];
}
return $menu;
}
}

View File

@ -0,0 +1,247 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use ExtensionRegistry;
use Hooks;
use MediaWiki\Minerva\Menu\Group;
use MessageLocalizer;
use MinervaUI;
use MWException;
use SkinMinerva;
use SpecialMobileHistory;
use SpecialPage;
use Title;
use User;
class ToolbarBuilder {
/**
* @var User Currently logged in user
*/
private $user;
/**
* @var Title Article title user is currently browsing
*/
private $title;
/**
* Temporary variable to access isAllowedPageAction() method.
* FIXME: Create MinervaAllowedPageActions service
* @var SkinMinerva user skin
*/
private $skin;
/**
* @var MessageLocalizer Message localizer to generate localized texts
*/
private $messageLocalizer;
/**
* Build Group containing icons for toolbar
* @param SkinMinerva $skin User Skin
* @param Title $title Article title user is currently browsing
* @param User $user Currently logged in user
* @param MessageLocalizer $msgLocalizer Message localizer to generate localized texts
*/
public function __construct( SkinMinerva $skin, Title $title, User $user,
MessageLocalizer $msgLocalizer ) {
$this->skin = $skin;
$this->title = $title;
$this->user = $user;
$this->messageLocalizer = $msgLocalizer;
}
/**
* @param bool $doesPageHaveLanguages Whether the page is also available in other languages
* or variants
* @return Group
* @throws MWException
*/
public function getGroup( $doesPageHaveLanguages ) {
$group = new Group();
if ( $this->skin->isAllowedPageAction( 'switch-language' ) ) {
$group->insertEntry( $this->createSwitchLanguageAction( $doesPageHaveLanguages ) );
}
if ( $this->skin->isAllowedPageAction( 'watch' ) ) {
$group->insertEntry( $this->createWatchPageAction() );
}
if ( $this->skin->isAllowedPageAction( 'history' ) ) {
$group->insertEntry( $this->getHistoryPageAction() );
}
Hooks::run( 'MobileMenu', [ 'pageactions.toolbar', &$group ] );
// We want the edit icon/action always to be the last element on the toolbar list
if ( $this->skin->isAllowedPageAction( 'edit' ) ) {
$group->insertEntry( $this->createEditPageAction() );
}
return $group;
}
/**
* Creates the "edit" page action: the well-known pencil icon that, when tapped, will open an
* editor with the lead section loaded.
*
* @return PageActionMenuEntry An edit page actions menu entry
* @throws MWException
*/
protected function createEditPageAction() {
$title = $this->title;
$user = $this->user;
$editArgs = [ 'action' => 'edit' ];
if ( $title->isWikitextPage() ) {
// If the content model is wikitext we'll default to editing the lead section.
// Full wikitext editing is hard on mobile devices.
$editArgs['section'] = SkinMinerva::LEAD_SECTION_NUMBER;
}
$userQuickEditCheck = $title->quickUserCan( 'edit', $user )
&& ( $title->exists() || $title->quickUserCan( 'create', $user ) );
$userBlockInfo = $user->getId() == 0 ? false : $user->isBlockedFrom( $title, true );
$userCanEdit = $userQuickEditCheck && !$userBlockInfo;
$entry = new PageActionMenuEntry(
'page-actions-edit',
$title->getLocalURL( $editArgs ),
'edit-page '
. MinervaUI::iconClass( $userCanEdit ? 'edit-enabled' : 'edit', 'element' ),
$this->messageLocalizer->msg( 'mobile-frontend-editor-edit' )
);
return $entry
->setTitle( $this->messageLocalizer->msg( 'mobile-frontend-pageaction-edit-tooltip' ) )
->setNodeID( 'ca-edit' );
}
/**
* Creates the "switch-language" action: the icon that, when tapped, opens the language
* switcher.
* @param bool $doesPageHaveLanguages Whether the page is also available in other
* languages or variants
* @return PageActionMenuEntry A menu entry object that represents a map of HTML attributes
* and a 'text' property to be used with the pageActionMenu.mustache template.
* @throws MWException
*/
protected function createSwitchLanguageAction( $doesPageHaveLanguages ) {
$languageSwitcherLink = false;
$languageSwitcherClasses = ' language-selector';
if ( $doesPageHaveLanguages ) {
$languageSwitcherLink = SpecialPage::getTitleFor(
'MobileLanguages',
$this->title
)->getLocalURL();
} else {
$languageSwitcherClasses .= ' disabled';
}
$entry = new PageActionMenuEntry(
'page-actions-languages',
$languageSwitcherLink,
MinervaUI::iconClass( 'language-switcher', 'element', $languageSwitcherClasses ),
$this->messageLocalizer->msg( 'mobile-frontend-language-article-heading' )
);
return $entry->setTitle(
$this->messageLocalizer->msg( 'mobile-frontend-language-article-heading' )
);
}
/**
* Creates the "watch" or "unwatch" action: the well-known star icon that, when tapped, will
* add the page to or remove the page from the user's watchlist; or, if the user is logged out,
* will direct the user's UA to Special:Login.
*
* @return PageActionMenuEntry An watch/unwatch page actions menu entry
* @throws MWException
*/
protected function createWatchPageAction() {
$title = $this->title;
$user = $this->user;
$isWatched = $title && $user->isLoggedIn() && $user->isWatched( $title );
$mode = $isWatched ? 'watch' : 'unwatch';
$href = $user->isAnon()
? $this->getLoginUrl( [ 'returnto' => $title ] )
: $title->getLocalURL( [ 'action' => $mode ] );
if ( $isWatched ) {
$msg = $this->messageLocalizer->msg( 'unwatchthispage' );
$icon = 'watched';
} else {
$msg = $this->messageLocalizer->msg( 'watchthispage' );
$icon = 'watch';
}
$iconClass = MinervaUI::iconClass( $icon, 'element', 'watch-this-article' ) . ' jsonly';
if ( $isWatched ) {
$iconClass .= ' watched';
}
$entry = new PageActionMenuEntry(
'page-actions-watch',
$href,
$iconClass,
$msg
);
return $entry
->setTitle( $msg )
->setNodeID( 'ca-watch' );
}
/**
* Creates a history action: An icon that links to the mobile history page.
*
* @return PageActionMenuEntry A menu entry object that represents a map of HTML attributes
* and a 'text' property to be used with the pageActionMenu.mustache template.
* @throws MWException
*/
protected function getHistoryPageAction() {
return new PageActionMenuEntry(
'page-actions-history',
$this->getHistoryUrl( $this->title ),
MinervaUI::iconClass( 'clock' ),
$this->messageLocalizer->msg( 'mobile-frontend-history' )
);
}
/**
* Get the URL for the history page for the given title using Special:History
* when available.
* FIXME: temporary duplicated code, same as SkinMinerva::getHistoryUrl()
* @param Title $title The Title object of the page being viewed
* @return string
* @throws MWException
*/
protected function getHistoryUrl( Title $title ) {
return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) &&
SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->user ) ?
SpecialPage::getTitleFor( 'History', $title )->getLocalURL() :
$title->getLocalURL( [ 'action' => 'history' ] );
}
/**
* Prepares a url to the Special:UserLogin with query parameters
* @param array $query
* @return string
* @throws MWException
*/
private function getLoginUrl( $query ) {
return SpecialPage::getTitleFor( 'Userlogin' )->getLocalURL( $query );
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* 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
*/
namespace MediaWiki\Minerva\Menu\PageActions;
use Hooks;
use MediaWiki\Minerva\Menu\Group;
use MediaWiki\Minerva\SkinUserPageHelper;
use MessageLocalizer;
use MinervaUI;
use MWException;
use SpecialPage;
use User;
class UserNamespaceOverflowBuilder implements IOverflowBuilder {
/**
* @var MessageLocalizer
*/
private $messageLocalizer;
/**
* @var User|null
*/
private $pageUser;
/**
* Initialize the overflow menu visible on the User namespace
* @param MessageLocalizer $msgLocalizer
* @param SkinUserPageHelper $userPageHelper
*/
public function __construct( MessageLocalizer $msgLocalizer, SkinUserPageHelper $userPageHelper ) {
$this->messageLocalizer = $msgLocalizer;
$this->pageUser = $userPageHelper->getPageUser();
}
/**
* @inheritDoc
* @throws MWException
*/
public function getGroup( array $navUrls ) {
$group = new Group();
$group->insertEntry( $this->buildEntry(
'uploads', 'upload', SpecialPage::getTitleFor( 'Uploads', $this->pageUser )->getLocalURL()
) );
$possibleEntries = array_filter( [
$this->buildEntryFromNav( 'user-rights', 'userAvatar', 'userrights', $navUrls ),
$this->buildEntryFromNav( 'logs', 'listBullet', 'log', $navUrls ),
$this->buildEntryFromNav( 'info', 'info', 'info', $navUrls ),
$this->buildEntryFromNav( 'permalink', 'link', 'permalink', $navUrls ),
$this->buildEntryFromNav( 'backlinks', 'articleRedirect', 'whatlinkshere', $navUrls )
] );
foreach ( $possibleEntries as $menuEntry ) {
$group->insertEntry( $menuEntry );
}
Hooks::run( 'MobileMenu', [ 'pageactions.overflow', &$group ] );
return $group;
}
/**
* @param string $name
* @param string $icon Wikimedia UI icon name.
* @param string $navUrlKey
* @param array $navUrls A set of navigation urls build by SkinTemplate::buildNavUrls()
* @return PageActionMenuEntry|null
*/
private function buildEntryFromNav( $name, $icon, $navUrlKey, array $navUrls ) {
return $this->buildEntry( $name, $icon, $navUrls[$navUrlKey]['href'] ?? null );
}
/**
* @param string $name
* @param string $icon Wikimedia UI icon name.
* @param string|null $href
* @return PageActionMenuEntry|null
*/
private function buildEntry( $name, $icon, $href ) {
return $href ?
new PageActionMenuEntry(
'page-actions-overflow-' . $name,
$href,
MinervaUI::iconClass( '', 'before', 'wikimedia-ui-' . $icon . '-base20' ),
$this->messageLocalizer->msg( 'minerva-page-actions-' . $name )
) : null;
}
}

View File

@ -20,8 +20,6 @@
use MediaWiki\MediaWikiServices;
use MediaWiki\Minerva\Menu\Main\Director as MainMenuDirector;
use MediaWiki\Minerva\Menu\Group;
use MediaWiki\Minerva\Menu\PageActions\PageActionMenuEntry;
use MediaWiki\Minerva\SkinOptions;
use MediaWiki\Minerva\SkinUserPageHelper;
@ -213,7 +211,7 @@ class SkinMinerva extends SkinTemplate {
* @param string $action
* @return bool
*/
protected function isAllowedPageAction( $action ) {
public function isAllowedPageAction( $action ) {
$title = $this->getTitle();
$config = $this->getConfig();
// Title may be undefined in certain contexts (T179833)
@ -472,15 +470,6 @@ class SkinMinerva extends SkinTemplate {
$tpl->set( 'language_urls', $language_urls );
}
/**
* Prepares a url to the Special:UserLogin with query parameters
* @param array $query
* @return string
*/
public function getLoginUrl( $query ) {
return SpecialPage::getTitleFor( 'Userlogin' )->getLocalURL( $query );
}
/**
* Get a history link which describes author and relative time of last edit
* @param Title $title The Title object of the page being viewed
@ -840,128 +829,11 @@ class SkinMinerva extends SkinTemplate {
* @param BaseTemplate $tpl
*/
protected function preparePageActions( BaseTemplate $tpl ) {
$toolbar = new Group();
$overflowMenu = null;
if ( $this->isAllowedPageAction( 'switch-language' ) ) {
$toolbar->insertEntry( $this->createSwitchLanguageAction() );
}
if ( $this->isAllowedPageAction( 'watch' ) ) {
$toolbar->insertEntry( $this->createWatchPageAction() );
}
if ( $this->isAllowedPageAction( 'history' ) ) {
$toolbar->insertEntry( $this->getHistoryPageAction() );
}
if ( $this->isAllowedPageAction( 'edit' ) ) {
$toolbar->insertEntry( $this->createEditPageAction() );
}
if ( $this->isAllowedPageAction( SkinOptions::OPTION_OVERFLOW_SUBMENU ) ) {
$overflowMenu = $this->newToolbarOverflowMenu( $tpl );
}
$tpl->set( 'page_actions', [
'toolbar' => $toolbar->getEntries(),
'overflowMenu' => $overflowMenu
] );
}
/**
* Creates the "edit" page action: the well-known pencil icon that, when tapped, will open an
* editor with the lead section loaded.
*
* @return PageActionMenuEntry An edit page actions menu entry
*/
protected function createEditPageAction() {
$title = $this->getTitle();
$user = $this->getUser();
$editArgs = [ 'action' => 'edit' ];
if ( $title->isWikitextPage() ) {
// If the content model is wikitext we'll default to editing the lead section.
// Full wikitext editing is hard on mobile devices.
$editArgs['section'] = self::LEAD_SECTION_NUMBER;
}
$userQuickEditCheck = $title->quickUserCan( 'edit', $user )
&& ( $title->exists() || $title->quickUserCan( 'create', $user ) );
$userBlockInfo = $user->getId() == 0 ? false : $user->isBlockedFrom( $title, true );
$userCanEdit = $userQuickEditCheck && !$userBlockInfo;
return PageActionMenuEntry::create(
'page-actions-edit',
$title->getLocalURL( $editArgs ),
'edit-page '
. MinervaUI::iconClass( $userCanEdit ? 'edit-enabled' : 'edit', 'element' ),
$this->msg( 'mobile-frontend-editor-edit' )
)
->setTitle( $this->msg( 'mobile-frontend-pageaction-edit-tooltip' ) )
->setNodeID( 'ca-edit' );
}
/**
* Creates the "watch" or "unwatch" action: the well-known star icon that, when tapped, will
* add the page to or remove the page from the user's watchlist; or, if the user is logged out,
* will direct the user's UA to Special:Login.
*
* @return PageActionMenuEntry An watch/unwatch page actions menu entry
*/
protected function createWatchPageAction() {
$title = $this->getTitle();
$user = $this->getUser();
$isWatched = $title && $user->isLoggedIn() && $user->isWatched( $title );
$actionOnClick = $isWatched ? 'unwatch' : 'watch';
$href = $user->isAnon()
? $this->getLoginUrl( [ 'returnto' => $title ] )
: $title->getLocalURL( [ 'action' => $actionOnClick ] );
$additionalClassNames = ' jsonly';
if ( $isWatched ) {
$msg = $this->msg( 'unwatchthispage' );
$icon = 'watched';
$additionalClassNames .= ' watched';
} else {
$msg = $this->msg( 'watchthispage' );
$icon = 'watch';
}
$iconClass = MinervaUI::iconClass( $icon, 'element', 'watch-this-article' );
return PageActionMenuEntry::create(
'page-actions-watch',
$href,
$iconClass . $additionalClassNames,
$msg
)
->setTitle( $msg )
->setNodeID( 'ca-watch' );
}
/**
* Creates the "switch-language" action: the icon that, when tapped, opens the language
* switcher.
*
* @return PageActionMenuEntry A menu entry object that represents a map of HTML attributes
* and a 'text' property to be used with the pageActionMenu.mustache template.
*/
protected function createSwitchLanguageAction() {
$languageSwitcherLink = false;
$languageSwitcherClasses = ' language-selector';
if ( $this->doesPageHaveLanguages ) {
$languageSwitcherLink = SpecialPage::getTitleFor(
'MobileLanguages',
$this->getTitle()
)->getLocalURL();
} else {
$languageSwitcherClasses .= ' disabled';
}
return PageActionMenuEntry::create(
'page-actions-languages',
$languageSwitcherLink,
MinervaUI::iconClass( 'language-switcher', 'element', $languageSwitcherClasses ),
$this->msg( 'mobile-frontend-language-article-heading' )
)->setTitle( $this->msg( 'mobile-frontend-language-article-heading' ) );
/** @var \MediaWiki\Minerva\Menu\PageActions\PageActionsDirector $director */
$director = MediaWikiServices::getInstance()->getService( 'Minerva.Menu.PageActionsDirector' );
$tpl->set( 'page_actions',
$director->buildMenu( $tpl->data[ 'nav_urls'], $this->doesPageHaveLanguages )
);
}
/**
@ -973,133 +845,12 @@ class SkinMinerva extends SkinTemplate {
}
/**
* Creates a history action: An icon that links to the mobile history page.
*
* @return PageActionMenuEntry A menu entry object that represents a map of HTML attributes
* and a 'text' property to be used with the pageActionMenu.mustache template.
*/
protected function getHistoryPageAction() {
return new PageActionMenuEntry(
'page-actions-history',
$this->getHistoryUrl( $this->getTitle() ),
MinervaUI::iconClass( 'clock' ),
$this->msg( 'mobile-frontend-history' )
);
}
/**
* Minerva skin do not use any of those, there is no need to calculate that
* Minerva skin do use any of those, there is no need to calculate that
* @return array
*/
protected function buildPersonalUrls() {
return [];
}
/**
* Creates an overflow action: An icon that links to the overflow menu.
*
* @param BaseTemplate $tpl
* @return array|null A map of HTML attributes and a 'text' property to be used with the
* pageActionMenu.mustache template.
*/
private function newToolbarOverflowMenu( BaseTemplate $tpl ) {
$pageActions = $this->getUserPageHelper()->isUserPage()
? $this->getUserNamespaceOverflowPageActions( $tpl )
: $this->getDefaultOverflowPageActions( $tpl );
return $pageActions->hasEntries() ? [
'item-id' => 'page-actions-overflow',
'class' => MinervaUI::iconClass( 'page-actions-overflow' ),
'text' => $this->msg( 'minerva-page-actions-overflow' ),
'pageActions' => $pageActions->getEntries()
] : null;
}
/**
* @param BaseTemplate $tpl
* @return Group
*/
private function getDefaultOverflowPageActions( BaseTemplate $tpl ) {
$group = new Group();
foreach ( [
$this->newOverflowPageAction( 'info', 'info', $tpl->data['nav_urls']['info']['href'] ?? null ),
$this->newOverflowPageAction(
'permalink', 'link', $tpl->data['nav_urls']['permalink']['href'] ?? null
),
$this->newOverflowPageAction(
'backlinks', 'articleRedirect', $tpl->data['nav_urls']['whatlinkshere']['href'] ?? null
),
$this->newOverflowPageAction(
'cite', 'quotes', $tpl->data['nav_urls']['citethispage']['href'] ?? null
)
] as $menuEntry ) {
if ( $menuEntry !== null ) {
$group->insertEntry( $menuEntry );
}
}
return $group;
}
/**
* Unless the OverflowMenu is enabled, Minerva doesn't use nav_urls from QuikcTemplate.
* We can skip that heavy operation
* @return array
*/
protected function buildNavUrls() {
if ( $this->isAllowedPageAction( SkinOptions::OPTION_OVERFLOW_SUBMENU ) ) {
// the OverflowMenu uses nav_urls, use the value from SkinTemplate
return parent::buildNavUrls();
} else {
return [];
}
}
/**
* @param BaseTemplate $tpl
* @return Group
*/
private function getUserNamespaceOverflowPageActions( BaseTemplate $tpl ) {
$pageUser = $this->getUserPageHelper()->getPageUser();
$group = new Group();
foreach ( [
$this->newOverflowPageAction(
'uploads', 'upload', SpecialPage::getTitleFor( 'Uploads', $pageUser )->getLocalURL()
),
$this->newOverflowPageAction(
'user-rights', 'userAvatar', $tpl->data['nav_urls']['userrights']['href'] ?? null
),
$this->newOverflowPageAction(
'logs', 'listBullet', $tpl->data['nav_urls']['log']['href'] ?? null
),
$this->newOverflowPageAction( 'info', 'info', $tpl->data['nav_urls']['info']['href'] ?? null ),
$this->newOverflowPageAction(
'permalink', 'link', $tpl->data['nav_urls']['permalink']['href'] ?? null
),
$this->newOverflowPageAction(
'backlinks', 'articleRedirect', $tpl->data['nav_urls']['whatlinkshere']['href'] ?? null
)
] as $menuEntry ) {
if ( $menuEntry !== null ) {
$group->insertEntry( $menuEntry );
}
}
return $group;
}
/**
* @param string $name
* @param string $icon Wikimedia UI icon name.
* @param string|null $href
* @return PageActionMenuEntry|null
*/
private function newOverflowPageAction( $name, $icon, $href ) {
return $href ?
new PageActionMenuEntry(
'page-actions-overflow-' . $name,
$href,
MinervaUI::iconClass( '', 'before', 'wikimedia-ui-' . $icon . '-base20' ),
$this->msg( 'minerva-page-actions-' . $name )
) : null;
}
/**
* Checks whether the editor can handle the existing content handler type.