Add user rights 'viewmywatchlist', 'editmywatchlist'

These are needed for OAuth grants.

Note that, even if 'editmywatchlist' is not granted, various actions
will still allow for adding but not removing of pages.

Change-Id: Ie33446a228dd6ed0114730935c1bf65667f5ce01
This commit is contained in:
Brad Jorsch 2013-06-13 14:02:55 -04:00
parent 3d14c52a47
commit 18062eb3b0
26 changed files with 196 additions and 78 deletions

View file

@ -28,9 +28,9 @@ production.
* $wgLogAutopatrol added to allow disabling logging of autopatrol edits in the logging table.
default for $wgLogAutopatrol is true.
* The 'edit' right no longer allows for editing a user's own CSS and JS.
* New rights 'editmyusercss' and 'editmyuserjs' restrict actions that were
formerly allowed by default. They have been added to the default for
$wgGroupPermissions['*'].
* New rights 'editmyusercss', 'editmyuserjs', 'viewmywatchlist',
and 'editmywatchlist' restrict actions that were formerly allowed by default.
They have been added to the default for $wgGroupPermissions['*'].
=== New features in 1.22 ===
* (bug 44525) mediawiki.jqueryMsg can now parse (whitelisted) HTML elements and attributes.
@ -108,6 +108,8 @@ production.
for extensions such as OAuth:
** editmyusercss controls whether a user may edit their own CSS subpages.
** editmyuserjs controls whether a user may edit their own JS subpages.
** viewmywatchlist controls whether a user may view their watchlist.
** editmywatchlist controls whether a user may edit their watchlist.
* Add new hook AbortTalkPageEmailNotification, this will be used to determine
whether to send the regular talk page email notification
* (bug 46513) Vector: Add the collapsibleTabs script from the Vector extension.

View file

@ -1546,13 +1546,7 @@ class Article implements Page {
$this->doDelete( $reason, $suppress );
if ( $user->isLoggedIn() && $request->getCheck( 'wpWatch' ) != $user->isWatched( $title ) ) {
if ( $request->getCheck( 'wpWatch' ) ) {
WatchAction::doWatch( $title, $user );
} else {
WatchAction::doUnwatch( $title, $user );
}
}
WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
return;
}

View file

@ -3873,6 +3873,8 @@ $wgGroupPermissions['*']['createtalk'] = true;
$wgGroupPermissions['*']['writeapi'] = true;
$wgGroupPermissions['*']['editmyusercss'] = true;
$wgGroupPermissions['*']['editmyuserjs'] = true;
$wgGroupPermissions['*']['viewmywatchlist'] = true;
$wgGroupPermissions['*']['editmywatchlist'] = true;
#$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled
// Implicit group for all logged-in accounts

View file

@ -1743,7 +1743,9 @@ class EditPage {
protected function updateWatchlist() {
global $wgUser;
if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
if ( $wgUser->isLoggedIn()
&& $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
) {
$fname = __METHOD__;
$title = $this->mTitle;
$watch = $this->watchthis;
@ -1752,11 +1754,7 @@ class EditPage {
$dbw = wfGetDB( DB_MASTER );
$dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
$dbw->begin( $fname );
if ( $watch ) {
WatchAction::doWatch( $title, $wgUser );
} else {
WatchAction::doUnwatch( $title, $wgUser );
}
WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser );
$dbw->commit( $fname );
} );
}

View file

@ -120,13 +120,7 @@ class FileDeleteForm {
// file, otherwise go back to the description page
$wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() );
if ( $wgUser->isLoggedIn() && $wgRequest->getCheck( 'wpWatch' ) != $wgUser->isWatched( $this->title ) ) {
if ( $wgRequest->getCheck( 'wpWatch' ) ) {
WatchAction::doWatch( $this->title, $wgUser );
} else {
WatchAction::doUnwatch( $this->title, $wgUser );
}
}
WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'wpWatch' ), $this->title, $wgUser );
}
return;
}

View file

@ -330,13 +330,8 @@ class ProtectionForm {
return false;
}
if ( $wgUser->isLoggedIn() && $wgRequest->getCheck( 'mwProtectWatch' ) != $wgUser->isWatched( $this->mTitle ) ) {
if ( $wgRequest->getCheck( 'mwProtectWatch' ) ) {
WatchAction::doWatch( $this->mTitle, $wgUser );
} else {
WatchAction::doUnwatch( $this->mTitle, $wgUser );
}
}
WatchAction::doWatchOrUnwatch( $wgRequest->getCheck( 'mwProtectWatch' ), $this->mTitle, $wgUser );
return true;
}

