Provide a code structure for menus handling and add Advanced menu

Changes:
 - moved all menu elements definitions from SkinMinerva into
 a separate Definitions.php file
 - moved menu building from SkinMinerva into includes/menu/Main
 folder
 - introduced Builder pattern for easy menu building
 Minerva/Menu/Main/Director takes an Minerva/Menu/Main/IBuilder
 and builds the menu. The IBuilders use definitions from
 Minerva/Menu/Definitions file, so all definitions can be shared
 across different menus
 - used ServiceWiring file to register MainMenu Director as Service
 - left class_alias for old MenuBuilder as some extensions still use it
 - The hooks system have to stay like that as some extensions
 are using it (BlueSpiceMultiUpload and GrowthExperiments).
 - introduced AdvancedMenu builder for the AMC mode

Bug: T216152
Change-Id: I210c3f1fa36bbd2f9108d728b12cbb21ee210354
This commit is contained in:
Piotr Miazga 2019-04-08 19:08:57 +02:00
parent 258e635ae5
commit 1f4582cc09
14 changed files with 813 additions and 339 deletions

View file

@ -1,6 +1,29 @@
<?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
*/
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\SkinOptions;
use MediaWiki\Minerva\SkinUserPageHelper;
@ -8,6 +31,19 @@ return [
'Minerva.ContentHandler' => function ( MediaWikiServices $services ) {
return ContentHandler::getForTitle( RequestContext::getMain()->getTitle() );
},
'Minerva.Menu.MainDirector' => function ( MediaWikiServices $services ) {
$context = RequestContext::getMain();
/** @var SkinOptions $options */
$options = $services->getService( 'Minerva.SkinOptions' );
$showMobileOptions = $options->get( SkinOptions::OPTION_MOBILE_OPTIONS );
$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 );
return new Director( $builder );
},
'Minerva.SkinUserPageHelper' => function ( MediaWikiServices $services ) {
return new SkinUserPageHelper( RequestContext::getMain()->getTitle() );
},

View file

