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
* $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.
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.
$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;
}
$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
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;
$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 );
} );
}
// 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;
}
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;
}
'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
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
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!
'editprotected',
'editmyusercss',
'editmyuserjs',
+ 'editmywatchlist',
'editusercssjs', #deprecated
'editusercss',
'edituserjs',
'upload_by_url',
'userrights',
'userrights-interwiki',
+ 'viewmywatchlist',
'writeapi',
);
/**
/**
* 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];
$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();
}
* 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 ) {
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 ) ) ) {
* 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 );
'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' );
+ }
+ }
}
* @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;
}
}
}
+ /**
+ * 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;
}
* the wl_notificationtimestamp field otherwise
*/
public function getNotificationTimestamp() {
+ if ( !$this->isAllowed( 'viewmywatchlist' ) ) {
+ return false;
+ }
+
$this->load();
if ( $this->watched ) {
return $this->timestamp;
*/
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;
}
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;
}
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;
}
return parent::checkCanExecute( $user );
}
+ /**
+ * 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 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 object
+ * @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 ) {
+ 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;
* @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' );
*/
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':
return;
}
- $user = $this->getUser();
- if ( $value ) {
- WatchAction::doWatch( $titleObj, $user );
- } else {
- WatchAction::doUnwatch( $titleObj, $user );
- }
+ WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
/**
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;
private function getWatchedInfo() {
$user = $this->getUser();
- if ( $user->isAnon() || count( $this->everything ) == 0 ) {
+ if ( $user->isAnon() || count( $this->everything ) == 0
+ || !$user->isAllowed( 'viewmywatchlist' )
+ ) {
return;
}
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' );
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'] );
# 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
private $badItems = array();
public function __construct() {
- parent::__construct( 'EditWatchlist' );
+ parent::__construct( 'EditWatchlist', 'editmywatchlist' );
}
/**
}
# 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.
$fields = RecentChange::selectFields();
// JOIN on watchlist for users
- if ( $uid ) {
+ if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
// 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(
/**
* Constructor
*/
- public function __construct( $page = 'Watchlist' ) {
- parent::__construct( $page );
+ public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
+ parent::__construct( $page, $restriction );
}
/**
return;
}
+ // Check permissions
+ $this->checkPermissions();
+
// Add feed links
$wlToken = $user->getOption( 'watchlisttoken' );
if ( !$wlToken ) {
if ( $status->isGood() ) {
if ( $watch ) {
- $user->addWatch( $this->getLocalFile()->getTitle() );
+ WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS );
}
wfRunHooks( 'UploadComplete', array( &$this ) );
}
'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',
'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}}',
'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}}
'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.',
editlink
editmyusercss
editmyuserjs
+editmywatchlist
editnotice
editnotsupported
editondblclick
viewcount
viewdeleted
viewhelppage
+viewmywatchlist
viewprevnext
viewsource
viewsourcelink
'right-edituserjs',
'right-editmyusercss',
'right-editmyuserjs',
+ 'right-viewmywatchlist',
+ 'right-editmywatchlist',
'right-rollback',
'right-markbotedits',
'right-noratelimit',
'action-userrights-interwiki',
'action-siteadmin',
'action-sendemail',
+ 'action-editmywatchlist',
+ 'action-viewmywatchlist',
),
'recentchanges' => array(
'nchanges',