View file

@ -613,12 +613,15 @@ class SkinTemplate extends Skin {
'href' => $href,
'active' => ( $href == $pageurl )
);
$href = self::makeSpecialUrl( 'Watchlist' );
$personal_urls['watchlist'] = array(
'text' => $this->msg( 'mywatchlist' )->text(),
'href' => $href,
'active' => ( $href == $pageurl )
);
if ( $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$href = self::makeSpecialUrl( 'Watchlist' );
$personal_urls['watchlist'] = array(
'text' => $this->msg( 'mywatchlist' )->text(),
'href' => $href,
'active' => ( $href == $pageurl )
);
}
# We need to do an explicit check for Special:Contributions, as we
# have to match both the title, and the target, which could come
@ -992,7 +995,7 @@ class SkinTemplate extends Skin {
wfProfileOut( __METHOD__ . '-live' );
// Checks if the user is logged in
if ( $this->loggedin ) {
if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
/**
* The following actions use messages which, if made particular to
* the any specific skins, would break the Ajax code which makes this

View file

@ -4531,7 +4531,7 @@ class Title {
if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
return $this->mNotificationTimestamp[$uid];
}
if ( !$uid || !$wgShowUpdatedMarker ) {
if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) {
return $this->mNotificationTimestamp[$uid] = false;
}
// Don't cache too much!

View file

@ -126,6 +126,7 @@ class User {
'editprotected',
'editmyusercss',
'editmyuserjs',
'editmywatchlist',
'editusercssjs', #deprecated
'editusercss',
'edituserjs',
@ -166,6 +167,7 @@ class User {
'upload_by_url',
'userrights',
'userrights-interwiki',
'viewmywatchlist',
'writeapi',
);
/**
@ -2860,11 +2862,14 @@ class User {
/**
* Get a WatchedItem for this user and $title.
*
* @since 1.22 $checkRights parameter added
* @param $title Title
* @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
* @return WatchedItem
*/
public function getWatchedItem( $title ) {
$key = $title->getNamespace() . ':' . $title->getDBkey();
public function getWatchedItem( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
$key = $checkRights . ':' . $title->getNamespace() . ':' . $title->getDBkey();
if ( isset( $this->mWatchedItems[$key] ) ) {
return $this->mWatchedItems[$key];
@ -2874,34 +2879,43 @@ class User {
$this->mWatchedItems = array();
}
$this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title );
$this->mWatchedItems[$key] = WatchedItem::fromUserTitle( $this, $title, $checkRights );
return $this->mWatchedItems[$key];
}
/**
* Check the watched status of an article.
* @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
* @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
* @return bool
*/
public function isWatched( $title ) {
return $this->getWatchedItem( $title )->isWatched();
public function isWatched( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
return $this->getWatchedItem( $title, $checkRights )->isWatched();
}
/**
* Watch an article.
* @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
* @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
public function addWatch( $title ) {
$this->getWatchedItem( $title )->addWatch();
public function addWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
$this->getWatchedItem( $title, $checkRights )->addWatch();
$this->invalidateCache();
}
/**
* Stop watching an article.
* @since 1.22 $checkRights parameter added
* @param $title Title of the article to look at
* @param $checkRights int Whether to check 'viewmywatchlist'/'editmywatchlist' rights.
* Pass WatchedItem::CHECK_USER_RIGHTS or WatchedItem::IGNORE_USER_RIGHTS.
*/
public function removeWatch( $title ) {
$this->getWatchedItem( $title )->removeWatch();
public function removeWatch( $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
$this->getWatchedItem( $title, $checkRights )->removeWatch();
$this->invalidateCache();
}
@ -2909,6 +2923,7 @@ class User {
* Clear the user's notification timestamp for the given title.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of the page if it's watched etc.
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
* @param $title Title of the article to look at
*/
public function clearNotification( &$title ) {
@ -2919,6 +2934,11 @@ class User {
return;
}
// Do nothing if not allowed to edit the watchlist
if ( !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
if ( $title->getNamespace() == NS_USER_TALK &&
$title->getText() == $this->getName() ) {
if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) ) {
@ -2954,12 +2974,18 @@ class User {
* Resets all of the given user's page-change notification timestamps.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of any watched page.
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
*/
public function clearAllNotifications() {
if ( wfReadOnly() ) {
return;
}
// Do nothing if not allowed to edit the watchlist
if ( !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
global $wgUseEnotif, $wgShowUpdatedMarker;
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
$this->setNewtalk( false );
@ -4587,4 +4613,26 @@ class User {
'user_editcount',
);
}
/**
* Factory function for fatal permission-denied errors
*
* @since 1.22
* @param string $permission User right required
* @return Status
*/
static function newFatalPermissionDeniedStatus( $permission ) {
global $wgLang;
$groups = array_map(
array( 'User', 'makeGroupLinkWiki' ),
User::getGroupsWithPermission( $permission )
);
if ( $groups ) {
return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
} else {
return Status::newFatal( 'badaccess-group0' );
}
}
}

View file

@ -27,19 +27,37 @@
* @ingroup Watchlist
*/
class WatchedItem {
var $mTitle, $mUser;
/**
* Constant to specify that user rights 'editmywatchlist' and
* 'viewmywatchlist' should not be checked.
* @since 1.22
*/
const IGNORE_USER_RIGHTS = 0;
/**
* Constant to specify that user rights 'editmywatchlist' and
* 'viewmywatchlist' should be checked.
* @since 1.22
*/
const CHECK_USER_RIGHTS = 1;
var $mTitle, $mUser, $mCheckRights;
private $loaded = false, $watched, $timestamp;
/**
* Create a WatchedItem object with the given user and title
* @since 1.22 $checkRights parameter added
* @param $user User: the user to use for (un)watching
* @param $title Title: the title we're going to (un)watch
* @param $checkRights int: Whether to check the 'viewmywatchlist' and 'editmywatchlist' rights.
* Pass either WatchedItem::IGNORE_USER_RIGHTS or WatchedItem::CHECK_USER_RIGHTS.
* @return WatchedItem object
*/
public static function fromUserTitle( $user, $title ) {
public static function fromUserTitle( $user, $title, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
$wl = new WatchedItem;
$wl->mUser = $user;
$wl->mTitle = $title;
$wl->mCheckRights = $checkRights;
return $wl;
}
@ -110,11 +128,23 @@ class WatchedItem {
}
}
/**
* Check permissions
* @param $what string: 'viewmywatchlist' or 'editmywatchlist'
*/
private function isAllowed( $what ) {
return !$this->mCheckRights || $this->mUser->isAllowed( $what );
}
/**
* Is mTitle being watched by mUser?
* @return bool
*/
public function isWatched() {
if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
return false;
}
$this->load();
return $this->watched;
}
@ -126,6 +156,10 @@ class WatchedItem {
* the wl_notificationtimestamp field otherwise
*/
public function getNotificationTimestamp() {
if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
return false;
}
$this->load();
if ( $this->watched ) {
return $this->timestamp;
@ -142,7 +176,7 @@ class WatchedItem {
*/
public function resetNotificationTimestamp( $force = '' ) {
// Only loggedin user can have a watchlist
if ( wfReadOnly() || $this->mUser->isAnon() ) {
if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
@ -170,7 +204,7 @@ class WatchedItem {
wfProfileIn( __METHOD__ );
// Only loggedin user can have a watchlist
if ( wfReadOnly() || $this->mUser->isAnon() ) {
if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
wfProfileOut( __METHOD__ );
return false;
}
@ -210,7 +244,7 @@ class WatchedItem {
wfProfileIn( __METHOD__ );
// Only loggedin user can have a watchlist
if ( wfReadOnly() || $this->mUser->isAnon() ) {
if ( wfReadOnly() || $this->mUser->isAnon() || !$this->isAllowed( 'editmywatchlist' ) ) {
wfProfileOut( __METHOD__ );
return false;
}

View file

@ -88,19 +88,45 @@ class WatchAction extends FormAction {
}
/**
* Watch a page
* @since 1.22 Returns Status object
* Watch or unwatch a page
* @since 1.22
* @param bool $watch Whether to watch or unwatch the page
* @param Title $title Page to watch/unwatch
* @param User $user User who is watching/unwatching
* @return Status
*/
public static function doWatch( Title $title, User $user ) {
public static function doWatchOrUnwatch( $watch, Title $title, User $user ) {
if ( $user->isLoggedIn() && $user->isWatched( $title, WatchedItem::IGNORE_USER_RIGHTS ) != $watch ) {
// If the user doesn't have 'editmywatchlist', we still want to
// allow them to add but not remove items via edits and such.
if ( $watch ) {
return self::doWatch( $title, $user, WatchedItem::IGNORE_USER_RIGHTS );
} else {
return self::doUnwatch( $title, $user );
}
}
return Status::newGood();
}
/**
* Watch a page
* @since 1.22 Returns Status, $checkRights parameter added
* @param Title $title Page to watch/unwatch
* @param User $user User who is watching/unwatching
* @param int $checkRights Passed through to $user->addWatch()
* @return Status
*/
public static function doWatch( Title $title, User $user, $checkRights = WatchedItem::CHECK_USER_RIGHTS ) {
if ( $checkRights !== WatchedItem::IGNORE_USER_RIGHTS && !$user->isAllowed( 'editmywatchlist' ) ) {
return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
}
$page = WikiPage::factory( $title );
$status = Status::newFatal( 'hookaborted' );
if ( wfRunHooks( 'WatchArticle', array( &$user, &$page, &$status ) ) ) {
$status = Status::newGood();
$user->addWatch( $title );
$user->addWatch( $title, $checkRights );
wfRunHooks( 'WatchArticleComplete', array( &$user, &$page ) );
}
return $status;
@ -114,6 +140,10 @@ class WatchAction extends FormAction {
* @return Status
*/
public static function doUnwatch( Title $title, User $user ) {
if ( !$user->isAllowed( 'editmywatchlist' ) ) {
return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
}
$page = WikiPage::factory( $title );
$status = Status::newFatal( 'hookaborted' );

View file

@ -823,7 +823,7 @@ abstract class ApiBase extends ContextSource {
*/
protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
$userWatching = $this->getUser()->isWatched( $titleObj );
$userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS );
switch ( $watchlist ) {
case 'watch':
@ -865,12 +865,7 @@ abstract class ApiBase extends ContextSource {
return;
}
$user = $this->getUser();
if ( $value ) {
WatchAction::doWatch( $titleObj, $user );
} else {
WatchAction::doUnwatch( $titleObj, $user );
}
WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
/**
@ -1583,6 +1578,9 @@ abstract class ApiBase extends ContextSource {
if ( !$this->getUser()->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
}
$user = $this->getUser();
}
return $user;

View file

@ -669,7 +669,9 @@ class ApiQueryInfo extends ApiQueryBase {
private function getWatchedInfo() {
$user = $this->getUser();
if ( $user->isAnon() || count( $this->everything ) == 0 ) {
if ( $user->isAnon() || count( $this->everything ) == 0
|| !$user->isAllowed( 'viewmywatchlist' )
) {
return;
}

View file

@ -39,6 +39,9 @@ class ApiSetNotificationTimestamp extends ApiBase {
if ( $user->isAnon() ) {
$this->dieUsage( 'Anonymous users cannot use watchlist change notifications', 'notloggedin' );
}
if ( !$user->isAllowed( 'editmywatchlist' ) ) {
$this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
}
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' );

View file

@ -36,6 +36,9 @@ class ApiWatch extends ApiBase {
if ( !$user->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
if ( !$user->isAllowed( 'editmywatchlist' ) ) {
$this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' );
}
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );

View file

@ -753,7 +753,7 @@ class SpecialBlock extends FormSpecialPage {
# Can't watch a rangeblock
if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
$performer->addWatch( Title::makeTitle( NS_USER, $target ) );
WatchAction::doWatch( Title::makeTitle( NS_USER, $target ), $performer, WatchedItem::IGNORE_USER_RIGHTS );
}
# Block constructor sanitizes certain block options on insert

View file

@ -49,7 +49,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
private $badItems = array();
public function __construct() {
parent::__construct( 'EditWatchlist' );
parent::__construct( 'EditWatchlist', 'editmywatchlist' );
}
/**

View file

@ -690,13 +690,8 @@ class MovePageForm extends UnlistedSpecialPage {
}
# Deal with watches (we don't watch subpages)
if ( $this->watch && $user->isLoggedIn() ) {
$user->addWatch( $ot );
$user->addWatch( $nt );
} else {
$user->removeWatch( $ot );
$user->removeWatch( $nt );
}
WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
# Re-clear the file redirect cache, which may have been polluted by
# parsing in messages above. See CR r56745.

View file

@ -393,7 +393,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$fields = RecentChange::selectFields();
// JOIN on watchlist for users
if ( $uid ) {
if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';

View file

@ -99,7 +99,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
// left join with watchlist table to highlight watched rows
$uid = $this->getUser()->getId();
if ( $uid ) {
if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$select[] = 'wl_user';
$join_conds['watchlist'] = array( 'LEFT JOIN', array(

View file

@ -26,8 +26,8 @@ class SpecialWatchlist extends SpecialPage {
/**
* Constructor
*/
public function __construct( $page = 'Watchlist' ) {
parent::__construct( $page );
public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
parent::__construct( $page, $restriction );
}
/**
@ -54,6 +54,9 @@ class SpecialWatchlist extends SpecialPage {
return;
}
// Check permissions
$this->checkPermissions();
// Add feed links
$wlToken = $user->getOption( 'watchlisttoken' );
if ( !$wlToken ) {

View file

@ -688,7 +688,7 @@ abstract class UploadBase {
if ( $status->isGood() ) {
if ( $watch ) {
$user->addWatch( $this->getLocalFile()->getTitle() );
WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
}
wfRunHooks( 'UploadComplete', array( &$this ) );
}

View file

@ -2095,6 +2095,8 @@ Your email address is not revealed when other users contact you.',
'right-edituserjs' => "Edit other users' JavaScript files",
'right-editmyusercss' => 'Edit your own user CSS files',
'right-editmyuserjs' => 'Edit your own user JavaScript files',
'right-viewmywatchlist' => 'View your own watchlist',
'right-editmywatchlist' => 'Edit your own watchlist. Note some actions will still add pages even without this right.',
'right-rollback' => 'Quickly rollback the edits of the last user who edited a particular page',
'right-markbotedits' => 'Mark rolled-back edits as bot edits',
'right-noratelimit' => 'Not be affected by rate limits',
@ -2156,6 +2158,8 @@ Your email address is not revealed when other users contact you.',
'action-userrights-interwiki' => 'edit user rights of users on other wikis',
'action-siteadmin' => 'lock or unlock the database',
'action-sendemail' => 'send emails',
'action-viewmywatchlist' => 'view your watchlist',
'action-editmywatchlist' => 'edit your watchlist',
# Recent changes
'nchanges' => '$1 {{PLURAL:$1|change|changes}}',

View file

@ -2914,6 +2914,8 @@ See also {{msg-mw|Right-editusercss}}',
'right-editmyuserjs' => '{{doc-right|editmyuserjs}}
See also {{msg-mw|Right-edituserjs}}',
'right-viewmywatchlist' => '{{doc-right|viewmywatchlist}}',
'right-editmywatchlist' => '{{doc-right|editmywatchlist}}',
'right-rollback' => '{{doc-right|rollback}}
{{Identical|Rollback}}',
'right-markbotedits' => '{{doc-right|markbotedits}}
@ -2986,6 +2988,8 @@ This action allows editing of all of the "user rights", not just the rights of t
'action-userrights-interwiki' => '{{Doc-action|userrights-interwiki}}',
'action-siteadmin' => '{{Doc-action|siteadmin}}',
'action-sendemail' => '{{doc-action|sendemail}}',
'action-editmywatchlist' => '{{doc-action|editmywatchlist}}',
'action-viewmywatchlist' => '{{doc-action|viewmywatchlist}}',
# Recent changes
'nchanges' => 'Appears on the [[Special:RecentChanges]] special page in brackets after pages having more than one change on that date. $1 is the number of changes on that day.',

View file

@ -1286,6 +1286,7 @@ edititis
editlink
editmyusercss
editmyuserjs
editmywatchlist
editnotice
editnotsupported
editondblclick
@ -4342,6 +4343,7 @@ view
viewcount
viewdeleted
viewhelppage
viewmywatchlist
viewprevnext
viewsource
viewsourcelink

View file

@ -1222,6 +1222,8 @@ $wgMessageStructure = array(
'right-edituserjs',
'right-editmyusercss',
'right-editmyuserjs',
'right-viewmywatchlist',
'right-editmywatchlist',
'right-rollback',
'right-markbotedits',
'right-noratelimit',
@ -1283,6 +1285,8 @@ $wgMessageStructure = array(
'action-userrights-interwiki',
'action-siteadmin',
'action-sendemail',
'action-editmywatchlist',
'action-viewmywatchlist',
),
'recentchanges' => array(
'nchanges',