@ -0,0 +1,332 @@
<?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;
use IContextSource;
use MediaWiki\Special\SpecialPageFactory;
use MinervaUI;
use MWException;
use SpecialMobileWatchlist;
use SpecialPage;
use Title;
use User;
/**
* Set of all know menu items for easier building
*/
final class Definitions {
/**
* @var User
*/
private $user;
/**
* @var IContextSource
*/
private $context;
/**
* @var SpecialPageFactory
*/
private $specialPageFactory;
/**
* Initialize definitions helper class
*
* @param IContextSource $context
* @param SpecialPageFactory $factory
*/
public function __construct( IContextSource $context, SpecialPageFactory $factory ) {
$this->user = $context->getUser();
$this->context = $context;
$this->specialPageFactory = $factory;
}
/**
* Inserts the Contributions menu item into the menu.
*
* @param Group $group
* @throws MWException
*/
public function insertContributionsMenuItem( Group $group ) {
$group->insert( 'contribs' )
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-contributions' )->escaped(),
SpecialPage::getTitleFor( 'Contributions', $this->user->getName() )->getLocalUrl(),
MinervaUI::iconClass( 'contributions', 'before' ),
[ 'data-event-name' => 'contributions' ]
);
}
/**
* Inserts the Watchlist menu item into the menu for a logged in user
*
* @param Group $group
* @throws MWException
*/
public function insertWatchlistMenuItem( Group $group ) {
$watchTitle = SpecialPage::getTitleFor( 'Watchlist' );
// Watchlist link
$watchlistQuery = [];
// Avoid fatal when MobileFrontend not available (T171241)
if ( class_exists( 'SpecialMobileWatchlist' ) ) {
$view = $this->user->getOption( SpecialMobileWatchlist::VIEW_OPTION_NAME, false );
$filter = $this->user->getOption( SpecialMobileWatchlist::FILTER_OPTION_NAME, false );
if ( $view ) {
$watchlistQuery['watchlistview'] = $view;
}
if ( $filter && $view === 'feed' ) {
$watchlistQuery['filter'] = $filter;
}
}
$group->insert( 'watchlist' )
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-watchlist' )->escaped(),
$watchTitle->getLocalURL( $watchlistQuery ),
MinervaUI::iconClass( 'watchlist', 'before' ),
[ 'data-event-name' => 'watchlist' ]
);
}
/**
* Creates a login or logout button
*
* @param Group $group
* @throws MWException
*/
public function insertLogInOutMenuItem( Group $group ) {
$query = [];
$returntoquery = [];
$request = $this->context->getRequest();
if ( !$request->wasPosted() ) {
$returntoquery = $request->getValues();
unset( $returntoquery['title'] );
unset( $returntoquery['returnto'] );
unset( $returntoquery['returntoquery'] );
}
$title = $this->context->getTitle();
// Don't ever redirect back to the login page (bug 55379)
if ( !$title->isSpecial( 'Userlogin' ) ) {
$query[ 'returnto' ] = $title->getPrefixedText();
}
if ( $this->user->isLoggedIn() ) {
if ( !empty( $returntoquery ) ) {
$query[ 'returntoquery' ] = wfArrayToCgi( $returntoquery );
}
$url = SpecialPage::getTitleFor( 'Userlogout' )->getLocalURL( $query );
$username = $this->user->getName();
$group->insert( 'auth', false )
->addComponent(
$username,
Title::newFromText( $username, NS_USER )->getLocalUrl(),
MinervaUI::iconClass( 'profile', 'before', 'truncated-text primary-action' ),
[ 'data-event-name' => 'profile' ]
)
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-logout' )->escaped(),
$url,
MinervaUI::iconClass(
'logout', 'element', 'secondary-action truncated-text' ),
[ 'data-event-name' => 'logout' ]
);
} else {
// unset campaign on login link so as not to interfere with A/B tests
unset( $returntoquery['campaign'] );
$query[ 'returntoquery' ] = wfArrayToCgi( $returntoquery );
$url = $this->getLoginUrl( $query );
$group->insert( 'auth', false )
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-login' )->escaped(),
$url,
MinervaUI::iconClass( 'login', 'before' ),
[ 'data-event-name' => 'login' ]
);
}
}
/**
* Build and insert Home link
* @param Group $group
*/
public function insertHomeItem( Group $group ) {
// Home link
$group->insert( 'home' )
->addComponent(
$this->context->msg( 'mobile-frontend-home-button' )->escaped(),
Title::newMainPage()->getLocalUrl(),
MinervaUI::iconClass( 'home', 'before' ),
[ 'data-event-name' => 'home' ]
);
}
/**
* Build and insert Random link
* @param Group $group
* @throws MWException
*/
public function insertRandomItem( Group $group ) {
// Random link
$group->insert( 'random' )
->addComponent( $this->context->msg( 'mobile-frontend-random-button' )->escaped(),
SpecialPage::getTitleFor( 'Randompage' )->getLocalUrl() . '#/random',
MinervaUI::iconClass( 'random', 'before' ), [
'id' => 'randomButton',
'data-event-name' => 'random',
] );
}
/**
* If Nearby is supported, build and inject the Nearby link
* @param Group $group
* @throws MWException
*/
public function insertNearbyIfSupported( Group $group ) {
// Nearby link (if supported)
if ( $this->specialPageFactory->exists( 'Nearby' ) ) {
$group->insert( 'nearby', $isJSOnly = true )
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-nearby' )->escaped(),
SpecialPage::getTitleFor( 'Nearby' )->getLocalURL(),
MinervaUI::iconClass( 'nearby', 'before', 'nearby' ),
[ 'data-event-name' => 'nearby' ]
);
}
}
/**
* Build and insert the Settings link
* @param Group $group
* @throws MWException
*/
public function insertMobileOptionsItem( Group $group ) {
$title = $this->context->getTitle();
$returnToTitle = $title->getPrefixedText();
$group->insert( 'settings' )
->addComponent(
$this->context->msg( 'mobile-frontend-main-menu-settings' )->escaped(),
SpecialPage::getTitleFor( 'MobileOptions' )->
getLocalURL( [ 'returnto' => $returnToTitle ] ),
MinervaUI::iconClass( 'settings', 'before' ),
[ 'data-event-name' => 'settings' ]
);
}
/**
* Build and insert the Preferences link
* @param Group $group
* @throws MWException
*/
public function insertPreferencesItem( Group $group ) {
$group->insert( 'preferences' )
->addComponent(
$this->context->msg( 'preferences' )->escaped(),
SpecialPage::getTitleFor( 'Preferences' )->getLocalURL(),
MinervaUI::iconClass( 'settings', 'before' ),
[ 'data-event-name' => 'preferences' ]
);
}
/**
* Build and insert About page link
* @param Group $group
*/
public function insertAboutItem( Group $group ) {
$title = Title::newFromText( $this->context->msg( 'aboutpage' )->inContentLanguage()->text() );
$msg = $this->context->msg( 'aboutsite' );
if ( $title && !$msg->isDisabled() ) {
$group->insert( 'about' )
->addComponent( $msg->text(), $title->getLocalUrl() );
}
}
/**
* Build and insert Disclaimers link
* @param Group $group
*/
public function insertDisclaimersItem( Group $group ) {
$title = Title::newFromText( $this->context->msg( 'disclaimerpage' )
->inContentLanguage()->text() );
$msg = $this->context->msg( 'disclaimers' );
if ( $title && !$msg->isDisabled() ) {
$group->insert( 'disclaimers' )
->addComponent( $msg->text(), $title->getLocalUrl() );
}
}
/**
* Build and insert the RecentChanges link
* @param Group $group
* @throws MWException
*/
public function insertRecentChanges( Group $group ) {
$title = SpecialPage::getTitleFor( 'Recentchanges' );
$group->insert( 'recentchanges' )
->addComponent(
$this->context->msg( 'recentchanges' )->escaped(),
$title->getLocalURL(),
MinervaUI::iconClass( 'recentchanges', 'before' ),
[ 'data-event-name' => 'recentchanges' ]
);
}
/**
* Build and insert the SpecialPages link
* @param Group $group
* @throws MWException
*/
public function insertSpecialPages( Group $group ) {
$group->insert( 'specialpages' )
->addComponent(
$this->context->msg( 'specialpages' )->escaped(),
SpecialPage::getTitleFor( 'Specialpages' )->getLocalURL(),
MinervaUI::iconClass( 'specialpages', 'before' ),
[ 'data-event-name' => 'specialpages' ]
);
}
/**
* Build and insert the CommunityPortal link
* @param Group $group
* @throws MWException
*/
public function insertCommunityPortal( Group $group ) {
/// placeholder for a follow-up patch @see T216152
}
/**
* 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

@ -18,19 +18,28 @@
* @file
*/
namespace MediaWiki\Minerva;
namespace MediaWiki\Minerva\Menu;
use DomainException;
/**
* Model for a menu that can be presented in a skin.
*/
class MenuBuilder {
class Group {
/**
* @var MenuEntry[]
*/
private $entries = [];
/**
* Return entries count
*
* @return int
*/
public function hasEntries() {
return count( $this->entries ) > 0;
}
/**
* Get all entries represented as plain old PHP arrays.
*
@ -116,3 +125,9 @@ class MenuBuilder {
return $entry;
}
}
/**
* make sure BlueSpiceMultiUpload and GrowthExperiments use the new class
* @TODO remove after updating all extensions that still depend upon MenuBuilder
*/
class_alias( 'MediaWiki\Minerva\Menu\Group', 'MediaWiki\Minerva\MenuBuilder' );

View file

@ -0,0 +1,68 @@
<?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\Main;
use FatalError;
use Hooks;
use MWException;
use MediaWiki\Minerva\Menu\Group;
/**
* A menu builder that provides additional menu entries that match
* Advanced Mobile Contributions project requirements. This menu
* is used when AMC SkinOption flag is set to true.
*
* @package MediaWiki\Minerva\Menu\Main
*/
class AdvancedBuilder extends DefaultBuilder {
/**
* @return array|Group[]
* @throws FatalError
* @throws MWException
*/
public function getGroups() {
return [
$this->getDiscoveryTools(),
$this->getPersonalTools(),
$this->getSiteTools(),
$this->getConfigurationTools(),
];
}
/**
* Prepares a list of links that have the purpose of discovery in the main navigation menu
* @return Group
* @throws FatalError
* @throws MWException
*/
public function getSiteTools() {
$group = new Group();
$this->definitions->insertRecentChanges( $group );
$this->definitions->insertSpecialPages( $group );
$this->definitions->insertCommunityPortal( $group );
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'sitetools', &$group ] );
return $group;
}
}

