From bd3bf5d2a7e6b4daf6c7ee10c505832eae83a539 Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Thu, 4 Sep 2008 15:17:51 +0000 Subject: [PATCH] * Split patrol code ** Backend code moved to RecentChange::reallyMarkPatrolled() and doMarkPatrolled() ** Adapted Article::markpatrolled() * (bug 15466) Added action=patrol to the API. Modified patch by Soxred93 * Added rctoken=patrol to list=recentchanges * Detect duplicate warnings in ApiBase::setWarning() --- RELEASE-NOTES | 1 + docs/hooks.txt | 11 +++ includes/Article.php | 72 ++++++------------- includes/AutoLoader.php | 1 + includes/RecentChange.php | 54 +++++++++++++-- includes/api/ApiBase.php | 9 ++- includes/api/ApiMain.php | 1 + includes/api/ApiPatrol.php | 95 ++++++++++++++++++++++++++ includes/api/ApiQueryRecentChanges.php | 69 +++++++++++++++++-- includes/api/ApiQueryRevisions.php | 2 +- 10 files changed, 248 insertions(+), 67 deletions(-) create mode 100644 includes/api/ApiPatrol.php diff --git a/RELEASE-NOTES b/RELEASE-NOTES index a4bef36edb..a9f250fc5c 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -212,6 +212,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * (bug 15444) action=edit returns "Unknown error: ``AS_END''" where it should return just "Unknown error" * (bug 15448) YAML output returns empty values instead of 0 +* (bug 15445) Added action=patrol === Languages updated in 1.14 === diff --git a/docs/hooks.txt b/docs/hooks.txt index f17f3175e4..2d2fe94350 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -295,6 +295,17 @@ associated Revision object. In the hook, just add your callback to the $tokenFunctions array and return true (returning false makes no sense) $tokenFunctions: array(action => callback) +'APIQueryRecentChangesTokens': use this hook to add custom tokens to prop=revisions. +Every token has an action, which will be used in the rctoken parameter +and in the output (actiontoken="..."), and a callback function which +should return the token, or false if the user isn't allowed to obtain +it. The prototype of the callback function is func($pageid, $title, $rc) +where $pageid is the page ID of the page associated to the revision the +token is requested for, $title the associated Title object and $rc the +associated RecentChange object. In the hook, just add your callback to the +$tokenFunctions array and return true (returning false makes no sense) +$tokenFunctions: array(action => callback) + 'ArticleAfterFetchContent': after fetching content of an article from the database $article: the article (object) being loaded from the database $content: the content (string) of the article diff --git a/includes/Article.php b/includes/Article.php index 32d81fcc81..f43a5a1c5a 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1631,73 +1631,41 @@ class Article { global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUseNPPatrol, $wgUser; $wgOut->setRobotPolicy( 'noindex,nofollow' ); - # Check patrol config options - - if ( !($wgUseNPPatrol || $wgUseRCPatrol)) { - $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); - return; - } - # If we haven't been given an rc_id value, we can't do anything $rcid = (int) $wgRequest->getVal('rcid'); - $rc = $rcid ? RecentChange::newFromId($rcid) : null; - if ( is_null ( $rc ) ) + $rc = RecentChange::newFromId($rcid); + if (is_null($rc)) { $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); return; } - if ( !$wgUseRCPatrol && $rc->getAttribute( 'rc_type' ) != RC_NEW) { - // Only new pages can be patrolled if the general patrolling is off....??? - // @fixme -- is this necessary? Shouldn't we only bother controlling the - // front end here? + #It would be nice to see where the user had actually come from, but for now just guess + $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges'; + $return = Title::makeTitle( NS_SPECIAL, $returnto ); + + $errors = $rc->doMarkPatrolled(); + if (in_array(array('rcpatroldisabled'), $errors)) { $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); return; } - - # Check permissions - $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser ); - - if (count($permission_errors)>0) - { - $wgOut->showPermissionsErrorPage( $permission_errors ); + + if (in_array(array('hookaborted'), $errors)) { + // The hook itself has handled any output return; } - - # Handle the 'MarkPatrolled' hook - if( !wfRunHooks( 'MarkPatrolled', array( $rcid, &$wgUser, false ) ) ) { + + if (in_array(array('markedaspatrollederror-noautopatrol'), $errors)) { + $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) ); + $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' ); + $wgOut->returnToMain( false, $return ); return; } - #It would be nice to see where the user had actually come from, but for now just guess - $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges'; - $return = Title::makeTitle( NS_SPECIAL, $returnto ); - - # If it's left up to us, check that the user is allowed to patrol this edit - # If the user has the "autopatrol" right, then we'll assume there are no - # other conditions stopping them doing so - if( !$wgUser->isAllowed( 'autopatrol' ) ) { - $rc = RecentChange::newFromId( $rcid ); - # Graceful error handling, as we've done before here... - # (If the recent change doesn't exist, then it doesn't matter whether - # the user is allowed to patrol it or not; nothing is going to happen - if( is_object( $rc ) && $wgUser->getName() == $rc->getAttribute( 'rc_user_text' ) ) { - # The user made this edit, and can't patrol it - # Tell them so, and then back off - $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) ); - $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' ); - $wgOut->returnToMain( false, $return ); - return; - } - } - - # Check that the revision isn't patrolled already - # Prevents duplicate log entries - if( !$rc->getAttribute( 'rc_patrolled' ) ) { - # Mark the edit as patrolled - RecentChange::markPatrolled( $rcid ); - PatrolLog::record( $rcid ); - wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + if (!empty($errors)) + { + $wgOut->showPermissionsErrorPage( $errors ); + return; } # Inform the user diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 6ae1045a97..0ca2c94eef 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -238,6 +238,7 @@ $wgAutoloadLocalClasses = array( 'ApiPageSet' => 'includes/api/ApiPageSet.php', 'ApiParamInfo' => 'includes/api/ApiParamInfo.php', 'ApiParse' => 'includes/api/ApiParse.php', + 'ApiPatrol' => 'includes/api/ApiPatrol.php', 'ApiProtect' => 'includes/api/ApiProtect.php', 'ApiQuery' => 'includes/api/ApiQuery.php', 'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php', diff --git a/includes/RecentChange.php b/includes/RecentChange.php index f5d64bd57d..c22c9ea5a5 100644 --- a/includes/RecentChange.php +++ b/includes/RecentChange.php @@ -249,12 +249,56 @@ class RecentChange * Mark a given change as patrolled * * @param mixed $change RecentChange or corresponding rc_id - * @returns integer number of affected rows + * @return See doMarkPatrolled(), or null if $change is not an existing rc_id */ public static function markPatrolled( $change ) { - $rcid = $change instanceof RecentChange - ? $change->mAttribs['rc_id'] - : $change; + $change = $change instanceof RecentChange + ? $change + : RecentChange::newFromId($change); + if(!$change instanceof RecentChange) + return null; + return $change->doMarkPatrolled(); + } + + /** + * Mark this RecentChange as patrolled + * + * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and 'markedaspatrollederror-noautopatrol' as errors + * @return array of permissions errors, see Title::getUserPermissionsErrors() + */ + public function doMarkPatrolled() { + global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol; + $errors = array(); + // If recentchanges patrol is disabled, only new pages + // can be patrolled + if(!$wgUseRCPatrol && (!$wgUseNPPatrol || $this->getAttribute('rc_type') != RC_NEW)) + $errors[] = array('rcpatroldisabled'); + $errors = array_merge($errors, $this->getTitle()->getUserPermissionsErrors('patrol', $wgUser)); + if(!wfRunHooks('MarkPatrolled', array($this->getAttribute('rc_id'), &$wgUser, false))) + $errors[] = array('hookaborted'); + // Users without the 'autopatrol' right can't patrol their + // own revisions + if($wgUser->getName() == $this->getAttribute('rc_user_text') && + !$wgUser->isAllowed('autopatrol')) + $errors[] = array('markedaspatrollederror-noautopatrol'); + if(!empty($errors)) + return $errors; + + // If the change was patrolled already, do nothing + if($this->getAttribute('rc_patrolled')) + return array(); + $this->reallyMarkPatrolled(); + PatrolLog::record($this); + wfRunHooks('MarkPatrolledComplete', + array($this->getAttribute('rc_id'), &$wgUser, false)); + return array(); + } + + /** + * Mark this RecentChange patrolled, without error checking + * @return int Number of affected rows + */ + public function reallyMarkPatrolled() { $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'recentchanges', @@ -262,7 +306,7 @@ class RecentChange 'rc_patrolled' => 1 ), array( - 'rc_id' => $rcid + 'rc_id' => $this->getAttribute('rc_id') ), __METHOD__ ); diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index ae78e19ffc..387b2b5380 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -159,6 +159,10 @@ abstract class ApiBase { $data =& $this->getResult()->getData(); if(isset($data['warnings'][$this->getModuleName()])) { + # Don't add duplicate warnings + $warn_regex = preg_quote($warning, '/'); + if(preg_match("/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'])) + return; $warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning"; unset($data['warnings'][$this->getModuleName()]); } @@ -654,8 +658,8 @@ abstract class ApiBase { 'blockedemailuser' => array('code' => 'blockedfrommail', 'info' => "You have been blocked from sending e-mail"), 'notarget' => array('code' => 'notarget', 'info' => "You have not specified a valid target for this action"), 'noemail' => array('code' => 'noemail', 'info' => "The user has not specified a valid e-mail address, or has chosen not to receive e-mail from other users"), - - + 'rcpatroldisabled' => array('code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki"), + 'markedaspatrollederror-noautopatrol' => array('code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes"), // API-specific messages 'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"), @@ -675,6 +679,7 @@ abstract class ApiBase { 'permdenied-undelete' => array('code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions"), 'createonly-exists' => array('code' => 'articleexists', 'info' => "The article you tried to create has been created already"), 'nocreate-missing' => array('code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist"), + 'nosuchrcid' => array('code' => 'nosuchrcid', 'info' => "There is no change with rcid ``$1''"), // ApiEditPage messages 'noimageredirect-anon' => array('code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects"), diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index c115fefe37..ef053267cd 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -78,6 +78,7 @@ class ApiMain extends ApiBase { 'edit' => 'ApiEditPage', 'emailuser' => 'ApiEmailUser', 'watch' => 'ApiWatch', + 'patrol' => 'ApiPatrol', ); /** diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php new file mode 100644 index 0000000000..1da02f1556 --- /dev/null +++ b/includes/api/ApiPatrol.php @@ -0,0 +1,95 @@ +getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); + + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!isset($params['rcid'])) + $this->dieUsageMsg(array('missingparam', 'rcid')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $rc = RecentChange::newFromID($params['rcid']); + if(!$rc instanceof RecentChange) + $this->dieUsageMsg(array('nosuchrcid', $params['rcid'])); + $retval = RecentChange::markPatrolled($params['rcid']); + + if(!empty($retval)) + $this->dieUsageMsg(current($retval)); + + $result = array('rcid' => $rc->getAttribute('rc_id')); + ApiQueryBase::addTitleInfo($result, $rc->getTitle()); + $this->getResult()->addValue(null, $this->getModuleName(), $result); + } + + public function getAllowedParams() { + return array ( + 'token' => null, + 'rcid' => array( + ApiBase :: PARAM_TYPE => 'integer' + ), + ); + } + + public function getParamDescription() { + return array ( + 'token' => 'Patrol token obtained from list=recentchanges', + 'rcid' => 'Recentchanges ID to patrol', + ); + } + + public function getDescription() { + return array ( + 'Patrol a page or revision. ' + ); + } + + protected function getExamples() { + return array( + 'api.php?action=patrol&token=123abc&rcid=230672766' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id$'; + } +} diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 14d63548b1..822e22ab6a 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -43,13 +43,48 @@ class ApiQueryRecentChanges extends ApiQueryBase { private $fld_comment = false, $fld_user = false, $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false, $fld_sizes = false; + + protected function getTokenFunctions() { + // tokenname => function + // function prototype is func($pageid, $title, $rev) + // should return token or false + + // Don't call the hooks twice + if(isset($this->tokenFunctions)) + return $this->tokenFunctions; + + // If we're in JSON callback mode, no tokens can be obtained + if(!is_null($this->getMain()->getRequest()->getVal('callback'))) + return array(); + + $this->tokenFunctions = array( + 'patrol' => array( 'ApiQueryRecentChanges', 'getPatrolToken' ) + ); + wfRunHooks('APIQueryRecentChangesTokens', array(&$this->tokenFunctions)); + return $this->tokenFunctions; + } + + public static function getPatrolToken($pageid, $title, $rc) + { + global $wgUser; + if(!$wgUser->isAllowed('patrol')) + return false; + + // The patrol token is always the same, let's exploit that + static $cachedPatrolToken = null; + if(!is_null($cachedPatrolToken)) + return $cachedPatrolToken; + + $cachedPatrolToken = $wgUser->editToken(); + return $cachedPatrolToken; + } /** * Generates and outputs the result of this query based upon the provided parameters. */ public function execute() { /* Initialize vars */ - $limit = $prop = $namespace = $titles = $show = $type = $dir = $start = $end = null; + $limit = $prop = $namespace = $titles = $show = $type = $dir = $start = $end = $token = null; /* Get the parameters of the request. */ extract($this->extractRequestParams()); @@ -125,6 +160,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'rc_timestamp', 'rc_namespace', 'rc_title', + 'rc_cur_id', 'rc_type', 'rc_moved_to_ns', 'rc_moved_to_title' @@ -151,7 +187,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Add fields to our query if they are specified as a needed parameter. */ $this->addFieldsIf('rc_id', $this->fld_ids); - $this->addFieldsIf('rc_cur_id', $this->fld_ids); $this->addFieldsIf('rc_this_oldid', $this->fld_ids); $this->addFieldsIf('rc_last_oldid', $this->fld_ids); $this->addFieldsIf('rc_comment', $this->fld_comment); @@ -170,6 +205,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addFields('page_is_redirect'); } } + $this->token = $token; /* Specify the limit for our query. It's $limit+1 because we (possibly) need to * generate a "continue" parameter, to allow paging. */ $this->addOption('LIMIT', $limit +1); @@ -228,11 +264,11 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Determine what kind of change this was. */ switch ( $type ) { - case RC_EDIT: $vals['type'] = 'edit'; break; - case RC_NEW: $vals['type'] = 'new'; break; - case RC_MOVE: $vals['type'] = 'move'; break; - case RC_LOG: $vals['type'] = 'log'; break; - case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break; + case RC_EDIT: $vals['type'] = 'edit'; break; + case RC_NEW: $vals['type'] = 'new'; break; + case RC_MOVE: $vals['type'] = 'move'; break; + case RC_LOG: $vals['type'] = 'log'; break; + case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break; default: $vals['type'] = $type; } @@ -290,6 +326,20 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Add the patrolled flag */ if ($this->fld_patrolled && $row->rc_patrolled == 1) $vals['patrolled'] = ''; + + if(!is_null($this->token)) + { + $tokenFunctions = $this->getTokenFunctions(); + foreach($this->token as $t) + { + $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, + $title, RecentChange::newFromRow($row)); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $vals[$t . 'token'] = $val; + } + } return $vals; } @@ -348,6 +398,10 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'patrolled' ) ), + 'token' => array( + ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), + ApiBase :: PARAM_ISMULTI => true + ), 'show' => array ( ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array ( @@ -389,6 +443,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'namespace' => 'Filter log entries to only this namespace(s)', 'titles' => 'Filter log entries to only these page titles', 'prop' => 'Include additional pieces of information', + 'token' => 'Which tokens to obtain for each change', 'show' => array ( 'Show only items that meet this criteria.', 'For example, to see only minor edits done by logged-in users, set show=minor|!anon' diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 7ff3a6fed8..72fcea78d3 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -58,7 +58,7 @@ class ApiQueryRevisions extends ApiQueryBase { return array(); $this->tokenFunctions = array( - 'rollback' => array( 'ApiQueryRevisions','getRollbackToken' ) + 'rollback' => array( 'ApiQueryRevisions', 'getRollbackToken' ) ); wfRunHooks('APIQueryRevisionsTokens', array(&$this->tokenFunctions)); return $this->tokenFunctions; -- 2.20.1