* Split patrol code
authorRoan Kattouw <catrope@users.mediawiki.org>
Thu, 4 Sep 2008 15:17:51 +0000 (15:17 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Thu, 4 Sep 2008 15:17:51 +0000 (15:17 +0000)
** 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
docs/hooks.txt
includes/Article.php
includes/AutoLoader.php
includes/RecentChange.php
includes/api/ApiBase.php
includes/api/ApiMain.php
includes/api/ApiPatrol.php [new file with mode: 0644]
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php

index a4bef36..a9f250f 100644 (file)
@@ -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 ===
 
index f17f317..2d2fe94 100644 (file)
@@ -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
index 32d81fc..f43a5a1 100644 (file)
@@ -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
index 6ae1045..0ca2c94 100644 (file)
@@ -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',
index f5d64bd..c22c9ea 100644 (file)
@@ -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__
                );
index ae78e19..387b2b5 100644 (file)
@@ -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"),
index c115fef..ef05326 100644 (file)
@@ -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 (file)
index 0000000..1da02f1
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+/*
+ * Created on Sep 2, 2008
+ *
+ * API for MediaWiki 1.14+
+ *
+ * Copyright (C) 2008 Soxred93 soxred93@gmail.com,
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+       require_once ('ApiBase.php');
+}
+
+class ApiPatrol extends ApiBase {
+
+       public function __construct($main, $action) {
+               parent :: __construct($main, $action);
+       }
+
+       /**
+        * Patrols the article or provides the reason the patrol failed.
+        */
+       public function execute() {
+               global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol;
+               $this->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$';
+       }
+}
index 14d6354..822e22a 100644 (file)
@@ -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'
index 7ff3a6f..72fcea7 100644 (file)
@@ -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;