View file

@ -0,0 +1,152 @@
<?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\Main;
use FatalError;
use Hooks;
use MWException;
use User;
use MediaWiki\Minerva\Menu\Definitions;
use MediaWiki\Minerva\Menu\Group;
/**
* Used to build default (available for everyone by default) main menu
*/
class DefaultBuilder implements IBuilder {
/**
* @var bool
*/
private $showMobileOptions;
/**
* Currently logged in user
* @var User
*/
protected $user;
/**
* @var Definitions
*/
protected $definitions;
/**
* Initialize the Default Main Menu builder
*
* @param bool $showMobileOptions Show MobileOptions instead of Preferences
* @param User $user The current user
* @param Definitions $definitions A menu items definitions set
*/
public function __construct( $showMobileOptions, User $user, Definitions $definitions ) {
$this->showMobileOptions = $showMobileOptions;
$this->user = $user;
$this->definitions = $definitions;
}
/**
* @return Group[]
* @throws FatalError
* @throws MWException
*/
public function getGroups() {
return [
$this->getDiscoveryTools(),
$this->getPersonalTools(),
$this->getConfigurationTools(),
];
}
/**
* Prepares a list of links that have the purpose of discovery in the main navigation menu
* @return Group
* @throws FatalError
* @throws MWException
*/
protected function getDiscoveryTools() {
$group = new Group();
$this->definitions->insertHomeItem( $group );
$this->definitions->insertRandomItem( $group );
$this->definitions->insertNearbyIfSupported( $group );
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'discovery', &$group ] );
return $group;
}
/**
* Builds the personal tools menu item group.
*
* ... by adding the Watchlist, Settings, and Log{in,out} menu items in the given order.
*
* @return Group
* @throws FatalError
* @throws MWException
*/
protected function getPersonalTools() {
$group = new Group();
$this->definitions->insertLogInOutMenuItem( $group );
if ( $this->user->isLoggedIn() ) {
$this->definitions->insertWatchlistMenuItem( $group );
$this->definitions->insertContributionsMenuItem( $group );
}
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'personal', &$group ] );
return $group;
}
/**
* Like <code>SkinMinerva#getDiscoveryTools</code> and <code>#getPersonalTools</code>, create
* a group of configuration-related menu items. Currently, only the Settings menu item is in the
* group.
*
* @return Group
* @throws MWException
*/
protected function getConfigurationTools() {
$group = new Group();
$this->showMobileOptions ?
$this->definitions->insertMobileOptionsItem( $group ) :
$this->definitions->insertPreferencesItem( $group );
return $group;
}
/**
* Returns an array of sitelinks to add into the main menu footer.
* @return Group Collection of site links
* @throws MWException
*/
public function getSiteLinks() {
$group = new Group();
$this->definitions->insertAboutItem( $group );
$this->definitions->insertDisclaimersItem( $group );
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'sitelinks', &$group ] );
return $group;
}
}

