2017-07-12 15:12:40 +00:00
|
|
|
<?php
|
|
|
|
/**
|
2018-04-15 23:21:12 +00:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
*
|
|
|
|
* @file
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
2018-04-15 23:21:12 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2019-04-08 17:08:57 +00:00
|
|
|
use MediaWiki\Minerva\Menu\Main\Director as MainMenuDirector;
|
|
|
|
use MediaWiki\Minerva\SkinOptions;
|
|
|
|
use MediaWiki\Minerva\SkinUserPageHelper;
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Minerva: Born from the godhead of Jupiter with weapons!
|
|
|
|
* A skin that works on both desktop and mobile
|
|
|
|
* @ingroup Skins
|
|
|
|
*/
|
2018-04-15 23:40:02 +00:00
|
|
|
class SkinMinerva extends SkinTemplate {
|
2017-08-22 14:12:32 +00:00
|
|
|
/** @const LEAD_SECTION_NUMBER integer which corresponds to the lead section
|
|
|
|
in editing mode */
|
|
|
|
const LEAD_SECTION_NUMBER = 0;
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
/** @var string $skinname Name of this skin */
|
|
|
|
public $skinname = 'minerva';
|
|
|
|
/** @var string $template Name of this used template */
|
|
|
|
public $template = 'MinervaTemplate';
|
2019-04-10 12:52:34 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/** @var bool Whether the page is also available in other languages or variants */
|
|
|
|
protected $doesPageHaveLanguages = false;
|
|
|
|
|
2019-04-10 21:43:50 +00:00
|
|
|
/** @var SkinOptions */
|
|
|
|
private $skinOptions;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize Minerva Skin
|
|
|
|
*/
|
|
|
|
public function __construct() {
|
|
|
|
parent::__construct( 'minerva' );
|
|
|
|
$this->skinOptions = MediaWikiServices::getInstance()->getService( 'Minerva.SkinOptions' );
|
|
|
|
}
|
|
|
|
|
2019-04-08 17:08:57 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Returns the site name for the footer, either as a text or <img> tag
|
|
|
|
* @return string
|
2019-04-08 17:08:57 +00:00
|
|
|
* @throws ConfigException
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
public function getSitename() {
|
|
|
|
$config = $this->getConfig();
|
|
|
|
$customLogos = $config->get( 'MinervaCustomLogos' );
|
|
|
|
|
|
|
|
$footerSitename = $this->msg( 'mobile-frontend-footer-sitename' )->text();
|
|
|
|
|
|
|
|
// If there's a custom site logo, use that instead of text
|
|
|
|
if ( isset( $customLogos['copyright'] ) ) {
|
2017-09-01 05:01:10 +00:00
|
|
|
$attributes = [
|
2017-07-12 15:12:40 +00:00
|
|
|
'src' => $customLogos['copyright'],
|
|
|
|
'alt' => $footerSitename,
|
|
|
|
];
|
2017-12-03 10:37:03 +00:00
|
|
|
if ( pathinfo( $customLogos['copyright'], PATHINFO_EXTENSION ) === 'svg' ) {
|
|
|
|
$attributes['srcset'] = $customLogos['copyright'] . ' 1x';
|
|
|
|
if ( isset( $customLogos['copyright-fallback'] ) ) {
|
|
|
|
$attributes['src'] = $customLogos['copyright-fallback'];
|
|
|
|
} else {
|
2017-12-06 23:39:47 +00:00
|
|
|
$attributes['src'] = preg_replace( '/\.svg$/i', '.png', $customLogos['copyright'] );
|
2017-12-03 10:37:03 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
if ( isset( $customLogos['copyright-height'] ) ) {
|
|
|
|
$attributes['height'] = $customLogos['copyright-height'];
|
|
|
|
}
|
|
|
|
if ( isset( $customLogos['copyright-width'] ) ) {
|
|
|
|
$attributes['width'] = $customLogos['copyright-width'];
|
|
|
|
}
|
|
|
|
$sitename = Html::element( 'img', $attributes );
|
|
|
|
} else {
|
|
|
|
$sitename = $footerSitename;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $sitename;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* initialize various variables and generate the template
|
|
|
|
* @return QuickTemplate
|
|
|
|
*/
|
|
|
|
protected function prepareQuickTemplate() {
|
|
|
|
$out = $this->getOutput();
|
2019-01-10 22:59:56 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
// add head items
|
|
|
|
$out->addMeta( 'viewport', 'initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, ' .
|
|
|
|
'maximum-scale=5.0, width=device-width'
|
|
|
|
);
|
2018-09-19 17:49:54 +00:00
|
|
|
// T204691
|
|
|
|
$theme = $out->getConfig()->get( 'MFManifestThemeColor' );
|
|
|
|
if ( $theme ) {
|
|
|
|
$out->addMeta( 'theme-color', $theme );
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// Generate skin template
|
|
|
|
$tpl = parent::prepareQuickTemplate();
|
|
|
|
|
|
|
|
$this->doesPageHaveLanguages = $tpl->data['content_navigation']['variants'] ||
|
|
|
|
$tpl->data['language_urls'];
|
|
|
|
|
|
|
|
// Set whether or not the page content should be wrapped in div.content (for
|
|
|
|
// example, on a special page)
|
|
|
|
$tpl->set( 'unstyledContent', $out->getProperty( 'unstyledContent' ) );
|
|
|
|
|
|
|
|
// Set the links for the main menu
|
2019-04-08 17:08:57 +00:00
|
|
|
$tpl->set( 'menu_data', $this->getMainMenu()->getMenuData() );
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// Set the links for page secondary actions
|
|
|
|
$tpl->set( 'secondary_actions', $this->getSecondaryActions( $tpl ) );
|
|
|
|
|
|
|
|
// Construct various Minerva-specific interface elements
|
|
|
|
$this->preparePageContent( $tpl );
|
|
|
|
$this->prepareHeaderAndFooter( $tpl );
|
|
|
|
$this->prepareMenuButton( $tpl );
|
|
|
|
$this->prepareBanners( $tpl );
|
|
|
|
$this->preparePageActions( $tpl );
|
|
|
|
$this->prepareUserButton( $tpl );
|
|
|
|
$this->prepareLanguages( $tpl );
|
|
|
|
|
|
|
|
return $tpl;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares the header and the content of a page
|
|
|
|
* Stores in QuickTemplate prebodytext, postbodytext keys
|
|
|
|
* @param QuickTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function preparePageContent( QuickTemplate $tpl ) {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
|
|
|
|
// If it's a talk page, add a link to the main namespace page
|
2019-02-26 18:24:42 +00:00
|
|
|
// In AMC we do not need to do this as there is an easy way back to the article page
|
|
|
|
// via the talk/article tabs.
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $title->isTalkPage() && !$this->skinOptions->get( SkinOptions::OPTION_AMC ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
// if it's a talk page for which we have a special message, use it
|
|
|
|
switch ( $title->getNamespace() ) {
|
|
|
|
case NS_USER_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-userpage';
|
|
|
|
break;
|
|
|
|
case NS_PROJECT_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-projectpage';
|
|
|
|
break;
|
|
|
|
case NS_FILE_TALK:
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-filepage';
|
|
|
|
break;
|
|
|
|
default: // generic (all other NS)
|
|
|
|
$msg = 'mobile-frontend-talk-back-to-page';
|
|
|
|
}
|
|
|
|
$tpl->set( 'subject-page', MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
|
|
|
|
$title->getSubjectPage(),
|
|
|
|
$this->msg( $msg, $title->getText() )->text(),
|
|
|
|
[ 'class' => 'return-link' ]
|
|
|
|
) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets whether or not the page action is allowed.
|
|
|
|
*
|
|
|
|
* Page actions isn't allowed when:
|
|
|
|
* <ul>
|
|
|
|
* <li>
|
|
|
|
* the action is disabled (by removing it from the <code>MinervaPageActions</code>
|
|
|
|
* configuration variable; or
|
|
|
|
* </li>
|
|
|
|
* <li>the user is on the main page</li>
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* The "edit" page action is not allowed if editing is not possible on the page
|
|
|
|
* see @method: isCurrentPageContentModelEditable
|
|
|
|
*
|
|
|
|
* The "switch-language" is allowed if there are interlanguage links on the page,
|
|
|
|
* or <code>$wgMinervaAlwaysShowLanguageButton</code>
|
|
|
|
* is truthy.
|
|
|
|
*
|
|
|
|
* @param string $action
|
2017-07-24 16:53:04 +00:00
|
|
|
* @return bool
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function isAllowedPageAction( $action ) {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
$config = $this->getConfig();
|
2017-11-07 19:57:43 +00:00
|
|
|
// Title may be undefined in certain contexts (T179833)
|
|
|
|
if ( !$title ) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2018-10-21 15:10:41 +00:00
|
|
|
// T206406: Enable "Talk" or "Discussion" button on Main page, also, not forgetting
|
|
|
|
// the "switch-language" button. But disable "edit" and "watch" actions.
|
|
|
|
if ( $title->isMainPage() ) {
|
|
|
|
return ( in_array( $action, $config->get( 'MinervaPageActions' ) )
|
|
|
|
&& ( $action === 'talk' || $action === 'switch-language' ) );
|
|
|
|
}
|
|
|
|
|
2019-03-21 11:22:35 +00:00
|
|
|
if ( $action === 'history' && $title->exists() ) {
|
2019-04-10 21:43:50 +00:00
|
|
|
return $this->skinOptions->get( SkinOptions::OPTIONS_HISTORY_PAGE_ACTIONS );
|
2019-03-21 11:22:35 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 21:20:39 +00:00
|
|
|
if ( $action === SkinOptions::OPTION_OVERFLOW_SUBMENU ) {
|
|
|
|
return $this->skinOptions->get( SkinOptions::OPTION_OVERFLOW_SUBMENU );
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
if (
|
2019-02-07 07:51:12 +00:00
|
|
|
!in_array( $action, $config->get( 'MinervaPageActions' ) )
|
2017-07-12 15:12:40 +00:00
|
|
|
|| ( $this->getUserPageHelper()->isUserPage() && !$title->exists() )
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $action === 'edit' ) {
|
2018-04-05 22:23:14 +00:00
|
|
|
return $this->isCurrentPageContentModelEditable();
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( $action === 'switch-language' ) {
|
|
|
|
return $this->doesPageHaveLanguages || $config->get( 'MinervaAlwaysShowLanguageButton' );
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Overrides Skin::doEditSectionLink
|
|
|
|
* @param Title $nt
|
|
|
|
* @param string $section
|
|
|
|
* @param string|null $tooltip
|
2018-07-23 20:12:42 +00:00
|
|
|
* @param Language $lang
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-07-23 20:12:42 +00:00
|
|
|
public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
|
2018-10-21 15:10:41 +00:00
|
|
|
if ( $this->isAllowedPageAction( 'edit' ) && !$nt->isMainPage() ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$message = $this->msg( 'mobile-frontend-editor-edit' )->inLanguage( $lang )->text();
|
2018-09-21 01:38:10 +00:00
|
|
|
$html = Html::openElement( 'span', [ 'class' => 'mw-editsection' ] );
|
2017-07-12 15:12:40 +00:00
|
|
|
$html .= Html::element( 'a', [
|
2019-04-04 12:30:22 +00:00
|
|
|
'href' => $this->getTitle()->getLocalURL( [ 'action' => 'edit', 'section' => $section ] ),
|
2017-07-12 15:12:40 +00:00
|
|
|
'title' => $this->msg( 'editsectionhint', $tooltip )->inLanguage( $lang )->text(),
|
|
|
|
'data-section' => $section,
|
|
|
|
// Note visibility of the edit section link button is controlled by .edit-page in ui.less so
|
|
|
|
// we default to enabled even though this may not be true.
|
2017-08-16 19:18:08 +00:00
|
|
|
'class' => MinervaUI::iconClass( 'edit-enabled', 'element', 'edit-page' ),
|
2017-07-12 15:12:40 +00:00
|
|
|
], $message );
|
|
|
|
$html .= Html::closeElement( 'span' );
|
|
|
|
return $html;
|
|
|
|
}
|
2018-06-18 19:33:03 +00:00
|
|
|
return '';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes a title and returns classes to apply to the body tag
|
|
|
|
* @param Title $title
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getPageClasses( $title ) {
|
|
|
|
$className = parent::getPageClasses( $title );
|
2019-04-10 21:43:50 +00:00
|
|
|
$className .= ' ' . ( $this->skinOptions->get( SkinOptions::OPTIONS_MOBILE_BETA )
|
|
|
|
? 'beta' : 'stable' );
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
if ( $title->isMainPage() ) {
|
|
|
|
$className .= ' page-Main_Page ';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->isAuthenticatedUser() ) {
|
|
|
|
$className .= ' is-authenticated';
|
|
|
|
}
|
2018-11-12 20:32:28 +00:00
|
|
|
// The new treatment should only apply to the main namespace
|
|
|
|
if (
|
|
|
|
$title->getNamespace() === NS_MAIN &&
|
2019-04-10 21:43:50 +00:00
|
|
|
$this->skinOptions->get( SkinOptions::OPTION_PAGE_ISSUES )
|
2018-11-12 20:32:28 +00:00
|
|
|
) {
|
|
|
|
$className .= ' issues-group-B';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
return $className;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether the current user is authenticated or not.
|
|
|
|
* @todo This helper function is only truly needed whilst SkinMobileApp does not support login
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function isAuthenticatedUser() {
|
2017-12-13 01:25:10 +00:00
|
|
|
return $this->getUser()->isLoggedIn();
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the output page contains category links and the category feature is enabled.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
private function hasCategoryLinks() {
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( !$this->skinOptions->get( SkinOptions::OPTION_CATEGORIES ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$categoryLinks = $this->getOutput()->getCategoryLinks();
|
|
|
|
|
|
|
|
if ( !count( $categoryLinks ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !empty( $categoryLinks['normal'] ) || !empty( $categoryLinks['hidden'] );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return SkinUserPageHelper
|
|
|
|
*/
|
|
|
|
public function getUserPageHelper() {
|
2019-04-10 12:52:34 +00:00
|
|
|
return MediaWikiServices::getInstance()->getService( 'Minerva.SkinUserPageHelper' );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes output page and sets up skin-specific parameters
|
|
|
|
* @param OutputPage $out object to initialize
|
|
|
|
*/
|
|
|
|
public function initPage( OutputPage $out ) {
|
|
|
|
parent::initPage( $out );
|
|
|
|
$out->addJsConfigVars( $this->getSkinConfigVariables() );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns, if Extension:Echo should be used.
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function useEcho() {
|
2018-04-15 23:55:36 +00:00
|
|
|
return ExtensionRegistry::getInstance()->isLoaded( 'Echo' );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get Echo notification target user
|
|
|
|
* @param User $user
|
|
|
|
* @return MWEchoNotifUser
|
|
|
|
*/
|
|
|
|
protected function getEchoNotifUser( User $user ) {
|
|
|
|
return MWEchoNotifUser::newFromUser( $user );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
* Get the last time user has seen particular type of notifications.
|
2017-07-12 15:12:40 +00:00
|
|
|
* @param User $user
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
* @param string $type Type of seen time to get
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return string|bool Timestamp in TS_ISO_8601 format, or false if no stored time
|
|
|
|
*/
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
protected function getEchoSeenTime( User $user, $type = 'all' ) {
|
|
|
|
return EchoSeenTime::newFromUser( $user )->getTime( $type, TS_ISO_8601 );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get formatted Echo notification count
|
|
|
|
* @param int $count
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getFormattedEchoNotificationCount( $count ) {
|
|
|
|
return EchoNotificationController::formatNotificationCount( $count );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares the user button.
|
|
|
|
* @param QuickTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function prepareUserButton( QuickTemplate $tpl ) {
|
|
|
|
// Set user button to empty string by default
|
|
|
|
$tpl->set( 'secondaryButtonData', '' );
|
|
|
|
$notificationsTitle = '';
|
2017-10-18 18:28:37 +00:00
|
|
|
$count = 0;
|
2017-07-12 15:12:40 +00:00
|
|
|
$countLabel = '';
|
|
|
|
$isZero = true;
|
|
|
|
$hasUnseen = false;
|
|
|
|
|
|
|
|
$user = $this->getUser();
|
|
|
|
$newtalks = $this->getNewtalks();
|
|
|
|
$currentTitle = $this->getTitle();
|
|
|
|
|
|
|
|
// If Echo is available, the user is logged in, and they are not already on the
|
|
|
|
// notifications archive, show the notifications icon in the header.
|
|
|
|
if ( $this->useEcho() && $user->isLoggedIn() ) {
|
|
|
|
$notificationsTitle = SpecialPage::getTitleFor( 'Notifications' );
|
|
|
|
if ( $currentTitle->equals( $notificationsTitle ) ) {
|
|
|
|
// Don't show the secondary button at all
|
|
|
|
$notificationsTitle = null;
|
|
|
|
} else {
|
|
|
|
$notificationsMsg = $this->msg( 'mobile-frontend-user-button-tooltip' )->text();
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
|
|
|
|
$notifUser = $this->getEchoNotifUser( $user );
|
2017-07-12 15:12:40 +00:00
|
|
|
$count = $notifUser->getNotificationCount();
|
|
|
|
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
$seenAlertTime = $this->getEchoSeenTime( $user, 'alert' );
|
|
|
|
$seenMsgTime = $this->getEchoSeenTime( $user, 'message' );
|
|
|
|
|
|
|
|
$alertNotificationTimestamp = $notifUser->getLastUnreadAlertTime();
|
|
|
|
$msgNotificationTimestamp = $notifUser->getLastUnreadMessageTime();
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
$isZero = $count === 0;
|
Fix seen notifications appearing as unseen on mobile
Important note: Make sure to distinguish unseen from unread
One way to reproduce minerva and non-minerva notification inconsistencies:
- Have all your alerts and notices seen. This is displayed with grayed out
number on vector skin or no number at all, if you have (marked as) read.
- Generate new alert or notice (one is enough) in your preferred way.
- You can check minerva and non-minerva at this step. Both should be in sync.
But don't perform any additional action.
- Open the notification popup in some non-minerva skin (I have tried with
vector and monobook), marking it as seen.
- Check the notification icon in minerva. At this point, you should see
notification displayed as unseen.
The reason bug appeared in the first place is that alert/notice timestamps
were mixed up when seen time is obtained. We get seen time from
EchoSeenTime class, where we get smaller of the two timestamps,
using PHP method `min()`. See I27109ee6a248. Then, we get last unread
notification timestamp (which can be either alert or notice), and compare
that to seen time. That leads to the situation when you have only one of
alerts or notices with unread items, smaller timestamp is used for seen,
and most recent for unread, at which point we compare timestamps for
two separate things.
Previous behavior of getting seen timestamps (using max instead of min) would
probably solve the problem, but some other inconsistencies might arrise.
This should prevent any weird and unpredictable behavior to happen.
Bug: T183076
Change-Id: I20bbd6c590086b1c3eccf82983aad59eb3144a7a
2018-01-10 16:26:29 +00:00
|
|
|
$hasUnseen = $count > 0 &&
|
|
|
|
(
|
|
|
|
$seenMsgTime !== false && $msgNotificationTimestamp !== false &&
|
|
|
|
$seenMsgTime < $msgNotificationTimestamp->getTimestamp( TS_ISO_8601 )
|
|
|
|
) ||
|
|
|
|
(
|
|
|
|
$seenAlertTime !== false && $alertNotificationTimestamp !== false &&
|
|
|
|
$seenAlertTime < $alertNotificationTimestamp->getTimestamp( TS_ISO_8601 )
|
|
|
|
);
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
$countLabel = $this->getFormattedEchoNotificationCount( $count );
|
|
|
|
}
|
|
|
|
} elseif ( !empty( $newtalks ) ) {
|
|
|
|
$notificationsTitle = SpecialPage::getTitleFor( 'Mytalk' );
|
|
|
|
$notificationsMsg = $this->msg( 'mobile-frontend-user-newmessages' )->text();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $notificationsTitle ) {
|
|
|
|
$url = $notificationsTitle->getLocalURL(
|
|
|
|
[ 'returnto' => $currentTitle->getPrefixedText() ] );
|
|
|
|
|
|
|
|
$tpl->set( 'secondaryButtonData', [
|
2017-08-16 19:18:08 +00:00
|
|
|
'notificationIconClass' => MinervaUI::iconClass( 'notifications' ),
|
2017-07-12 15:12:40 +00:00
|
|
|
'title' => $notificationsMsg,
|
|
|
|
'url' => $url,
|
2017-10-18 18:28:37 +00:00
|
|
|
'notificationCountRaw' => $count,
|
|
|
|
'notificationCountString' => $countLabel,
|
2017-07-12 15:12:40 +00:00
|
|
|
'isNotificationCountZero' => $isZero,
|
|
|
|
'hasNotifications' => $hasUnseen,
|
|
|
|
'hasUnseenNotifications' => $hasUnseen
|
|
|
|
] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Rewrites the language list so that it cannot be contaminated by other extensions with things
|
|
|
|
* other than languages
|
|
|
|
* See bug 57094.
|
|
|
|
*
|
|
|
|
* @todo Remove when Special:Languages link goes stable
|
|
|
|
* @param QuickTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function prepareLanguages( $tpl ) {
|
|
|
|
$lang = $this->getTitle()->getPageViewLanguage();
|
|
|
|
$tpl->set( 'pageLang', $lang->getHtmlCode() );
|
|
|
|
$tpl->set( 'pageDir', $lang->getDir() );
|
2018-01-26 18:04:53 +00:00
|
|
|
// If the array is empty, then instead give the skin boolean false
|
|
|
|
$language_urls = $this->getLanguages() ?: false;
|
|
|
|
$tpl->set( 'language_urls', $language_urls );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 );
|
|
|
|
}
|
|
|
|
|
2017-11-22 18:55:24 +00:00
|
|
|
/**
|
|
|
|
* Get a history link which describes author and relative time of last edit
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @param int $timestamp
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getRelativeHistoryLink( Title $title, $timestamp ) {
|
|
|
|
$user = $this->getUser();
|
|
|
|
$text = $this->msg(
|
|
|
|
'minerva-last-modified-date',
|
|
|
|
$this->getLanguage()->userDate( $timestamp, $user ),
|
|
|
|
$this->getLanguage()->userTime( $timestamp, $user )
|
|
|
|
)->parse();
|
|
|
|
return [
|
|
|
|
// Use $edit['timestamp'] (Unix format) instead of $timestamp (MW format)
|
|
|
|
'data-timestamp' => wfTimestamp( TS_UNIX, $timestamp ),
|
|
|
|
'href' => $this->getHistoryUrl( $title ),
|
|
|
|
'text' => $text,
|
|
|
|
] + $this->getRevisionEditorData( $title );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a history link which makes no reference to user or last edited time
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getGenericHistoryLink( Title $title ) {
|
|
|
|
$text = $this->msg( 'mobile-frontend-history' )->plain();
|
|
|
|
return [
|
|
|
|
'href' => $this->getHistoryUrl( $title ),
|
|
|
|
'text' => $text,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the URL for the history page for the given title using Special:History
|
|
|
|
* when available.
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getHistoryUrl( Title $title ) {
|
2019-04-04 16:03:48 +00:00
|
|
|
return ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) &&
|
|
|
|
SpecialMobileHistory::shouldUseSpecialHistory( $title, $this->getUser() ) ?
|
2017-11-22 18:55:24 +00:00
|
|
|
SpecialPage::getTitleFor( 'History', $title )->getLocalURL() :
|
|
|
|
$title->getLocalURL( [ 'action' => 'history' ] );
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Prepare the content for the 'last edited' message, e.g. 'Last edited on 30 August
|
|
|
|
* 2013, at 23:31'. This message is different for the main page since main page
|
2019-01-11 12:13:49 +00:00
|
|
|
* content is typically transcluded rather than edited directly.
|
2017-07-12 15:12:40 +00:00
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return array
|
|
|
|
*/
|
2017-11-22 18:55:24 +00:00
|
|
|
protected function getHistoryLink( Title $title ) {
|
|
|
|
$isLatestRevision = $this->getRevisionId() === $title->getLatestRevID();
|
2017-07-12 15:12:40 +00:00
|
|
|
// Get rev_timestamp of current revision (preloaded by MediaWiki core)
|
|
|
|
$timestamp = $this->getOutput()->getRevisionTimestamp();
|
2017-11-16 23:21:05 +00:00
|
|
|
# No cached timestamp, load it from the database
|
|
|
|
if ( $timestamp === null ) {
|
|
|
|
$timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
|
|
|
|
}
|
|
|
|
|
2017-11-22 18:55:24 +00:00
|
|
|
return !$isLatestRevision || $title->isMainPage() ?
|
|
|
|
$this->getGenericHistoryLink( $title ) :
|
|
|
|
$this->getRelativeHistoryLink( $title, $timestamp );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2017-09-06 15:20:16 +00:00
|
|
|
|
|
|
|
/**
|
2017-11-22 18:55:24 +00:00
|
|
|
* Returns data attributes representing the editor for the current revision.
|
|
|
|
* @param Title $title The Title object of the page being viewed
|
|
|
|
* @return array representing user with name and gender fields. Empty if the editor no longer
|
|
|
|
* exists in the database or is hidden from public view.
|
2017-09-06 15:20:16 +00:00
|
|
|
*/
|
2017-11-22 18:55:24 +00:00
|
|
|
private function getRevisionEditorData( Title $title ) {
|
|
|
|
$rev = Revision::newFromTitle( $title );
|
|
|
|
$result = [];
|
|
|
|
if ( $rev ) {
|
|
|
|
$revUserId = $rev->getUser();
|
|
|
|
// Note the user will only be returned if that information is public
|
|
|
|
if ( $revUserId ) {
|
|
|
|
$revUser = User::newFromId( $revUserId );
|
|
|
|
$editorName = $revUser->getName();
|
|
|
|
$editorGender = $revUser->getOption( 'gender' );
|
|
|
|
$result += [
|
|
|
|
'data-user-name' => $editorName,
|
|
|
|
'data-user-gender' => $editorGender,
|
|
|
|
];
|
|
|
|
}
|
2017-09-06 15:20:16 +00:00
|
|
|
}
|
2017-11-22 18:55:24 +00:00
|
|
|
return $result;
|
2017-09-06 15:20:16 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Returns the HTML representing the tagline
|
|
|
|
* @return string HTML for tagline
|
|
|
|
*/
|
|
|
|
protected function getTaglineHtml() {
|
2017-11-28 21:55:58 +00:00
|
|
|
$tagline = '';
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
if ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
$pageUser = $this->getUserPageHelper()->getPageUser();
|
|
|
|
$fromDate = $pageUser->getRegistration();
|
|
|
|
if ( is_string( $fromDate ) ) {
|
|
|
|
$fromDateTs = wfTimestamp( TS_UNIX, $fromDate );
|
|
|
|
|
|
|
|
// This is shown when js is disabled. js enhancement made due to caching
|
|
|
|
$tagline = $this->msg( 'mobile-frontend-user-page-member-since',
|
|
|
|
$this->getLanguage()->userDate( new MWTimestamp( $fromDateTs ), $this->getUser() ),
|
2018-09-04 05:58:45 +00:00
|
|
|
$pageUser )->text();
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
// Define html attributes for usage with js enhancement (unix timestamp, gender)
|
|
|
|
$attrs = [ 'id' => 'tagline-userpage',
|
|
|
|
'data-userpage-registration-date' => $fromDateTs,
|
|
|
|
'data-userpage-gender' => $pageUser->getOption( 'gender' ) ];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
if ( $title ) {
|
2018-10-31 21:37:17 +00:00
|
|
|
$out = $this->getOutput();
|
|
|
|
$tagline = $out->getProperty( 'wgMFDescription' );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$attrs[ 'class' ] = 'tagline';
|
2017-11-28 21:55:58 +00:00
|
|
|
return Html::element( 'div', $attrs, $tagline );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Returns the HTML representing the heading.
|
|
|
|
* @return string HTML for header
|
|
|
|
*/
|
|
|
|
protected function getHeadingHtml() {
|
|
|
|
if ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
// The heading is just the username without namespace
|
|
|
|
$heading = $this->getUserPageHelper()->getPageUser()->getName();
|
|
|
|
} else {
|
2019-04-02 19:17:14 +00:00
|
|
|
$heading = $this->getOutput()->getPageTitle();
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
return Html::rawElement( 'h1', [ 'id' => 'section_0' ], $heading );
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Create and prepare header and footer content
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function prepareHeaderAndFooter( BaseTemplate $tpl ) {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
$user = $this->getUser();
|
|
|
|
$out = $this->getOutput();
|
2019-01-10 21:41:55 +00:00
|
|
|
$tpl->set( 'taglinehtml', $this->getTaglineHtml() );
|
2017-07-12 15:12:40 +00:00
|
|
|
if ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
$pageUser = $this->getUserPageHelper()->getPageUser();
|
|
|
|
$talkPage = $pageUser->getTalkPage();
|
|
|
|
$data = [
|
2018-01-05 18:52:17 +00:00
|
|
|
// Talk page icon is provided by mobile.userpage.icons for time being
|
|
|
|
'userPageIconClass' => MinervaUI::iconClass( 'talk', 'before', 'talk', 'mf' ),
|
2017-07-12 15:12:40 +00:00
|
|
|
'talkPageTitle' => $talkPage->getPrefixedURL(),
|
2019-04-04 12:30:22 +00:00
|
|
|
'talkPageLink' => $talkPage->getLocalURL(),
|
2017-07-12 15:12:40 +00:00
|
|
|
'talkPageLinkTitle' => $this->msg(
|
|
|
|
'mobile-frontend-user-page-talk' )->escaped(),
|
|
|
|
'contributionsPageLink' => SpecialPage::getTitleFor(
|
|
|
|
'Contributions', $pageUser )->getLocalURL(),
|
|
|
|
'contributionsPageTitle' => $this->msg(
|
|
|
|
'mobile-frontend-user-page-contributions' )->escaped(),
|
|
|
|
'uploadsPageLink' => SpecialPage::getTitleFor(
|
|
|
|
'Uploads', $pageUser )->getLocalURL(),
|
|
|
|
'uploadsPageTitle' => $this->msg(
|
|
|
|
'mobile-frontend-user-page-uploads' )->escaped(),
|
|
|
|
];
|
|
|
|
$templateParser = new TemplateParser( __DIR__ );
|
2019-01-10 21:41:55 +00:00
|
|
|
$tpl->set( 'postheadinghtml',
|
|
|
|
$templateParser->processTemplate( 'user_page_links', $data )
|
|
|
|
);
|
2017-07-12 15:12:40 +00:00
|
|
|
} elseif ( $title->isMainPage() ) {
|
|
|
|
if ( $user->isLoggedIn() ) {
|
|
|
|
$pageTitle = $this->msg(
|
|
|
|
'mobile-frontend-logged-in-homepage-notification', $user->getName() )->text();
|
|
|
|
} else {
|
|
|
|
$pageTitle = '';
|
|
|
|
}
|
|
|
|
$out->setPageTitle( $pageTitle );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->canUseWikiPage() ) {
|
|
|
|
// If it's a page that exists, add last edited timestamp
|
2017-11-16 23:13:27 +00:00
|
|
|
// The relative time is only rendered on the latest revision.
|
2017-09-06 15:26:39 +00:00
|
|
|
// For older revisions the last modified
|
2017-11-16 23:13:27 +00:00
|
|
|
// information will not render with a relative time
|
|
|
|
// nor will it show the name of the editor.
|
|
|
|
if ( $this->getWikiPage()->exists() ) {
|
2017-11-22 18:55:24 +00:00
|
|
|
$tpl->set( 'historyLink', $this->getHistoryLink( $title ) );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
$tpl->set( 'headinghtml', $this->getHeadingHtml() );
|
2017-09-06 15:26:39 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
$tpl->set( 'footer-site-heading-html', $this->getSitename() );
|
|
|
|
// set defaults
|
|
|
|
if ( !isset( $tpl->data['postbodytext'] ) ) {
|
|
|
|
$tpl->set( 'postbodytext', '' ); // not currently set in desktop skin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare the button opens the main side menu
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function prepareMenuButton( BaseTemplate $tpl ) {
|
|
|
|
// menu button
|
2017-08-16 19:32:30 +00:00
|
|
|
$url = SpecialPageFactory::exists( 'MobileMenu' ) ?
|
2019-04-04 12:30:22 +00:00
|
|
|
SpecialPage::getTitleFor( 'MobileMenu' )->getLocalURL() : '#';
|
2017-08-16 19:32:30 +00:00
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
$tpl->set( 'menuButton',
|
|
|
|
Html::element( 'a', [
|
2018-09-04 05:58:45 +00:00
|
|
|
'title' => $this->msg( 'mobile-frontend-main-menu-button-tooltip' )->text(),
|
2017-07-12 15:12:40 +00:00
|
|
|
'href' => $url,
|
2017-08-16 19:18:08 +00:00
|
|
|
'class' => MinervaUI::iconClass( 'mainmenu', 'element', 'main-menu-button' ),
|
2017-09-01 05:01:10 +00:00
|
|
|
'id' => 'mw-mf-main-menu-button',
|
2018-09-04 05:58:45 +00:00
|
|
|
], $this->msg( 'mobile-frontend-main-menu-button-tooltip' )->text() )
|
2017-07-12 15:12:40 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load internal banner content to show in pre content in template
|
|
|
|
* Beware of HTML caching when using this function.
|
|
|
|
* Content set as "internalbanner"
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function prepareBanners( BaseTemplate $tpl ) {
|
|
|
|
// Make sure Zero banner are always on top
|
|
|
|
$banners = [ '<div id="siteNotice"></div>' ];
|
|
|
|
if ( $this->getConfig()->get( 'MinervaEnableSiteNotice' ) ) {
|
|
|
|
$siteNotice = $this->getSiteNotice();
|
|
|
|
if ( $siteNotice ) {
|
|
|
|
$banners[] = $siteNotice;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$tpl->set( 'banners', $banners );
|
|
|
|
// These banners unlike 'banners' show inside the main content chrome underneath the
|
|
|
|
// page actions.
|
|
|
|
$tpl->set( 'internalBanner', '' );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with details for a language button.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getLanguageButton() {
|
|
|
|
$languageUrl = SpecialPage::getTitleFor(
|
|
|
|
'MobileLanguages',
|
|
|
|
$this->getSkin()->getTitle()
|
|
|
|
)->getLocalURL();
|
|
|
|
|
|
|
|
return [
|
|
|
|
'attributes' => [
|
|
|
|
'class' => 'language-selector',
|
|
|
|
'href' => $languageUrl,
|
|
|
|
],
|
|
|
|
'label' => $this->msg( 'mobile-frontend-language-article-heading' )->text()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with details for a talk button.
|
|
|
|
* @param Title $talkTitle Title object of the talk page
|
|
|
|
* @param array $talkButton Array with data of desktop talk button
|
2017-09-28 20:06:39 +00:00
|
|
|
* @param bool $addSection (optional) when added the talk button will render
|
|
|
|
* as an add topic button. Defaults to false.
|
2017-07-12 15:12:40 +00:00
|
|
|
* @return array
|
|
|
|
*/
|
2017-09-28 20:06:39 +00:00
|
|
|
protected function getTalkButton( $talkTitle, $talkButton, $addSection = false ) {
|
|
|
|
if ( $addSection ) {
|
|
|
|
$params = [ 'action' => 'edit', 'section' => 'new' ];
|
|
|
|
$className = 'talk continue add';
|
|
|
|
} else {
|
|
|
|
$params = [];
|
|
|
|
$className = 'talk';
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
return [
|
|
|
|
'attributes' => [
|
2017-09-28 20:06:39 +00:00
|
|
|
'href' => $talkTitle->getLinkURL( $params ),
|
2017-07-12 15:12:40 +00:00
|
|
|
'data-title' => $talkTitle->getFullText(),
|
2017-09-28 20:06:39 +00:00
|
|
|
'class' => $className,
|
2017-07-12 15:12:40 +00:00
|
|
|
],
|
|
|
|
'label' => $talkButton['text'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with details for a categories button.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getCategoryButton() {
|
|
|
|
return [
|
|
|
|
'attributes' => [
|
|
|
|
'href' => '#/categories',
|
|
|
|
// add hidden class (the overlay works only, when JS is enabled (class will
|
|
|
|
// be removed in categories/init.js)
|
|
|
|
'class' => 'category-button hidden',
|
|
|
|
],
|
|
|
|
'label' => $this->msg( 'categories' )->text()
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of links for page secondary actions
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
protected function getSecondaryActions( BaseTemplate $tpl ) {
|
|
|
|
$buttons = [];
|
|
|
|
|
|
|
|
// always add a button to link to the talk page
|
|
|
|
// in beta it will be the entry point for the talk overlay feature,
|
|
|
|
// in stable it will link to the wikitext talk page
|
|
|
|
$title = $this->getTitle();
|
2019-02-19 23:44:53 +00:00
|
|
|
$subjectPage = $title->getSubjectPage();
|
2019-04-10 21:43:50 +00:00
|
|
|
$talkAtBottom = !$this->skinOptions->get( SkinOptions::OPTIONS_TALK_AT_TOP ) ||
|
2019-02-19 23:44:53 +00:00
|
|
|
$subjectPage->isMainPage();
|
2017-07-12 15:12:40 +00:00
|
|
|
$namespaces = $tpl->data['content_navigation']['namespaces'];
|
2019-02-19 23:44:53 +00:00
|
|
|
if ( !$this->getUserPageHelper()->isUserPage()
|
|
|
|
&& $this->isTalkAllowed() && $talkAtBottom
|
2019-01-10 22:59:56 +00:00
|
|
|
) {
|
2017-07-12 15:12:40 +00:00
|
|
|
// FIXME [core]: This seems unnecessary..
|
|
|
|
$subjectId = $title->getNamespaceKey( '' );
|
|
|
|
$talkId = $subjectId === 'main' ? 'talk' : "{$subjectId}_talk";
|
2017-09-28 20:06:39 +00:00
|
|
|
|
|
|
|
if ( isset( $namespaces[$talkId] ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
$talkButton = $namespaces[$talkId];
|
|
|
|
$talkTitle = $title->getTalkPage();
|
2017-09-28 20:06:39 +00:00
|
|
|
if ( $title->isTalkPage() ) {
|
|
|
|
$talkButton['text'] = wfMessage( 'minerva-talk-add-topic' );
|
|
|
|
$buttons['talk'] = $this->getTalkButton( $title, $talkButton, true );
|
|
|
|
} else {
|
|
|
|
$buttons['talk'] = $this->getTalkButton( $talkTitle, $talkButton );
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->doesPageHaveLanguages && $title->isMainPage() ) {
|
|
|
|
$buttons['language'] = $this->getLanguageButton();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->hasCategoryLinks() ) {
|
|
|
|
$buttons['categories'] = $this->getCategoryButton();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $buttons;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare configured and available page actions
|
|
|
|
*
|
2019-02-25 19:50:56 +00:00
|
|
|
* If a page action should display a placeholder element
|
|
|
|
* (i.e. it will be hydrated on the client with JS) add the 'jsonly' CSS class to
|
|
|
|
* the 'class' key of its array.
|
2017-07-12 15:12:40 +00:00
|
|
|
*
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
*/
|
|
|
|
protected function preparePageActions( BaseTemplate $tpl ) {
|
2019-04-04 21:20:39 +00:00
|
|
|
$toolbar = [];
|
|
|
|
$overflowMenu = null;
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2019-02-25 19:50:56 +00:00
|
|
|
if ( $this->isAllowedPageAction( 'switch-language' ) ) {
|
2019-04-04 21:20:39 +00:00
|
|
|
$toolbar[] = $this->createSwitchLanguageAction();
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->isAllowedPageAction( 'watch' ) ) {
|
|
|
|
// SkinTemplate#buildContentNavigationUrls creates distinct "watch" and "unwatch" actions.
|
|
|
|
// Pass these actions in as context for #createWatchPageAction.
|
|
|
|
$actions = $tpl->data['content_navigation']['actions'];
|
|
|
|
|
2019-04-04 21:20:39 +00:00
|
|
|
$toolbar[] = $this->createWatchPageAction( $actions );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 11:22:35 +00:00
|
|
|
if ( $this->isAllowedPageAction( 'history' ) ) {
|
2019-04-04 21:20:39 +00:00
|
|
|
$toolbar[] = $this->getHistoryPageAction();
|
2019-02-28 14:21:44 +00:00
|
|
|
}
|
|
|
|
|
2019-02-25 19:50:56 +00:00
|
|
|
if ( $this->isAllowedPageAction( 'edit' ) ) {
|
2019-04-04 21:20:39 +00:00
|
|
|
$toolbar[] = $this->createEditPageAction();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $this->isAllowedPageAction( SkinOptions::OPTION_OVERFLOW_SUBMENU ) ) {
|
|
|
|
$overflowMenu = $this->newToolbarOverflowMenu( $tpl );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 21:20:39 +00:00
|
|
|
$tpl->set( 'page_actions', [
|
|
|
|
'toolbar' => $toolbar,
|
|
|
|
'overflowMenu' => $overflowMenu
|
|
|
|
] );
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates the "edit" page action: the well-known pencil icon that, when tapped, will open an
|
|
|
|
* editor with the lead section loaded.
|
|
|
|
*
|
2019-02-25 19:50:56 +00:00
|
|
|
* @return array A map of HTML attributes and a "text" property to be used with the
|
|
|
|
* pageActionMenu.mustache template.
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function createEditPageAction() {
|
2017-08-22 14:12:32 +00:00
|
|
|
$title = $this->getTitle();
|
2018-11-08 21:38:18 +00:00
|
|
|
$user = $this->getUser();
|
2017-08-22 14:12:32 +00:00
|
|
|
$editArgs = [ 'action' => 'edit' ];
|
|
|
|
if ( $title->isWikitextPage() ) {
|
|
|
|
// If the content model is wikitext we'll default to editing the lead section.
|
2018-09-21 01:38:10 +00:00
|
|
|
// Full wikitext editing is hard on mobile devices.
|
2017-08-22 14:12:32 +00:00
|
|
|
$editArgs['section'] = self::LEAD_SECTION_NUMBER;
|
|
|
|
}
|
2018-11-08 21:38:18 +00:00
|
|
|
$userQuickEditCheck = $title->quickUserCan( 'edit', $user )
|
|
|
|
&& ( $title->exists() || $title->quickUserCan( 'create', $user ) );
|
|
|
|
$userBlockInfo = $user->getId() == 0 ? false : $user->isBlockedFrom( $title, true );
|
|
|
|
$userCanEdit = $userQuickEditCheck && !$userBlockInfo;
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
return [
|
2019-03-25 12:15:09 +00:00
|
|
|
'item-id' => 'page-actions-edit',
|
2019-02-25 19:50:56 +00:00
|
|
|
'id' => 'ca-edit',
|
|
|
|
'href' => $title->getLocalURL( $editArgs ),
|
|
|
|
'class' => 'edit-page '
|
|
|
|
. MinervaUI::iconClass( $userCanEdit ? 'edit-enabled' : 'edit', 'element' ),
|
|
|
|
'title' => $this->msg( 'mobile-frontend-pageaction-edit-tooltip' ),
|
|
|
|
'text' => $this->msg( 'mobile-frontend-editor-edit' )
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @param array $actions
|
2019-02-25 19:50:56 +00:00
|
|
|
* @return array A map of HTML attributes and a "text" property to be used with the
|
|
|
|
* pageActionMenu.mustache template.
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function createWatchPageAction( $actions ) {
|
2018-10-10 21:19:55 +00:00
|
|
|
$title = $this->getTitle();
|
|
|
|
$user = $this->getUser();
|
|
|
|
$ctaUrl = $this->getLoginUrl( [ 'returnto' => $title ] );
|
|
|
|
if ( $title && $user->isWatched( $title ) ) {
|
|
|
|
$icon = 'watched';
|
|
|
|
} else {
|
|
|
|
$icon = 'watch';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
$baseResult = [
|
2019-03-25 12:15:09 +00:00
|
|
|
'item-id' => 'page-actions-watch',
|
2017-07-12 15:12:40 +00:00
|
|
|
'id' => 'ca-watch',
|
|
|
|
// Use blank icon to reserve space for watchstar icon once JS loads
|
2019-02-25 19:50:56 +00:00
|
|
|
'class' => MinervaUI::iconClass( $icon, 'element', 'watch-this-article' ) . ' jsonly',
|
|
|
|
'title' => $this->msg( 'watchthispage' ),
|
|
|
|
'text' => $this->msg( 'watchthispage' )
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
if ( isset( $actions['watch'] ) ) {
|
|
|
|
$result = array_merge( $actions['watch'], $baseResult );
|
|
|
|
} elseif ( isset( $actions['unwatch'] ) ) {
|
|
|
|
$result = array_merge( $actions['unwatch'], $baseResult );
|
|
|
|
$result['class'] .= ' watched';
|
2019-02-25 19:50:56 +00:00
|
|
|
$result[ 'text' ] = $this->msg( 'unwatchthispage' );
|
2017-07-12 15:12:40 +00:00
|
|
|
} else {
|
|
|
|
// placeholder for not logged in
|
|
|
|
$result = array_merge( $baseResult, [
|
2018-10-10 21:19:55 +00:00
|
|
|
'href' => $ctaUrl,
|
2017-07-12 15:12:40 +00:00
|
|
|
] );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-08-23 22:27:22 +00:00
|
|
|
* Creates the "switch-language" action: the icon that, when tapped, opens the language
|
2017-07-12 15:12:40 +00:00
|
|
|
* switcher.
|
|
|
|
*
|
2019-02-25 19:50:56 +00:00
|
|
|
* @return array A map of HTML attributes and a 'text' property to be used with the
|
|
|
|
* pageActionMenu.mustache template.
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function createSwitchLanguageAction() {
|
2019-02-25 19:50:56 +00:00
|
|
|
$languageSwitcherLink = false;
|
|
|
|
$languageSwitcherClasses = ' language-selector';
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
if ( $this->doesPageHaveLanguages ) {
|
2019-02-25 19:50:56 +00:00
|
|
|
$languageSwitcherLink = SpecialPage::getTitleFor(
|
|
|
|
'MobileLanguages',
|
|
|
|
$this->getTitle()
|
|
|
|
)->getLocalURL();
|
2017-07-12 15:12:40 +00:00
|
|
|
} else {
|
|
|
|
$languageSwitcherClasses .= ' disabled';
|
|
|
|
}
|
|
|
|
return [
|
2019-03-25 12:15:09 +00:00
|
|
|
'item-id' => 'page-actions-languages',
|
2019-02-25 19:50:56 +00:00
|
|
|
'class' => MinervaUI::iconClass( 'language-switcher', 'element', $languageSwitcherClasses ),
|
|
|
|
'href' => $languageSwitcherLink,
|
|
|
|
'title' => $this->msg( 'mobile-frontend-language-article-heading' ),
|
|
|
|
'text' => $this->msg( 'mobile-frontend-language-article-heading' )
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-05-07 20:21:10 +00:00
|
|
|
/**
|
|
|
|
* Minerva skin do not have sidebar, there is no need to calculate that.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function buildSidebar() {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2019-02-28 14:21:44 +00:00
|
|
|
/**
|
|
|
|
* Creates a history action: An icon that links to the mobile history page.
|
|
|
|
*
|
|
|
|
* @return array A map of HTML attributes and a 'text' property to be used with the
|
|
|
|
* pageActionMenu.mustache template.
|
|
|
|
*/
|
|
|
|
protected function getHistoryPageAction() {
|
2019-03-07 20:50:57 +00:00
|
|
|
return [
|
2019-03-25 12:15:09 +00:00
|
|
|
'item-id' => 'page-actions-history',
|
2019-03-21 14:50:23 +00:00
|
|
|
'class' => MinervaUI::iconClass( 'clock' ),
|
2019-02-28 14:21:44 +00:00
|
|
|
'text' => $this->msg( 'mobile-frontend-history' ),
|
|
|
|
'href' => $this->getHistoryUrl( $this->getTitle() )
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-05-07 20:23:57 +00:00
|
|
|
/**
|
|
|
|
* Minerva skin do use any of those, there is no need to calculate that
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function buildPersonalUrls() {
|
|
|
|
return [];
|
|
|
|
}
|
2019-04-04 21:20:39 +00:00
|
|
|
/**
|
|
|
|
* 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 empty( $pageActions ) ? null : [
|
|
|
|
'item-id' => 'page-actions-overflow',
|
|
|
|
'class' => MinervaUI::iconClass( 'page-actions-overflow' ),
|
|
|
|
'text' => $this->msg( 'minerva-page-actions-overflow' ),
|
|
|
|
'pageActions' => $pageActions
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getDefaultOverflowPageActions( BaseTemplate $tpl ) {
|
|
|
|
return array_values( array_filter( [
|
|
|
|
$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
|
|
|
|
)
|
|
|
|
] ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param BaseTemplate $tpl
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function getUserNamespaceOverflowPageActions( BaseTemplate $tpl ) {
|
|
|
|
$pageUser = $this->getUserPageHelper()->getPageUser();
|
|
|
|
return [
|
|
|
|
$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
|
|
|
|
)
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $name
|
|
|
|
* @param string $icon Wikimedia UI icon name.
|
|
|
|
* @param string|null $href
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
private function newOverflowPageAction( $name, $icon, $href ) {
|
|
|
|
return $href ? [
|
|
|
|
'item-id' => 'page-actions-overflow-' . $name,
|
|
|
|
'class' => MinervaUI::iconClass( '', 'before', 'wikimedia-ui-' . $icon . '-base20' ),
|
|
|
|
'text' => $this->msg( 'minerva-page-actions-' . $name ),
|
|
|
|
'href' => $href
|
|
|
|
] : null;
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
/**
|
|
|
|
* Checks whether the editor can handle the existing content handler type.
|
|
|
|
*
|
2017-07-24 16:53:04 +00:00
|
|
|
* @return bool
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function isCurrentPageContentModelEditable() {
|
2019-04-10 12:52:34 +00:00
|
|
|
$contentHandler = MediaWikiServices::getInstance()
|
|
|
|
->getService( 'Minerva.ContentHandler' );
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
return $contentHandler->supportsDirectEditing()
|
|
|
|
&& $contentHandler->supportsDirectApiEditing();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns array of config variables that should be added only to this skin
|
|
|
|
* for use in JavaScript.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getSkinConfigVariables() {
|
|
|
|
$vars = [
|
2019-04-10 21:43:50 +00:00
|
|
|
'wgMinervaFeatures' => $this->skinOptions->getAll(),
|
2017-11-28 21:17:27 +00:00
|
|
|
'wgMinervaDownloadNamespaces' => $this->getConfig()->get( 'MinervaDownloadNamespaces' ),
|
2019-04-08 17:08:57 +00:00
|
|
|
'wgMinervaMenuData' => $this->getMainMenu()->getMenuData(),
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
return $vars;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true, if the page can have a talk page and user is logged in.
|
2017-07-24 16:53:04 +00:00
|
|
|
* @return bool
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function isTalkAllowed() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
return $this->isAllowedPageAction( 'talk' ) &&
|
2017-09-28 20:06:39 +00:00
|
|
|
( $title->isTalkPage() || $title->canHaveTalkPage() ) &&
|
2017-07-12 15:12:40 +00:00
|
|
|
$this->getUser()->isLoggedIn();
|
|
|
|
}
|
|
|
|
|
2017-10-05 17:17:38 +00:00
|
|
|
/**
|
2017-07-12 15:12:40 +00:00
|
|
|
* Returns true, if the talk page of this page is wikitext-based.
|
2017-07-24 16:53:04 +00:00
|
|
|
* @return bool
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
protected function isWikiTextTalkPage() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
if ( !$title->isTalkPage() ) {
|
|
|
|
$title = $title->getTalkPage();
|
|
|
|
}
|
2017-08-22 14:12:32 +00:00
|
|
|
return $title->isWikitextPage();
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of modules related to the current context of the page.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getContextSpecificModules() {
|
|
|
|
$modules = [];
|
|
|
|
$user = $this->getUser();
|
|
|
|
$title = $this->getTitle();
|
|
|
|
|
2019-04-19 12:36:02 +00:00
|
|
|
if ( !$title->isSpecialPage() && $this->isAllowedPageAction( 'watch' ) ) {
|
|
|
|
// Explicitly add the mobile watchstar code.
|
|
|
|
$modules[] = 'skins.minerva.watchstar';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 12:36:02 +00:00
|
|
|
if ( $user->isLoggedIn() && $this->useEcho() ) {
|
|
|
|
$modules[] = 'skins.minerva.notifications';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TalkOverlay feature
|
|
|
|
if (
|
|
|
|
$this->getUserPageHelper()->isUserPage() ||
|
|
|
|
( $this->isTalkAllowed() || $title->isTalkPage() ) &&
|
|
|
|
$this->isWikiTextTalkPage()
|
|
|
|
) {
|
|
|
|
$modules[] = 'skins.minerva.talk';
|
|
|
|
}
|
|
|
|
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $this->skinOptions->hasSkinOptions() ) {
|
2018-09-26 23:03:20 +00:00
|
|
|
$modules[] = 'skins.minerva.options';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $this->skinOptions->get( SkinOptions::OPTION_SHARE_BUTTON ) ) {
|
2018-09-17 23:37:57 +00:00
|
|
|
$modules[] = 'skins.minerva.share';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
return $modules;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the javascript entry modules to load. Only modules that need to
|
|
|
|
* be overriden or added conditionally should be placed here.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getDefaultModules() {
|
|
|
|
$modules = parent::getDefaultModules();
|
|
|
|
// dequeue default content modules (toc, sortable, collapsible, etc.)
|
|
|
|
$modules['content'] = [];
|
2018-04-26 20:04:04 +00:00
|
|
|
// dequeue styles associated with `content` key.
|
|
|
|
$modules['styles']['content'] = [];
|
2018-05-04 23:40:59 +00:00
|
|
|
$modules['styles']['core'] = $this->getSkinStyles();
|
2017-07-12 15:12:40 +00:00
|
|
|
// dequeue default watch module (not needed, no watchstar in this skin)
|
|
|
|
$modules['watch'] = [];
|
|
|
|
// disable default skin search modules
|
|
|
|
$modules['search'] = [];
|
|
|
|
|
|
|
|
$modules['minerva'] = array_merge(
|
|
|
|
$this->getContextSpecificModules(),
|
|
|
|
[
|
2017-08-11 16:44:49 +00:00
|
|
|
'skins.minerva.scripts'
|
2017-07-12 15:12:40 +00:00
|
|
|
]
|
|
|
|
);
|
|
|
|
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $this->skinOptions->get( SkinOptions::OPTION_TOGGLING ) ) {
|
2017-07-12 15:12:40 +00:00
|
|
|
// Extension can unload "toggling" modules via the hook
|
|
|
|
$modules['toggling'] = [ 'skins.minerva.toggling' ];
|
|
|
|
}
|
|
|
|
|
|
|
|
Hooks::run( 'SkinMinervaDefaultModules', [ $this, &$modules ] );
|
|
|
|
|
|
|
|
return $modules;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modifies the `<body>` element's attributes.
|
|
|
|
*
|
|
|
|
* By default, the `class` attribute is set to the output's "bodyClassName"
|
|
|
|
* property.
|
|
|
|
*
|
|
|
|
* @param OutputPage $out
|
2017-10-05 17:17:38 +00:00
|
|
|
* @param array &$bodyAttrs
|
2017-07-12 15:12:40 +00:00
|
|
|
*/
|
|
|
|
public function addToBodyAttributes( $out, &$bodyAttrs ) {
|
|
|
|
$classes = $out->getProperty( 'bodyClassName' );
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $this->skinOptions->get( SkinOptions::OPTION_AMC ) ) {
|
2019-03-29 21:31:09 +00:00
|
|
|
$classes .= ' minerva--amc-enabled';
|
|
|
|
} else {
|
|
|
|
$classes .= ' minerva--amc-disabled';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
|
|
|
|
$bodyAttrs[ 'class' ] .= ' ' . $classes;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the needed styles for this skin
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getSkinStyles() {
|
|
|
|
$title = $this->getTitle();
|
|
|
|
$styles = [
|
|
|
|
'skins.minerva.base.styles',
|
|
|
|
'skins.minerva.content.styles',
|
2018-06-14 16:51:50 +00:00
|
|
|
'skins.minerva.content.styles.images',
|
2017-06-01 23:09:24 +00:00
|
|
|
'mediawiki.hlist',
|
2017-07-12 15:12:40 +00:00
|
|
|
'mediawiki.ui.icon',
|
|
|
|
'mediawiki.ui.button',
|
2019-04-25 18:58:16 +00:00
|
|
|
'skins.minerva.icons.images',
|
|
|
|
'wikimedia.ui'
|
2017-07-12 15:12:40 +00:00
|
|
|
];
|
|
|
|
if ( $title->isMainPage() ) {
|
|
|
|
$styles[] = 'skins.minerva.mainPage.styles';
|
2018-02-14 00:14:54 +00:00
|
|
|
} elseif ( $this->getUserPageHelper()->isUserPage() ) {
|
|
|
|
$styles[] = 'skins.minerva.userpage.styles';
|
|
|
|
$styles[] = 'skins.minerva.userpage.icons';
|
2017-07-12 15:12:40 +00:00
|
|
|
}
|
2017-12-13 01:25:10 +00:00
|
|
|
if ( $this->isAuthenticatedUser() ) {
|
|
|
|
$styles[] = 'skins.minerva.loggedin.styles';
|
|
|
|
$styles[] = 'skins.minerva.icons.loggedin';
|
|
|
|
}
|
2017-07-12 15:12:40 +00:00
|
|
|
|
2019-04-10 21:43:50 +00:00
|
|
|
if ( $this->skinOptions->get( SkinOptions::OPTION_AMC ) ) {
|
2019-01-10 22:59:56 +00:00
|
|
|
$styles[] = 'skins.minerva.amc.styles';
|
|
|
|
}
|
|
|
|
|
2017-07-12 15:12:40 +00:00
|
|
|
return $styles;
|
|
|
|
}
|
|
|
|
}
|
2017-11-08 00:05:20 +00:00
|
|
|
|
|
|
|
// Setup alias for compatibility with SkinMinervaNeue.
|
|
|
|
class_alias( 'SkinMinerva', 'SkinMinervaNeue' );
|