return true;
}
- /**
- * Revert a modification
- */
- function rollback() {
- global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
-
- if( $wgUser->isAllowed( 'rollback' ) ) {
- if( $wgUser->isBlocked() ) {
- $wgOut->blockedPage();
- return;
- }
- } else {
- $wgOut->permissionRequired( 'rollback' );
- return;
- }
-
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage( $this->getContent() );
- return;
- }
- if( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ),
- array( $this->mTitle->getPrefixedText(),
- $wgRequest->getVal( 'from' ) ) ) ) {
- $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
- $wgOut->addWikiText( wfMsg( 'sessionfailure' ) );
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
-
- # Enhanced rollback, marks edits rc_bot=1
- $bot = $wgRequest->getBool( 'bot' );
-
- # Replace all this user's current edits with the next one down
-
- # Get the last editor
- $current = Revision::newFromTitle( $this->mTitle );
- if( is_null( $current ) ) {
- # Something wrong... no page?
- $wgOut->addHTML( wfMsg( 'notanarticle' ) );
- return;
- }
-
- $from = str_replace( '_', ' ', $wgRequest->getVal( 'from' ) );
- if( $from != $current->getUserText() ) {
- $wgOut->setPageTitle( wfMsg('rollbackfailed') );
- $wgOut->addWikiText( wfMsg( 'alreadyrolled',
- htmlspecialchars( $this->mTitle->getPrefixedText()),
- htmlspecialchars( $from ),
- htmlspecialchars( $current->getUserText() ) ) );
- if( $current->getComment() != '') {
- $wgOut->addHTML(
- wfMsg( 'editcomment',
- $wgUser->getSkin()->formatComment( $current->getComment() ) ) );
- }
- return;
- }
-
- # Get the last edit not by this guy
- $user = intval( $current->getUser() );
- $user_text = $dbw->addQuotes( $current->getUserText() );
- $s = $dbw->selectRow( 'revision',
- array( 'rev_id', 'rev_timestamp' ),
+ /** Backend rollback implementation. UI logic is in rollback()
+ * @param string $user - Name of the user whose edits to rollback.
+ * @param string $token - Rollback token.
+ * @param bool $bot - If true, mark all reverted edits as bot.
+ * @param string $summary - Custom summary. Set to default summary if empty.
+ * @param array $info - Reference to associative array that will be set to contain the revision ID, edit summary, etc.
+ * @return ROLLBACK_SUCCES on succes, ROLLBACK_* on failure
+ */
+ public function doRollback($user, $token, $bot = false, $summary = "", &$info = NULL)
+ {
+ global $wgUser, $wgUseRCPatrol;
+ if(!$wgUser->isAllowed('rollback'))
+ return ROLLBACK_PERM;
+ if($wgUser->isBlocked())
+ return ROLLBACK_BLOCKED;
+ if(wfReadOnly())
+ return ROLLBACK_READONLY;
+
+ // Check token first
+ if(!$wgUser->matchEditToken($token, array($this->mTitle->getPrefixedText(), $user)))
+ return ROLLBACK_BADTOKEN;
+
+ $dbw = wfGetDB(DB_MASTER);
+ $current = Revision::newFromTitle($this->mTitle);
+ if(is_null($current))
+ return ROLLBACK_BADARTICLE;
+
+ // Check if someone else was there first
+ if($user != $current->getUserText())
+ {
+ $info['usertext'] = $current->getUserText();
+ $info['comment'] = $current->getComment();
+ return ROLLBACK_ALREADYROLLED;
+ }
+ // Get the last edit not by $user
+ $userid = intval($current->getUser());
+ $s = $dbw->selectRow('revision',
+ array('rev_id', 'rev_timestamp'),
array(
'rev_page' => $current->getPage(),
- "rev_user <> {$user} OR rev_user_text <> {$user_text}"
+ "rev_user <> $userid OR rev_user_text <> {$dbw->addQuotes($user)}"
), __METHOD__,
array(
'USE INDEX' => 'page_timestamp',
- 'ORDER BY' => 'rev_timestamp DESC' )
- );
- if( $s === false ) {
- # Something wrong
- $wgOut->setPageTitle(wfMsg('rollbackfailed'));
- $wgOut->addHTML( wfMsg( 'cantrollback' ) );
- return;
- }
+ 'ORDER BY' => 'rev_timestamp DESC'
+ ));
+ if($s === false)
+ return ROLLBACK_ONLYAUTHOR;
+ $target = Revision::newFromID($s->rev_id);
+ // If the reverted edits should be marked bot or patrolled, do so
$set = array();
- if ( $bot ) {
- # Mark all reverted edits as bot
+ if($bot)
$set['rc_bot'] = 1;
- }
- if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
+ if($wgUseRCPatrol)
$set['rc_patrolled'] = 1;
- }
-
- if ( $set ) {
- $dbw->update( 'recentchanges', $set,
- array( /* WHERE */
- 'rc_cur_id' => $current->getPage(),
- 'rc_user_text' => $current->getUserText(),
- "rc_timestamp > '{$s->rev_timestamp}'",
- ), __METHOD__
- );
- }
-
- # Get the edit summary
- $target = Revision::newFromId( $s->rev_id );
- $newComment = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
- $newComment = $wgRequest->getText( 'summary', $newComment );
-
- # Save it!
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
- $wgOut->setRobotpolicy( 'noindex,nofollow' );
- $wgOut->addHTML( '<h2>' . htmlspecialchars( $newComment ) . "</h2>\n<hr />\n" );
+ if($set)
+ $dbw->update('recentchanges', $set,
+ array(
+ 'rc_cur_id' => $current->getPage(),
+ 'rc_user_text' => $user,
+ "rc_timestamp > '{$s->rev_timestamp}'"
+ ), __METHOD__
+ );
- $this->updateArticle( $target->getText(), $newComment, 1, $this->mTitle->userIsWatching(), $bot );
+ // Generate an edit summary
+ if(empty($summary))
+ $summary = wfMsgForContent('revertpage', $target->getUserText(), $user);
+
+ // Now we *finally* get to commit the edit
+ $flags = EDIT_UPDATE | EDIT_MINOR;
+ if($bot)
+ $flags |= EDIT_FORCE_BOT;
+ if(!$this->doEdit($target->getText(), $summary, $flags))
+ return ROLLBACK_EDITFAILED;
+
+ if(is_null($info))
+ // Save time
+ return ROLLBACK_SUCCESS;
+
+ $info['title'] = $this->mTitle->getPrefixedText();
+ $info['pageid'] = $current->getPage();
+ $info['summary'] = $summary;
+ // NOTE: If the rollback turned out to be a null edit, revid and old_revid will be equal
+ $info['revid'] = $this->mTitle->getLatestRevID(); // The revid of your rollback
+ $info['old_revid'] = $current->getId(); // The revid of the last edit before your rollback
+ $info['last_revid'] = $s->rev_id; // The revid of the last edit that was not rolled back
+ $info['user'] = $user; // The name of the victim
+ $info['userid'] = $userid; // And their userid
+ $info['to'] = $target->getUserText(); // The user whose last version was reverted to
+ if($bot)
+ $info['bot'] = "";
+ return ROLLBACK_SUCCESS;
+ }
+
+ /** UI entry point for rollbacks. Relies on doRollback() to do the hard work */
+ function rollback() {
+ global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
- $wgOut->returnToMain( false );
+ // Basically, we just call doRollback() and interpret its return value
+ $info = array();
+ $retval = $this->doRollback($wgRequest->getVal('from'), $wgRequest->getVal('token'), $wgRequest->getBool('bot'),
+ $wgRequest->getText('summary'), &$info);
+ switch($retval)
+ {
+ case ROLLBACK_SUCCESS:
+ case ROLLBACK_EDITFAILED: // Is ignored
+ $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->addHTML( '<h2>' . htmlspecialchars( $info['summary'] ) . "</h2>\n<hr />\n" );
+ $this->doRedirect(true);
+ $wgOut->returnToMain(false);
+ return;
+ case ROLLBACK_PERM:
+ $wgOut->permissionRequired('rollback');
+ return;
+ case ROLLBACK_BLOCKED:
+ $wgOut->blockedPage();
+ return;
+ case ROLLBACK_READONLY:
+ $wgOut->readOnlyPage($this->getContent());
+ return;
+ case ROLLBACK_BADTOKEN:
+ $wgOut->setPageTitle(wfMsg('rollbackfailed'));
+ $wgOut->addWikiText(wfMsg('sessionfailure'));
+ return;
+ case ROLLBACK_BADARTICLE:
+ $wgOut->addHTML(wfMsg('notanarticle'));
+ return;
+ case ROLLBACK_ALREADYROLLED:
+ $wgOut->setPageTitle(wfMsg('rollbackfailed'));
+ $wgOut->addWikiText(wfMsg('alreadyrolled',
+ htmlspecialchars($this->mTitle->getPrefixedText()),
+ htmlspecialchars($wgRequest->getVal('from')),
+ htmlspecialchars($info['usertext'])));
+ if($info['comment'] != '')
+ $wgOut->addHTML(wfMsg('editcomment',
+ $wgUser->getSkin()->formatComment($info['comment'])));
+ return;
+ case ROLLBACK_ONLYAUTHOR:
+ $wgOut->setPageTitle(wfMsg('rollbackfailed'));
+ $wgOut->addHTML(wfMsg('cantrollback'));
+ return;
+ }
}
-
/**
* Do standard deferred updates after page view
* @private
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',
'ApiResult' => 'includes/api/ApiResult.php',
+ 'ApiRollback' => 'includes/api/ApiRollback.php'
);
wfProfileIn( __METHOD__ );
define( 'EDIT_AUTOSUMMARY', 64 );
/**#@-*/
+/**#@+
+ * Article::doRollback() return values
+ */
+define('ROLLBACK_SUCCES', 0);
+define('ROLLBACK_PERM', 1); // Permission denied
+define('ROLLBACK_BLOCKED', 2); // User has been blocked
+define('ROLLBACK_READONLY', 3); // Wiki is in read-only mode
+define('ROLLBACK_BADTOKEN', 4); // Invalid token specified
+define('ROLLBACK_BADARTICLE', 5); // $article is not a valid Article
+define('ROLLBACK_ALREADYROLLED', 6); // Someone else already rolled this back. $info['usertext'] and $info['comment'] will be set
+define('ROLLBACK_ONLYAUTHOR', 7); // User is the only author of the page
+define('ROLLBACK_EDITFAILED', 8); // Article::doEdit() failed. This is a very weird error
+
/**
* Flags for Database::makeList()
* These are also available as Database class constants
private static $Modules = array (
'login' => 'ApiLogin',
'query' => 'ApiQuery',
+ 'rollback' => 'ApiRollback',
'opensearch' => 'ApiOpenSearch',
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
$this->mRequest = & $request;
$this->mSquidMaxage = 0;
+
+ global $wgUser, $wgCookiePrefix;
+ if(session_id() == '')
+ wfSetupSession();
+ // Reinit $wgUser with info from lg* or the session data. The former overrides the latter
+ if(isset($_REQUEST['lguserid']) && isset($_REQUEST['lgusername']) && isset($_REQUEST['lgtoken']))
+ {
+ $_SESSION['wsUserID'] = $_REQUEST['lguserid'];
+ $_SESSION['wsUserName'] = $_REQUEST['lgusername'];
+ $_SESSION['wsToken'] = $_REQUEST['lgtoken'];
+ }
+ $wgUser = User::newFromSession();
}
/**
}
public function execute() {
+ global $wgUser;
$params = $this->extractRequestParams();
$fld_protection = false;
if(!is_null($params['prop'])) {
$prop = array_flip($params['prop']);
$fld_protection = isset($prop['protection']);
+ $fld_lastrevby = isset($prop['lastrevby']);
}
+ if(!is_null($params['tokens']))
+ $params['tokens'] = array_flip($params['tokens']);
$pageSet = $this->getPageSet();
$titles = $pageSet->getGoodTitles();
$db->freeResult($res);
}
- foreach ( $titles as $pageid => $unused ) {
+ foreach ( $titles as $pageid => $title ) {
$pageInfo = array (
'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]),
'lastrevid' => intval($pageLatest[$pageid]),
'counter' => intval($pageCounter[$pageid]),
- 'length' => intval($pageLength[$pageid]),
+ 'length' => intval($pageLength[$pageid])
);
+ if(isset($params['tokens']) || $fld_lastrevby)
+ {
+ $lastrev = Revision::newFromId($pageInfo['lastrevid']);
+ $pageInfo['lastrevby'] = $lastrev->getUserText();
+ }
if ($pageIsRedir[$pageid])
$pageInfo['redirect'] = '';
}
}
+ $tokenArr = array();
+ foreach($params['tokens'] as $token => $unused)
+ switch($token)
+ {
+ case 'rollback':
+ $tokenArr[$token] = $wgUser->editToken(array($title->getPrefixedText(), $pageInfo['lastrevby']));
+ break;
+ case 'edit':
+ case 'move':
+ case 'delete':
+ case 'undelete':
+ case 'protect':
+ case 'unprotect':
+ if($wgUser->isAnon())
+ $tokenArr[$token] = EDIT_TOKEN_SUFFIX;
+ else
+ $tokenArr[$token] = $wgUser->editToken();
+ // default: can't happen, ignore it if it does happen in some weird way
+ }
+ if(count($tokenArr) > 0)
+ {
+ $pageInfo['tokens'] = $tokenArr;
+ $result->setIndexedTagName($pageInfo['tokens'], 't');
+ }
+
$result->addValue(array (
'query',
'pages'
ApiBase :: PARAM_DFLT => NULL,
ApiBase :: PARAM_ISMULTI => true,
ApiBase :: PARAM_TYPE => array (
- 'protection'
+ 'protection',
+ 'lastrevby'
+ )),
+ 'tokens' => array(
+ ApiBase :: PARAM_DFLT => NULL,
+ ApiBase :: PARAM_ISMULTI => true,
+ ApiBase :: PARAM_TYPE => array(
+ 'edit',
+ 'move',
+ 'delete',
+ 'undelete',
+ 'rollback',
+ 'protect',
+ 'unprotect'
))
);
}
return array (
'prop' => array (
'Which additional properties to get:',
- ' "protection" - List the protection level of each page'
- )
+ ' "protection" - List the protection level of each page',
+ ' "lastrevby" - The name of the user who made the last edit. You may need this for action=rollback.'
+ ),
+ 'tokens' => 'Which tokens to get.'
);
}
protected function getExamples() {
return array (
'api.php?action=query&prop=info&titles=Main%20Page',
- 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
+ 'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page',
+ 'api.php?action=query&prop=info&intokens=edit|rollback&titles=Main%20Page'
);
}
--- /dev/null
+<?php
+
+/*
+ * Created on Jun 20, 2007
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * 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')) {
+ // Eclipse helper - will be ignored in production
+ require_once ("ApiBase.php");
+}
+
+/**
+ * @addtogroup API
+ */
+class ApiRollback extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ public function execute() {
+ global $wgUser;
+ $params = $this->extractRequestParams();
+
+ $titleObj = NULL;
+ if(!isset($params['title']))
+ $this->dieUsage('The title parameter must be set', 'notarget');
+ if(!isset($params['user']))
+ $this->dieUsage('The user parameter must be set', 'nouser');
+ if(!isset($params['token']))
+ $this->dieUsage('The token parameter must be set', 'notoken');
+
+ // doRollback() also checks for these, but we wanna save some work
+ if(!$wgUser->isAllowed('rollback'))
+ $this->dieUsage('You don\'t have permission to rollback', 'permissiondenied');
+ if($wgUser->isBlocked())
+ $this->dieUsage('You have been blocked from editing', 'blocked');
+ if(wfReadOnly())
+ $this->dieUsage('The wiki is in read-only mode', 'readonly');
+
+ $titleObj = Title::newFromText($params['title']);
+ if(!$titleObj)
+ $this->dieUsage("bad title {$params['title']}", 'invalidtitle');
+
+ $articleObj = new Article($titleObj);
+ $summary = (isset($params['summary']) ? $params['summary'] : "");
+ $info = array();
+ $retval = $articleObj->doRollback($params['user'], $params['token'], isset($params['markbot']), $summary, &$info);
+
+ switch($retval)
+ {
+ case ROLLBACK_SUCCESS:
+ break; // We'll deal with that later
+ case ROLLBACK_PERM:
+ $this->dieUsage('You don\'t have permission to rollback', 'permissiondenied');
+ case ROLLBACK_BLOCKED: // If we get BLOCKED or PERM that's very weird, but it's possible
+ $this->dieUsage('You have been blocked from editing', 'blocked');
+ case ROLLBACK_READONLY:
+ $this->dieUsage('The wiki is in read-only mode', 'readonly');
+ case ROLLBACK_BADTOKEN:
+ $this->dieUsage('Invalid token', 'badtoken');
+ case ROLLBACK_BADARTICLE:
+ $this->dieUsage("The article ``{$params['title']}'' doesn't exist", 'missingtitle');
+ case ROLLBACK_ALREADYROLLED:
+ $this->dieUsage('The edit(s) you tried to rollback is/are already rolled back', 'alreadyrolled');
+ case ROLLBACK_ONLYAUTHOR:
+ $this->dieUsage("{$params['user']} is the only author of the page", 'onlyauthor');
+ case ROLLBACK_EDITFAILED:
+ $this->dieDebug(__METHOD__, 'Article::doEdit() failed');
+ default:
+ // rollback() has apparently invented a new error, which is extremely weird
+ $this->dieDebug(__METHOD__, "rollback() returned an unknown error ($retval)");
+ }
+ // $retval has to be ROLLBACK_SUCCESS if we get here
+ $this->getResult()->addValue(null, 'rollback', $info);
+ }
+
+ protected function getAllowedParams() {
+ return array (
+ 'title' => null,
+ 'user' => null,
+ 'token' => null,
+ 'summary' => null,
+ 'markbot' => null
+ );
+ }
+
+ protected function getParamDescription() {
+ return array (
+ 'title' => 'Title of the page you want to rollback.',
+ 'user' => 'Name of the user whose edits are to be rolled back. If set incorrectly, you\'ll get a badtoken error.',
+ 'token' => 'A rollback token previously retrieved through prop=info',
+ 'summary' => 'Custom edit summary. If not set, default summary will be used.',
+ 'markbot' => 'Mark the reverted edits and the revert as bot edits'
+ );
+ }
+
+ protected function getDescription() {
+ return array(
+ 'Undoes the last edit to the page. If the last user who edited the page made multiple edits in a row,',
+ 'they will all be rolled back. You need to be logged in as a sysop to use this function, see also action=login.'
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC',
+ 'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&token=123ABC&summary=Reverting%20vandalism&markbot=1'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiRollback.php 22289 2007-05-20 23:31:44Z yurik $';
+ }
+}
+?>