From 1f4582cc09773ea0bf5260beedceb70002c8aab4 Mon Sep 17 00:00:00 2001 From: Piotr Miazga Date: Mon, 8 Apr 2019 19:08:57 +0200 Subject: [PATCH] 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 --- includes/ServiceWiring.php | 36 ++ includes/menu/Definitions.php | 332 ++++++++++++++++++ .../{skins/MenuBuilder.php => menu/Group.php} | 19 +- includes/menu/Main/AdvancedBuilder.php | 68 ++++ includes/menu/Main/DefaultBuilder.php | 152 ++++++++ includes/menu/Main/Director.php | 74 ++++ includes/menu/Main/IBuilder.php | 36 ++ includes/{skins => menu}/MenuEntry.php | 2 +- includes/skins/SkinMinerva.php | 309 ++-------------- .../recentchanges.svg | 1 + .../specialpages.svg | 1 + skin.json | 11 +- .../GroupTest.php} | 84 ++--- tests/phpunit/menu/MenuEntryTest.php | 27 ++ 14 files changed, 813 insertions(+), 339 deletions(-) create mode 100644 includes/menu/Definitions.php rename includes/{skins/MenuBuilder.php => menu/Group.php} (87%) create mode 100644 includes/menu/Main/AdvancedBuilder.php create mode 100644 includes/menu/Main/DefaultBuilder.php create mode 100644 includes/menu/Main/Director.php create mode 100644 includes/menu/Main/IBuilder.php rename includes/{skins => menu}/MenuEntry.php (98%) create mode 100644 resources/skins.minerva.mainMenu.icons/recentchanges.svg create mode 100644 resources/skins.minerva.mainMenu.icons/specialpages.svg rename tests/phpunit/{MenuBuilderTest.php => menu/GroupTest.php} (68%) create mode 100644 tests/phpunit/menu/MenuEntryTest.php diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 23c6686..f896c14 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -1,6 +1,29 @@ 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() ); }, diff --git a/includes/menu/Definitions.php b/includes/menu/Definitions.php new file mode 100644 index 0000000..9844bab --- /dev/null +++ b/includes/menu/Definitions.php @@ -0,0 +1,332 @@ +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 ); + } + +} diff --git a/includes/skins/MenuBuilder.php b/includes/menu/Group.php similarity index 87% rename from includes/skins/MenuBuilder.php rename to includes/menu/Group.php index 6ce1874..9bcdcce 100644 --- a/includes/skins/MenuBuilder.php +++ b/includes/menu/Group.php @@ -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' ); diff --git a/includes/menu/Main/AdvancedBuilder.php b/includes/menu/Main/AdvancedBuilder.php new file mode 100644 index 0000000..bbdcece --- /dev/null +++ b/includes/menu/Main/AdvancedBuilder.php @@ -0,0 +1,68 @@ +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; + } +} diff --git a/includes/menu/Main/DefaultBuilder.php b/includes/menu/Main/DefaultBuilder.php new file mode 100644 index 0000000..fca41f7 --- /dev/null +++ b/includes/menu/Main/DefaultBuilder.php @@ -0,0 +1,152 @@ +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 SkinMinerva#getDiscoveryTools and #getPersonalTools, 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; + } + +} diff --git a/includes/menu/Main/Director.php b/includes/menu/Main/Director.php new file mode 100644 index 0000000..40f6d00 --- /dev/null +++ b/includes/menu/Main/Director.php @@ -0,0 +1,74 @@ +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; + } + +} diff --git a/includes/menu/Main/IBuilder.php b/includes/menu/Main/IBuilder.php new file mode 100644 index 0000000..e81424e --- /dev/null +++ b/includes/menu/Main/IBuilder.php @@ -0,0 +1,36 @@ +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 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 SkinMinerva#getDiscoveryTools and #getPersonalTools, 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; diff --git a/resources/skins.minerva.mainMenu.icons/recentchanges.svg b/resources/skins.minerva.mainMenu.icons/recentchanges.svg new file mode 100644 index 0000000..cb357ba --- /dev/null +++ b/resources/skins.minerva.mainMenu.icons/recentchanges.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/skins.minerva.mainMenu.icons/specialpages.svg b/resources/skins.minerva.mainMenu.icons/specialpages.svg new file mode 100644 index 0000000..d4fc074 --- /dev/null +++ b/resources/skins.minerva.mainMenu.icons/specialpages.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/skin.json b/skin.json index d304877..04eceb3 100644 --- a/skin.json +++ b/skin.json @@ -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": { diff --git a/tests/phpunit/MenuBuilderTest.php b/tests/phpunit/menu/GroupTest.php similarity index 68% rename from tests/phpunit/MenuBuilderTest.php rename to tests/phpunit/menu/GroupTest.php index 2f3bc96..3983c3f 100644 --- a/tests/phpunit/MenuBuilderTest.php +++ b/tests/phpunit/menu/GroupTest.php @@ -1,14 +1,14 @@ '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() ); - } } diff --git a/tests/phpunit/menu/MenuEntryTest.php b/tests/phpunit/menu/MenuEntryTest.php new file mode 100644 index 0000000..418e4a5 --- /dev/null +++ b/tests/phpunit/menu/MenuEntryTest.php @@ -0,0 +1,27 @@ +assertSame( $name, $entry->getName() ); + $this->assertSame( $isJSOnly, $entry->isJSOnly() ); + $this->assertSame( [], $entry->getComponents() ); + } +}