View file

@ -0,0 +1,74 @@
<?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\Main;
/**
* Director responsible for building Main Menu
*/
final class Director {
/**
* @var IBuilder
*/
private $builder;
/**
* @var array
*/
private $menuData;
/**
* Director responsible for Main Menu building
*
* @param IBuilder $builder
*/
public function __construct( IBuilder $builder ) {
$this->builder = $builder;
}
/**
* Returns a data representation of the main menus
* @return array
*/
public function getMenuData() {
if ( $this->menuData === null ) {
$this->menuData = $this->buildMenu();
}
return $this->menuData;
}
/**
* Build the menu data array that can be passed to views/javascript
* @return array
*/
private function buildMenu() {
$menuData = [
'groups' => [],
'sitelinks' => $this->builder->getSiteLinks()->getEntries()
];
foreach ( $this->builder->getGroups() as $group ) {
if ( $group->hasEntries() ) {
$menuData['groups'][] = $group->getEntries();
}
}
return $menuData;
}
}

View file

@ -0,0 +1,36 @@
<?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\Main;
use MediaWiki\Minerva\Menu\Group;
interface IBuilder {
/**
* @return Group[]
*/
public function getGroups();
/**
* @return Group
*/
public function getSiteLinks();
}

View file

@ -15,7 +15,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
namespace MediaWiki\Minerva;
namespace MediaWiki\Minerva\Menu;
/**
* Model for a menu entry.

View file

@ -18,9 +18,10 @@
* @file
*/
use MediaWiki\Minerva\MenuBuilder;
use MediaWiki\Minerva\SkinOptions;
use MediaWiki\MediaWikiServices;
use MediaWiki\Minerva\Menu\Main\Director as MainMenuDirector;
use MediaWiki\Minerva\SkinOptions;
use MediaWiki\Minerva\SkinUserPageHelper;
/**
* Minerva: Born from the godhead of Jupiter with weapons!
@ -52,9 +53,29 @@ class SkinMinerva extends SkinTemplate {
$this->skinOptions = MediaWikiServices::getInstance()->getService( 'Minerva.SkinOptions' );
}
/**
* Initalized main menu. Please use getter.
* @return MainMenuDirector
*
*/
private $mainMenu;
/**
* Build the Main Menu Director by passing the skin options
*
* @return MainMenuDirector
*/
protected function getMainMenu() {
if ( !$this->mainMenu ) {
$this->mainMenu = MediaWikiServices::getInstance()->getService( 'Minerva.Menu.MainDirector' );
}
return $this->mainMenu;
}
/**
* Returns the site name for the footer, either as a text or <img> tag
* @return string
* @throws ConfigException
*/
public function getSitename() {
$config = $this->getConfig();
@ -118,7 +139,7 @@ class SkinMinerva extends SkinTemplate {
$tpl->set( 'unstyledContent', $out->getProperty( 'unstyledContent' ) );
// Set the links for the main menu
$tpl->set( 'menu_data', $this->getMenuData() );
$tpl->set( 'menu_data', $this->getMainMenu()->getMenuData() );
// Set the links for page secondary actions
$tpl->set( 'secondary_actions', $this->getSecondaryActions( $tpl ) );
@ -428,122 +449,6 @@ class SkinMinerva extends SkinTemplate {
] );
}
}
/**
* Inserts the Contributions menu item into the menu.
*
* @param MenuBuilder $menu
* @param User $user The user to whom the contributions belong
*/
private function insertContributionsMenuItem( MenuBuilder $menu, User $user ) {
$menu->insert( 'contribs' )
->addComponent(
$this->msg( 'mobile-frontend-main-menu-contributions' )->escaped(),
SpecialPage::getTitleFor( 'Contributions', $user->getName() )->getLocalUrl(),
MinervaUI::iconClass( 'contributions', 'before' ),
[ 'data-event-name' => 'contributions' ]
);
}
/**
* Inserts the Watchlist menu item into the menu for a logged in user
*
* @param MenuBuilder $menu
* @param User $user that must be logged in
*/
protected function insertWatchlistMenuItem( MenuBuilder $menu, User $user ) {
$watchTitle = SpecialPage::getTitleFor( 'Watchlist' );
// Watchlist link
$watchlistQuery = [];
// Avoid fatal when MobileFrontend not available (T171241)
if ( class_exists( 'SpecialMobileWatchlist' ) ) {
$view = $user->getOption( SpecialMobileWatchlist::VIEW_OPTION_NAME, false );
$filter = $user->getOption( SpecialMobileWatchlist::FILTER_OPTION_NAME, false );
if ( $view ) {
$watchlistQuery['watchlistview'] = $view;
}
if ( $filter && $view === 'feed' ) {
$watchlistQuery['filter'] = $filter;
}
}
$menu->insert( 'watchlist' )
->addComponent(
$this->msg( 'mobile-frontend-main-menu-watchlist' )->escaped(),
$watchTitle->getLocalURL( $watchlistQuery ),
MinervaUI::iconClass( 'watchlist', 'before' ),
[ 'data-event-name' => 'watchlist' ]
);
}
/**
* If the user is using a mobile device (or the UA presents itself as a mobile device), then the
* Settings menu item is inserted into the menu; otherwise the Preferences menu item is inserted.
*
* @param MenuBuilder $menu
*/
protected function insertSettingsMenuItem( MenuBuilder $menu ) {
$returnToTitle = $this->getTitle()->getPrefixedText();
// Links specifically for mobile mode
if ( $this->skinOptions->get( SkinOptions::OPTION_MOBILE_OPTIONS ) ) {
// Settings link
$menu->insert( 'settings' )
->addComponent(
$this->msg( 'mobile-frontend-main-menu-settings' )->escaped(),
SpecialPage::getTitleFor( 'MobileOptions' )->
getLocalURL( [ 'returnto' => $returnToTitle ] ),
MinervaUI::iconClass( 'settings', 'before' ),
[ 'data-event-name' => 'settings' ]
);
// Links specifically for desktop mode
} else {
// Preferences link
$menu->insert( 'preferences' )
->addComponent(
$this->msg( 'preferences' )->escaped(),
SpecialPage::getTitleFor( 'Preferences' )->getLocalURL(),
MinervaUI::iconClass( 'settings', 'before' ),
[ 'data-event-name' => 'preferences' ]
);
}
}
/**
* Builds the personal tools menu item group.
*
* ... by adding the Watchlist, Settings, and Log{in,out} menu items in the given order.
*
* @param MenuBuilder $menu
*/
protected function buildPersonalTools( MenuBuilder $menu ) {
$this->insertLogInOutMenuItem( $menu );
$user = $this->getUser();
if ( $user->isLoggedIn() ) {
$this->insertWatchlistMenuItem( $menu, $user );
$this->insertContributionsMenuItem( $menu, $user );
}
}
/**
* Prepares and returns urls and links personal to the given user
* @return array
*/
protected function getPersonalTools() {
$menu = new MenuBuilder();
$this->buildPersonalTools( $menu );
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'personal', &$menu ] );
return $menu->getEntries();
}
/**
* Rewrites the language list so that it cannot be contaminated by other extensions with things
* other than languages
@ -561,67 +466,6 @@ class SkinMinerva extends SkinTemplate {
$tpl->set( 'language_urls', $language_urls );
}
/**
* Like <code>SkinMinerva#getDiscoveryTools</code> and <code>#getPersonalTools</code>, create
* a group of configuration-related menu items. Currently, only the Settings menu item is in the
* group.
*
* @return array
*/
private function getConfigurationTools() {
$menu = new MenuBuilder();
$this->insertSettingsMenuItem( $menu );
return $menu->getEntries();
}
/**
* Prepares a list of links that have the purpose of discovery in the main navigation menu
* @return array
*/
protected function getDiscoveryTools() {
$config = $this->getConfig();
$menu = new MenuBuilder();
$factory = MediaWikiServices::getInstance()->getSpecialPageFactory();
// Home link
$menu->insert( 'home' )
->addComponent(
$this->msg( 'mobile-frontend-home-button' )->escaped(),
Title::newMainPage()->getLocalURL(),
MinervaUI::iconClass( 'home', 'before' ),
[ 'data-event-name' => 'home' ]
);
// Random link
$menu->insert( 'random' )
->addComponent(
$this->msg( 'mobile-frontend-random-button' )->escaped(),
SpecialPage::getTitleFor( 'Randompage' )->getLocalURL() . '#/random',
MinervaUI::iconClass( 'random', 'before' ),
[
'id' => 'randomButton',
'data-event-name' => 'random',
]
);
// Nearby link (if supported)
if ( $factory->exists( 'Nearby' ) ) {
$menu->insert( 'nearby', $isJSOnly = true )
->addComponent(
$this->msg( 'mobile-frontend-main-menu-nearby' )->escaped(),
SpecialPage::getTitleFor( 'Nearby' )->getLocalURL(),
MinervaUI::iconClass( 'nearby', 'before', 'nearby' ),
[ 'data-event-name' => 'nearby' ]
);
}
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'discovery', &$menu ] );
return $menu->getEntries();
}
/**
* Prepares a url to the Special:UserLogin with query parameters
* @param array $query
@ -631,64 +475,6 @@ class SkinMinerva extends SkinTemplate {
return SpecialPage::getTitleFor( 'Userlogin' )->getLocalURL( $query );
}
/**
* Creates a login or logout button
*
* @param MenuBuilder $menu
*/
protected function insertLogInOutMenuItem( MenuBuilder $menu ) {
$query = [];
$returntoquery = [];
if ( !$this->getRequest()->wasPosted() ) {
$returntoquery = $this->getRequest()->getValues();
unset( $returntoquery['title'] );
unset( $returntoquery['returnto'] );
unset( $returntoquery['returntoquery'] );
}
$title = $this->getTitle();
// Don't ever redirect back to the login page (bug 55379)
if ( !$title->isSpecial( 'Userlogin' ) ) {
$query[ 'returnto' ] = $title->getPrefixedText();
}
$user = $this->getUser();
if ( $user->isLoggedIn() ) {
if ( !empty( $returntoquery ) ) {
$query[ 'returntoquery' ] = wfArrayToCgi( $returntoquery );
}
$url = SpecialPage::getTitleFor( 'Userlogout' )->getLocalURL( $query );
$username = $user->getName();
$menu->insert( 'auth', false )
->addComponent(
$username,
Title::newFromText( $username, NS_USER )->getLocalURL(),
MinervaUI::iconClass( 'profile', 'before', 'truncated-text primary-action' ),
[ 'data-event-name' => 'profile' ]
)
->addComponent(
$this->msg( 'mobile-frontend-main-menu-logout' )->escaped(),
$url,
MinervaUI::iconClass(
'logout', 'element', 'secondary-action truncated-text' ),
[ 'data-event-name' => 'logout' ]
);
} else {
// unset campaign on login link so as not to interfere with A/B tests
unset( $returntoquery['campaign'] );
$query[ 'returntoquery' ] = wfArrayToCgi( $returntoquery );
$url = $this->getLoginUrl( $query );
$menu->insert( 'auth', false )
->addComponent(
$this->msg( 'mobile-frontend-main-menu-login' )->escaped(),
$url,
MinervaUI::iconClass( 'login', 'before' ),
[ 'data-event-name' => 'login' ]
);
}
}
/**
* Get a history link which describes author and relative time of last edit
* @param Title $title The Title object of the page being viewed
@ -930,34 +716,6 @@ class SkinMinerva extends SkinTemplate {
$tpl->set( 'internalBanner', '' );
}
/**
* Returns an array of sitelinks to add into the main menu footer.
* @return array Array of site links
*/
protected function getSiteLinks() {
$menu = new MenuBuilder();
// About link
$title = Title::newFromText( $this->msg( 'aboutpage' )->inContentLanguage()->text() );
$msg = $this->msg( 'aboutsite' );
if ( $title && !$msg->isDisabled() ) {
$menu->insert( 'about' )
->addComponent( $msg->text(), $title->getLocalURL() );
}
// Disclaimers link
$title = Title::newFromText( $this->msg( 'disclaimerpage' )->inContentLanguage()->text() );
$msg = $this->msg( 'disclaimers' );
if ( $title && !$msg->isDisabled() ) {
$menu->insert( 'disclaimers' )
->addComponent( $msg->text(), $title->getLocalURL() );
}
// Allow other extensions to add or override tools
Hooks::run( 'MobileMenu', [ 'sitelinks', &$menu ] );
return $menu->getEntries();
}
/**
* Returns an array with details for a language button.
* @return array
@ -1232,23 +990,6 @@ class SkinMinerva extends SkinTemplate {
&& $contentHandler->supportsDirectApiEditing();
}
/**
* Returns a data representation of the main menus
* @return array
*/
protected function getMenuData() {
$data = [
'groups' => [
$this->getDiscoveryTools(),
$this->getPersonalTools(),
$this->getConfigurationTools(),
],
'sitelinks' => $this->getSiteLinks(),
];
return $data;
}
/**
* Returns array of config variables that should be added only to this skin
* for use in JavaScript.
@ -1258,7 +999,7 @@ class SkinMinerva extends SkinTemplate {
$vars = [
'wgMinervaFeatures' => $this->skinOptions->getAll(),
'wgMinervaDownloadNamespaces' => $this->getConfig()->get( 'MinervaDownloadNamespaces' ),
'wgMinervaMenuData' => $this->getMenuData(),
'wgMinervaMenuData' => $this->getMainMenu()->getMenuData(),
];
return $vars;

View file

@ -0,0 +1 @@
<svg width="21" height="17" xmlns="http://www.w3.org/2000/svg"><path d="M0 16.625h21v-4.156H0v4.156zm2.1-3.117h2.1v2.078H2.1v-2.078zM0 0v4.156h21V0H0zm4.2 3.117H2.1V1.04h2.1v2.078zM0 10.391h21V6.234H0v4.157zm2.1-3.118h2.1v2.079H2.1V7.273z" fill="#4A4F53"/></svg>

After

Width:  |  Height:  |  Size: 262 B

View file

@ -0,0 +1 @@
<svg width="22" height="18" xmlns="http://www.w3.org/2000/svg"><path d="M19.8 2.177H11L8.8 0H2.2C.99 0 0 .98 0 2.177V15.24c0 1.197.99 2.177 2.2 2.177h17.6c1.21 0 2.2-.98 2.2-2.177V4.354c0-1.197-.99-2.177-2.2-2.177zm-2.266 11.974L14.3 12.279l-3.234 1.872.858-3.625-2.849-2.438 3.751-.316L14.3 4.354l1.474 3.418 3.751.316-2.849 2.438.858 3.625z" fill="#4A4F53"/></svg>

After

Width:  |  Height:  |  Size: 366 B

View file

@ -67,14 +67,17 @@
"ValidSkinNames": {
"minerva": "MinervaNeue"
},
"AutoloadNamespaces": {
"MediaWiki\\Minerva\\Menu\\": "includes/menu/"
},
"AutoloadClasses": {
"MinervaUI": "includes/MinervaUI.php",
"MinervaHooks": "includes/MinervaHooks.php",
"MinervaTemplate": "includes/skins/MinervaTemplate.php",
"SkinMinerva": "includes/skins/SkinMinerva.php",
"SkinMinervaNeue": "includes/skins/SkinMinerva.php",
"MediaWiki\\Minerva\\MenuBuilder": "includes/skins/MenuBuilder.php",
"MediaWiki\\Minerva\\MenuEntry": "includes/skins/MenuEntry.php",
"MediaWiki\\Minerva\\Menu\\Group": "includes/menu/Group.php",
"MediaWiki\\Minerva\\MenuBuilder": "includes/menu/Group.php",
"MediaWiki\\Minerva\\ResourceLoaderLessVarFileModule": "includes/ResourceLoaderLessVarFileModule.php",
"MediaWiki\\Minerva\\SkinOptions": "includes/SkinOptions.php",
"MediaWiki\\Minerva\\SkinUserPageHelper": "includes/skins/SkinUserPageHelper.php"
@ -341,7 +344,9 @@
"random": "resources/skins.minerva.mainMenu.icons/random.svg",
"settings": "resources/skins.minerva.mainMenu.icons/settings.svg",
"watchlist": "resources/skins.minerva.mainMenu.icons/watchlist.svg",
"contributions": "resources/skins.minerva.mainMenu.icons/contributions.svg"
"contributions": "resources/skins.minerva.mainMenu.icons/contributions.svg",
"recentchanges": "resources/skins.minerva.mainMenu.icons/recentchanges.svg",
"specialpages": "resources/skins.minerva.mainMenu.icons/specialpages.svg"
}
},
"skins.minerva.mainMenu.styles": {

View file

@ -1,14 +1,14 @@
<?php
namespace Tests\MediaWiki\Minerva;
namespace Tests\MediaWiki\Minerva\Menu;
use MediaWiki\Minerva\MenuBuilder;
use MediaWiki\Minerva\MenuEntry;
use MediaWiki\Minerva\Menu\Group;
/**
* @group MinervaNeue
* @coversDefaultClass \MediaWiki\Minerva\Menu\Group
*/
class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
class GroupTest extends \MediaWikiTestCase {
private $homeComponent = [
'text' => 'Home',
'href' => '/Main_page',
@ -23,22 +23,22 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
];
/**
* @covers \MediaWiki\Minerva\MenuBuilder::getEntries
* @covers ::getEntries
*/
public function testItShouldntHaveEntriesByDefault() {
$menu = new MenuBuilder();
$menu = new Group();
$this->assertEmpty( $menu->getEntries() );
}
/**
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers \MediaWiki\Minerva\MenuBuilder::search
* @covers \MediaWiki\Minerva\MenuBuilder::getEntries
* @covers \MediaWiki\Minerva\MenuEntry::addComponent
* @covers ::insert
* @covers ::search
* @covers ::getEntries
* @covers \MediaWiki\Minerva\Menu\MenuEntry::addComponent
*/
public function testInsertingAnEntry() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'home' )
->addComponent(
$this->homeComponent['text'],
@ -60,13 +60,13 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers \MediaWiki\Minerva\MenuBuilder::search
* @covers \MediaWiki\Minerva\MenuBuilder::getEntries
* @covers \MediaWiki\Minerva\MenuEntry::addComponent
* @covers ::insert
* @covers ::search
* @covers ::getEntries
* @covers \MediaWiki\Minerva\Menu\MenuEntry::addComponent
*/
public function testInsertingAnEntryAfterAnother() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'home' )
->addComponent(
$this->homeComponent['text'],
@ -113,12 +113,12 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
/**
* @expectedException \DomainException
* @expectedExceptionMessage The "home" entry doesn't exist.
* @covers \MediaWiki\Minerva\MenuBuilder::insertAfter
* @covers \MediaWiki\Minerva\MenuBuilder::search
* @covers \MediaWiki\Minerva\MenuEntry::addComponent
* @covers ::insertAfter
* @covers ::search
* @covers \MediaWiki\Minerva\Menu\MenuEntry::addComponent
*/
public function testInsertAfterWhenTargetEntryDoesntExist() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insertAfter( 'home', 'nearby' )
->addComponent(
$this->nearbyComponent['text'],
@ -130,10 +130,10 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
/**
* @expectedException \DomainException
* @expectedExceptionMessage The "car" entry already exists.
* @covers \MediaWiki\Minerva\MenuBuilder::insertAfter
* @covers ::insertAfter
*/
public function testInsertAfterWithAnEntryWithAnExistingName() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'home' );
$menu->insert( 'car' );
$menu->insertAfter( 'home', 'car' );
@ -142,20 +142,20 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
/**
* @expectedException \DomainException
* @expectedExceptionMessage The "home" entry already exists.
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers ::insert
*/
public function testInsertingAnEntryWithAnExistingName() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'home' );
$menu->insert( 'home' );
}
/**
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers \MediaWiki\Minerva\MenuBuilder::insertAfter
* @covers ::insert
* @covers ::insertAfter
*/
public function testInsertingAnEntryAfterAnotherOne() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'first' );
$menu->insert( 'last' );
$menu->insertAfter( 'first', 'middle' );
@ -167,9 +167,9 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers \MediaWiki\Minerva\MenuBuilder::getEntries
* @covers \MediaWiki\Minerva\MenuEntry::addComponent
* @covers ::insert
* @covers ::getEntries
* @covers \MediaWiki\Minerva\Menu\MenuEntry::addComponent
*/
public function testinsertingAnEntryWithMultipleComponents() {
$authLoginComponent = [
@ -185,7 +185,7 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
'mw-ui-icon mw-ui-icon-element secondary-logout secondary-action truncated-text',
];
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'auth' )
->addComponent(
$authLoginComponent['text'],
@ -212,12 +212,12 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
}
/**
* @covers \MediaWiki\Minerva\MenuBuilder::insert
* @covers \MediaWiki\Minerva\MenuBuilder::getEntries
* @covers \MediaWiki\Minerva\MenuEntry::addComponent
* @covers ::insert
* @covers ::getEntries
* @covers \MediaWiki\Minerva\Menu\MenuEntry::addComponent
*/
public function testInsertingAJavascriptOnlyEntry() {
$menu = new MenuBuilder();
$menu = new Group();
$menu->insert( 'nearby', $isJSOnly = true )
->addComponent(
$this->nearbyComponent['text'],
@ -236,18 +236,4 @@ class MenuBuilderTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals( $expectedEntries, $menu->getEntries() );
}
/**
* @covers \MediaWiki\Minerva\MenuEntry::__construct
* @covers \MediaWiki\Minerva\MenuEntry::getName()
* @covers \MediaWiki\Minerva\MenuEntry::isJSOnly()
* @covers \MediaWiki\Minerva\MenuEntry::getComponents()
*/
public function testMenuEntryConstruction() {
$name = 'test';
$isJSOnly = true;
$entry = new MenuEntry( $name, $isJSOnly );
$this->assertSame( $name, $entry->getName() );
$this->assertSame( $isJSOnly, $entry->isJSOnly() );
$this->assertSame( [], $entry->getComponents() );
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Tests\MediaWiki\Minerva\Menu;
use MediaWiki\Minerva\Menu\MenuEntry;
/**
* @group MinervaNeue
* @coversDefaultClass \MediaWiki\Minerva\Menu\MenuEntry
*/
class MenuEntryTest extends \MediaWikiTestCase {
/**
* @covers ::__construct
* @covers ::getName()
* @covers ::isJSOnly()
* @covers ::getComponents()
*/
public function testMenuEntryConstruction() {
$name = 'test';
$isJSOnly = true;
$entry = new MenuEntry( $name, $isJSOnly );
$this->assertSame( $name, $entry->getName() );
$this->assertSame( $isJSOnly, $entry->isJSOnly() );
$this->assertSame( [], $entry->getComponents() );
}
}