00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 if ( !defined( 'MEDIAWIKI' ) ) {
00027
00028 require_once ( "ApiBase.php" );
00029 }
00030
00038 class ApiEditPage extends ApiBase {
00039
00040 public function __construct( $query, $moduleName ) {
00041 parent :: __construct( $query, $moduleName );
00042 }
00043
00044 public function execute() {
00045 global $wgUser;
00046 $params = $this->extractRequestParams();
00047
00048 if ( is_null( $params['title'] ) )
00049 $this->dieUsageMsg( array( 'missingparam', 'title' ) );
00050
00051 if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
00052 is_null( $params['prependtext'] ) &&
00053 $params['undo'] == 0 )
00054 $this->dieUsageMsg( array( 'missingtext' ) );
00055
00056 $titleObj = Title::newFromText( $params['title'] );
00057 if ( !$titleObj || $titleObj->isExternal() )
00058 $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
00059
00060
00061 global $wgTitle;
00062 $wgTitle = $titleObj;
00063
00064 if ( $params['createonly'] && $titleObj->exists() )
00065 $this->dieUsageMsg( array( 'createonly-exists' ) );
00066 if ( $params['nocreate'] && !$titleObj->exists() )
00067 $this->dieUsageMsg( array( 'nocreate-missing' ) );
00068
00069
00070 $errors = $titleObj->getUserPermissionsErrors( 'edit', $wgUser );
00071 if ( !$titleObj->exists() )
00072 $errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $wgUser ) );
00073 if ( count( $errors ) )
00074 $this->dieUsageMsg( $errors[0] );
00075
00076 $articleObj = new Article( $titleObj );
00077 $toMD5 = $params['text'];
00078 if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
00079 {
00080
00081
00082
00083
00084 if ( $articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI )
00085 $content = '';
00086 else
00087 $content = $articleObj->getContent();
00088
00089 if ( !is_null( $params['section'] ) )
00090 {
00091
00092 global $wgParser;
00093 $section = intval( $params['section'] );
00094 $content = $wgParser->getSection( $content, $section, false );
00095 if ( $content === false )
00096 $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
00097 }
00098 $params['text'] = $params['prependtext'] . $content . $params['appendtext'];
00099 $toMD5 = $params['prependtext'] . $params['appendtext'];
00100 }
00101
00102 if ( $params['undo'] > 0 )
00103 {
00104 if ( $params['undoafter'] > 0 )
00105 {
00106 if ( $params['undo'] < $params['undoafter'] )
00107 list( $params['undo'], $params['undoafter'] ) =
00108 array( $params['undoafter'], $params['undo'] );
00109 $undoafterRev = Revision::newFromID( $params['undoafter'] );
00110 }
00111 $undoRev = Revision::newFromID( $params['undo'] );
00112 if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) )
00113 $this->dieUsageMsg( array( 'nosuchrevid', $params['undo'] ) );
00114
00115 if ( $params['undoafter'] == 0 )
00116 $undoafterRev = $undoRev->getPrevious();
00117 if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) )
00118 $this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
00119
00120 if ( $undoRev->getPage() != $articleObj->getID() )
00121 $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText() ) );
00122 if ( $undoafterRev->getPage() != $articleObj->getID() )
00123 $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
00124
00125 $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
00126 if ( $newtext === false )
00127 $this->dieUsageMsg( array( 'undo-failure' ) );
00128 $params['text'] = $newtext;
00129
00130
00131 if ( is_null( $params['summary'] ) && $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo'] )
00132 $params['summary'] = wfMsgForContent( 'undo-summary', $params['undo'], $undoRev->getUserText() );
00133 }
00134
00135
00136 if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] )
00137 $this->dieUsageMsg( array( 'hashcheckfailed' ) );
00138
00139 $ep = new EditPage( $articleObj );
00140
00141
00142 $reqArr = array( 'wpTextbox1' => $params['text'],
00143 'wpEditToken' => $params['token'],
00144 'wpIgnoreBlankSummary' => ''
00145 );
00146
00147 if ( !is_null( $params['summary'] ) )
00148 $reqArr['wpSummary'] = $params['summary'];
00149
00150
00151
00152 if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' )
00153 $reqArr['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
00154 else
00155 $reqArr['wpEdittime'] = $articleObj->getTimestamp();
00156
00157 if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' )
00158 $reqArr['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
00159 else
00160 $reqArr['wpStarttime'] = $reqArr['wpEdittime'];
00161
00162 if ( $params['minor'] || ( !$params['notminor'] && $wgUser->getOption( 'minordefault' ) ) )
00163 $reqArr['wpMinoredit'] = '';
00164
00165 if ( $params['recreate'] )
00166 $reqArr['wpRecreate'] = '';
00167
00168 if ( !is_null( $params['section'] ) )
00169 {
00170 $section = intval( $params['section'] );
00171 if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' )
00172 $this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
00173 $reqArr['wpSection'] = $params['section'];
00174 }
00175 else
00176 $reqArr['wpSection'] = '';
00177
00178
00179 switch ( $params['watchlist'] )
00180 {
00181 case 'watch':
00182 $watch = true;
00183 break;
00184 case 'unwatch':
00185 $watch = false;
00186 break;
00187 case 'preferences':
00188 if ( $titleObj->exists() )
00189 $watch = $wgUser->getOption( 'watchdefault' ) || $titleObj->userIsWatching();
00190 else
00191 $watch = $wgUser->getOption( 'watchcreations' );
00192 break;
00193 case 'nochange':
00194 default:
00195 $watch = $titleObj->userIsWatching();
00196 }
00197
00198 if ( $params['watch'] )
00199 $watch = true;
00200 elseif ( $params['unwatch'] )
00201 $watch = false;
00202
00203 if ( $watch )
00204 $reqArr['wpWatchthis'] = '';
00205
00206 $req = new FauxRequest( $reqArr, true );
00207 $ep->importFormData( $req );
00208
00209
00210
00211 global $wgRequest;
00212 if ( !is_null( $params['captchaid'] ) )
00213 $wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] );
00214 if ( !is_null( $params['captchaword'] ) )
00215 $wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] );
00216
00217 $r = array();
00218 if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) )
00219 {
00220 if ( count( $r ) )
00221 {
00222 $r['result'] = "Failure";
00223 $this->getResult()->addValue( null, $this->getModuleName(), $r );
00224 return;
00225 }
00226 else
00227 $this->dieUsageMsg( array( 'hookaborted' ) );
00228 }
00229
00230
00231 $oldRevId = $articleObj->getRevIdFetched();
00232 $result = null;
00233
00234
00235 $oldRequest = $wgRequest;
00236 $wgRequest = $req;
00237
00238 $retval = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] );
00239 $wgRequest = $oldRequest;
00240 switch( $retval )
00241 {
00242 case EditPage::AS_HOOK_ERROR:
00243 case EditPage::AS_HOOK_ERROR_EXPECTED:
00244 $this->dieUsageMsg( array( 'hookaborted' ) );
00245
00246 case EditPage::AS_IMAGE_REDIRECT_ANON:
00247 $this->dieUsageMsg( array( 'noimageredirect-anon' ) );
00248
00249 case EditPage::AS_IMAGE_REDIRECT_LOGGED:
00250 $this->dieUsageMsg( array( 'noimageredirect-logged' ) );
00251
00252 case EditPage::AS_SPAM_ERROR:
00253 $this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) );
00254
00255 case EditPage::AS_FILTERING:
00256 $this->dieUsageMsg( array( 'filtered' ) );
00257
00258 case EditPage::AS_BLOCKED_PAGE_FOR_USER:
00259 $this->dieUsageMsg( array( 'blockedtext' ) );
00260
00261 case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
00262 case EditPage::AS_CONTENT_TOO_BIG:
00263 global $wgMaxArticleSize;
00264 $this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
00265
00266 case EditPage::AS_READ_ONLY_PAGE_ANON:
00267 $this->dieUsageMsg( array( 'noedit-anon' ) );
00268
00269 case EditPage::AS_READ_ONLY_PAGE_LOGGED:
00270 $this->dieUsageMsg( array( 'noedit' ) );
00271
00272 case EditPage::AS_READ_ONLY_PAGE:
00273 $this->dieReadOnly();
00274
00275 case EditPage::AS_RATE_LIMITED:
00276 $this->dieUsageMsg( array( 'actionthrottledtext' ) );
00277
00278 case EditPage::AS_ARTICLE_WAS_DELETED:
00279 $this->dieUsageMsg( array( 'wasdeleted' ) );
00280
00281 case EditPage::AS_NO_CREATE_PERMISSION:
00282 $this->dieUsageMsg( array( 'nocreate-loggedin' ) );
00283
00284 case EditPage::AS_BLANK_ARTICLE:
00285 $this->dieUsageMsg( array( 'blankpage' ) );
00286
00287 case EditPage::AS_CONFLICT_DETECTED:
00288 $this->dieUsageMsg( array( 'editconflict' ) );
00289
00290
00291 case EditPage::AS_TEXTBOX_EMPTY:
00292 $this->dieUsageMsg( array( 'emptynewsection' ) );
00293
00294 case EditPage::AS_SUCCESS_NEW_ARTICLE:
00295 $r['new'] = '';
00296 case EditPage::AS_SUCCESS_UPDATE:
00297 $r['result'] = "Success";
00298 $r['pageid'] = intval( $titleObj->getArticleID() );
00299 $r['title'] = $titleObj->getPrefixedText();
00300
00301
00302
00303
00304 $newArticle = new Article( $titleObj );
00305 $newRevId = $newArticle->getRevIdFetched();
00306 if ( $newRevId == $oldRevId )
00307 $r['nochange'] = '';
00308 else
00309 {
00310 $r['oldrevid'] = intval( $oldRevId );
00311 $r['newrevid'] = intval( $newRevId );
00312 $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
00313 $newArticle->getTimestamp() );
00314 }
00315 break;
00316
00317 case EditPage::AS_END:
00318
00319
00320
00321
00322
00323
00324
00325 default:
00326 $this->dieUsageMsg( array( 'unknownerror', $retval ) );
00327 }
00328 $this->getResult()->addValue( null, $this->getModuleName(), $r );
00329 }
00330
00331 public function mustBePosted() {
00332 return true;
00333 }
00334
00335 public function isWriteMode() {
00336 return true;
00337 }
00338
00339 protected function getDescription() {
00340 return 'Create and edit pages.';
00341 }
00342
00343 public function getPossibleErrors() {
00344 global $wgMaxArticleSize;
00345
00346 return array_merge( parent::getPossibleErrors(), array(
00347 array( 'missingparam', 'title' ),
00348 array( 'missingtext' ),
00349 array( 'invalidtitle', 'title' ),
00350 array( 'createonly-exists' ),
00351 array( 'nocreate-missing' ),
00352 array( 'nosuchrevid', 'undo' ),
00353 array( 'nosuchrevid', 'undoafter' ),
00354 array( 'revwrongpage', 'id', 'text' ),
00355 array( 'undo-failure' ),
00356 array( 'hashcheckfailed' ),
00357 array( 'hookaborted' ),
00358 array( 'noimageredirect-anon' ),
00359 array( 'noimageredirect-logged' ),
00360 array( 'spamdetected', 'spam' ),
00361 array( 'filtered' ),
00362 array( 'blockedtext' ),
00363 array( 'contenttoobig', $wgMaxArticleSize ),
00364 array( 'noedit-anon' ),
00365 array( 'noedit' ),
00366 array( 'actionthrottledtext' ),
00367 array( 'wasdeleted' ),
00368 array( 'nocreate-loggedin' ),
00369 array( 'blankpage' ),
00370 array( 'editconflict' ),
00371 array( 'emptynewsection' ),
00372 array( 'unknownerror', 'retval' ),
00373 array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
00374 array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
00375 ) );
00376 }
00377
00378 protected function getAllowedParams() {
00379 return array (
00380 'title' => null,
00381 'section' => null,
00382 'text' => null,
00383 'token' => null,
00384 'summary' => null,
00385 'minor' => false,
00386 'notminor' => false,
00387 'bot' => false,
00388 'basetimestamp' => null,
00389 'starttimestamp' => null,
00390 'recreate' => false,
00391 'createonly' => false,
00392 'nocreate' => false,
00393 'captchaword' => null,
00394 'captchaid' => null,
00395 'watch' => array(
00396 ApiBase :: PARAM_DFLT => false,
00397 ApiBase :: PARAM_DEPRECATED => true,
00398 ),
00399 'unwatch' => array(
00400 ApiBase :: PARAM_DFLT => false,
00401 ApiBase :: PARAM_DEPRECATED => true,
00402 ),
00403 'watchlist' => array(
00404 ApiBase :: PARAM_DFLT => 'preferences',
00405 ApiBase :: PARAM_TYPE => array(
00406 'watch',
00407 'unwatch',
00408 'preferences',
00409 'nochange'
00410 ),
00411 ),
00412 'md5' => null,
00413 'prependtext' => null,
00414 'appendtext' => null,
00415 'undo' => array(
00416 ApiBase :: PARAM_TYPE => 'integer'
00417 ),
00418 'undoafter' => array(
00419 ApiBase :: PARAM_TYPE => 'integer'
00420 ),
00421 );
00422 }
00423
00424 protected function getParamDescription() {
00425 return array (
00426 'title' => 'Page title',
00427 'section' => 'Section number. 0 for the top section, \'new\' for a new section',
00428 'text' => 'Page content',
00429 'token' => 'Edit token. You can get one of these through prop=info',
00430 'summary' => 'Edit summary. Also section title when section=new',
00431 'minor' => 'Minor edit',
00432 'notminor' => 'Non-minor edit',
00433 'bot' => 'Mark this edit as bot',
00434 'basetimestamp' => array( 'Timestamp of the base revision (gotten through prop=revisions&rvprop=timestamp).',
00435 'Used to detect edit conflicts; leave unset to ignore conflicts.'
00436 ),
00437 'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
00438 'Used to detect edit conflicts; leave unset to ignore conflicts.'
00439 ),
00440 'recreate' => 'Override any errors about the article having been deleted in the meantime',
00441 'createonly' => 'Don\'t edit the page if it exists already',
00442 'nocreate' => 'Throw an error if the page doesn\'t exist',
00443 'watch' => 'Add the page to your watchlist',
00444 'unwatch' => 'Remove the page from your watchlist',
00445 'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
00446 'captchaid' => 'CAPTCHA ID from previous request',
00447 'captchaword' => 'Answer to the CAPTCHA',
00448 'md5' => array( 'The MD5 hash of the text parameter, or the prependtext and appendtext parameters concatenated.',
00449 'If set, the edit won\'t be done unless the hash is correct' ),
00450 'prependtext' => 'Add this text to the beginning of the page. Overrides text.',
00451 'appendtext' => 'Add this text to the end of the page. Overrides text',
00452 'undo' => 'Undo this revision. Overrides text, prependtext and appendtext',
00453 'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
00454 );
00455 }
00456
00457 public function needsToken() {
00458 return true;
00459 }
00460
00461 public function getTokenSalt() {
00462 return '';
00463 }
00464
00465 protected function getExamples() {
00466 return array (
00467 "Edit a page (anonymous user):",
00468 " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\",
00469 "Prepend __NOTOC__ to a page (anonymous user):",
00470 " api.php?action=edit&title=Test&summary=NOTOC&minor&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\",
00471 "Undo r13579 through r13585 with autosummary(anonymous user):",
00472 " api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\",
00473 );
00474 }
00475
00476 public function getVersion() {
00477 return __CLASS__ . ': $Id: ApiEditPage.php 74217 2010-10-03 15:53:07Z reedy $';
00478 }
00479 }