MediaWiki  REL1_23
ApiEditPage.php
Go to the documentation of this file.
00001 <?php
00034 class ApiEditPage extends ApiBase {
00035     public function execute() {
00036         $user = $this->getUser();
00037         $params = $this->extractRequestParams();
00038 
00039         if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
00040             is_null( $params['prependtext'] ) &&
00041             $params['undo'] == 0
00042         ) {
00043             $this->dieUsageMsg( 'missingtext' );
00044         }
00045 
00046         $pageObj = $this->getTitleOrPageId( $params );
00047         $titleObj = $pageObj->getTitle();
00048         $apiResult = $this->getResult();
00049 
00050         if ( $params['redirect'] ) {
00051             if ( $titleObj->isRedirect() ) {
00052                 $oldTitle = $titleObj;
00053 
00054                 $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
00055                     ->getContent( Revision::FOR_THIS_USER, $user )
00056                     ->getRedirectChain();
00057                 // array_shift( $titles );
00058 
00059                 $redirValues = array();
00060 
00062                 foreach ( $titles as $id => $newTitle ) {
00063 
00064                     if ( !isset( $titles[$id - 1] ) ) {
00065                         $titles[$id - 1] = $oldTitle;
00066                     }
00067 
00068                     $redirValues[] = array(
00069                         'from' => $titles[$id - 1]->getPrefixedText(),
00070                         'to' => $newTitle->getPrefixedText()
00071                     );
00072 
00073                     $titleObj = $newTitle;
00074                 }
00075 
00076                 $apiResult->setIndexedTagName( $redirValues, 'r' );
00077                 $apiResult->addValue( null, 'redirects', $redirValues );
00078 
00079                 // Since the page changed, update $pageObj
00080                 $pageObj = WikiPage::factory( $titleObj );
00081             }
00082         }
00083 
00084         if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
00085             $contentHandler = $pageObj->getContentHandler();
00086         } else {
00087             $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
00088         }
00089 
00090         // @todo Ask handler whether direct editing is supported at all! make
00091         // allowFlatEdit() method or some such
00092 
00093         if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
00094             $params['contentformat'] = $contentHandler->getDefaultFormat();
00095         }
00096 
00097         $contentFormat = $params['contentformat'];
00098 
00099         if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
00100             $name = $titleObj->getPrefixedDBkey();
00101             $model = $contentHandler->getModelID();
00102 
00103             $this->dieUsage( "The requested format $contentFormat is not supported for content model " .
00104                 " $model used by $name", 'badformat' );
00105         }
00106 
00107         if ( $params['createonly'] && $titleObj->exists() ) {
00108             $this->dieUsageMsg( 'createonly-exists' );
00109         }
00110         if ( $params['nocreate'] && !$titleObj->exists() ) {
00111             $this->dieUsageMsg( 'nocreate-missing' );
00112         }
00113 
00114         // Now let's check whether we're even allowed to do this
00115         $errors = $titleObj->getUserPermissionsErrors( 'edit', $user );
00116         if ( !$titleObj->exists() ) {
00117             $errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $user ) );
00118         }
00119         if ( count( $errors ) ) {
00120             $this->dieUsageMsg( $errors[0] );
00121         }
00122 
00123         $toMD5 = $params['text'];
00124         if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
00125             $content = $pageObj->getContent();
00126 
00127             if ( !$content ) {
00128                 if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
00129                     # If this is a MediaWiki:x message, then load the messages
00130                     # and return the message value for x.
00131                     $text = $titleObj->getDefaultMessageText();
00132                     if ( $text === false ) {
00133                         $text = '';
00134                     }
00135 
00136                     try {
00137                         $content = ContentHandler::makeContent( $text, $this->getTitle() );
00138                     } catch ( MWContentSerializationException $ex ) {
00139                         $this->dieUsage( $ex->getMessage(), 'parseerror' );
00140 
00141                         return;
00142                     }
00143                 } else {
00144                     # Otherwise, make a new empty content.
00145                     $content = $contentHandler->makeEmptyContent();
00146                 }
00147             }
00148 
00149             // @todo Add support for appending/prepending to the Content interface
00150 
00151             if ( !( $content instanceof TextContent ) ) {
00152                 $mode = $contentHandler->getModelID();
00153                 $this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
00154             }
00155 
00156             if ( !is_null( $params['section'] ) ) {
00157                 if ( !$contentHandler->supportsSections() ) {
00158                     $modelName = $contentHandler->getModelID();
00159                     $this->dieUsage(
00160                         "Sections are not supported for this content model: $modelName.",
00161                         'sectionsnotsupported'
00162                     );
00163                 }
00164 
00165                 if ( $params['section'] == 'new' ) {
00166                     // DWIM if they're trying to prepend/append to a new section.
00167                     $content = null;
00168                 } else {
00169                     // Process the content for section edits
00170                     $section = intval( $params['section'] );
00171                     $content = $content->getSection( $section );
00172 
00173                     if ( !$content ) {
00174                         $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
00175                     }
00176                 }
00177             }
00178 
00179             if ( !$content ) {
00180                 $text = '';
00181             } else {
00182                 $text = $content->serialize( $contentFormat );
00183             }
00184 
00185             $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
00186             $toMD5 = $params['prependtext'] . $params['appendtext'];
00187         }
00188 
00189         if ( $params['undo'] > 0 ) {
00190             if ( $params['undoafter'] > 0 ) {
00191                 if ( $params['undo'] < $params['undoafter'] ) {
00192                     list( $params['undo'], $params['undoafter'] ) =
00193                         array( $params['undoafter'], $params['undo'] );
00194                 }
00195                 $undoafterRev = Revision::newFromID( $params['undoafter'] );
00196             }
00197             $undoRev = Revision::newFromID( $params['undo'] );
00198             if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) ) {
00199                 $this->dieUsageMsg( array( 'nosuchrevid', $params['undo'] ) );
00200             }
00201 
00202             if ( $params['undoafter'] == 0 ) {
00203                 $undoafterRev = $undoRev->getPrevious();
00204             }
00205             if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) ) {
00206                 $this->dieUsageMsg( array( 'nosuchrevid', $params['undoafter'] ) );
00207             }
00208 
00209             if ( $undoRev->getPage() != $pageObj->getID() ) {
00210                 $this->dieUsageMsg( array( 'revwrongpage', $undoRev->getID(),
00211                     $titleObj->getPrefixedText() ) );
00212             }
00213             if ( $undoafterRev->getPage() != $pageObj->getID() ) {
00214                 $this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(),
00215                     $titleObj->getPrefixedText() ) );
00216             }
00217 
00218             $newContent = $contentHandler->getUndoContent(
00219                 $pageObj->getRevision(),
00220                 $undoRev,
00221                 $undoafterRev
00222             );
00223 
00224             if ( !$newContent ) {
00225                 $this->dieUsageMsg( 'undo-failure' );
00226             }
00227 
00228             $params['text'] = $newContent->serialize( $params['contentformat'] );
00229 
00230             // If no summary was given and we only undid one rev,
00231             // use an autosummary
00232             if ( is_null( $params['summary'] ) &&
00233                 $titleObj->getNextRevisionID( $undoafterRev->getID() ) == $params['undo']
00234             ) {
00235                 $params['summary'] = wfMessage( 'undo-summary' )
00236                     ->params ( $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
00237             }
00238         }
00239 
00240         // See if the MD5 hash checks out
00241         if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
00242             $this->dieUsageMsg( 'hashcheckfailed' );
00243         }
00244 
00245         // EditPage wants to parse its stuff from a WebRequest
00246         // That interface kind of sucks, but it's workable
00247         $requestArray = array(
00248             'wpTextbox1' => $params['text'],
00249             'format' => $contentFormat,
00250             'model' => $contentHandler->getModelID(),
00251             'wpEditToken' => $params['token'],
00252             'wpIgnoreBlankSummary' => ''
00253         );
00254 
00255         if ( !is_null( $params['summary'] ) ) {
00256             $requestArray['wpSummary'] = $params['summary'];
00257         }
00258 
00259         if ( !is_null( $params['sectiontitle'] ) ) {
00260             $requestArray['wpSectionTitle'] = $params['sectiontitle'];
00261         }
00262 
00263         // TODO: Pass along information from 'undoafter' as well
00264         if ( $params['undo'] > 0 ) {
00265             $requestArray['wpUndidRevision'] = $params['undo'];
00266         }
00267 
00268         // Watch out for basetimestamp == ''
00269         // wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
00270         if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
00271             $requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
00272         } else {
00273             $requestArray['wpEdittime'] = $pageObj->getTimestamp();
00274         }
00275 
00276         if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
00277             $requestArray['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
00278         } else {
00279             $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
00280         }
00281 
00282         if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
00283             $requestArray['wpMinoredit'] = '';
00284         }
00285 
00286         if ( $params['recreate'] ) {
00287             $requestArray['wpRecreate'] = '';
00288         }
00289 
00290         if ( !is_null( $params['section'] ) ) {
00291             $section = intval( $params['section'] );
00292             if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
00293                 $this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
00294             }
00295             $content = $pageObj->getContent();
00296             if ( $section !== 0 && ( !$content || !$content->getSection( $section ) ) ) {
00297                 $this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
00298             }
00299             $requestArray['wpSection'] = $params['section'];
00300         } else {
00301             $requestArray['wpSection'] = '';
00302         }
00303 
00304         $watch = $this->getWatchlistValue( $params['watchlist'], $titleObj );
00305 
00306         // Deprecated parameters
00307         if ( $params['watch'] ) {
00308             $watch = true;
00309         } elseif ( $params['unwatch'] ) {
00310             $watch = false;
00311         }
00312 
00313         if ( $watch ) {
00314             $requestArray['wpWatchthis'] = '';
00315         }
00316 
00317         // Pass through anything else we might have been given, to support extensions
00318         // This is kind of a hack but it's the best we can do to make extensions work
00319         $requestArray += $this->getRequest()->getValues();
00320 
00321         global $wgTitle, $wgRequest;
00322 
00323         $req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
00324 
00325         // Some functions depend on $wgTitle == $ep->mTitle
00326         // TODO: Make them not or check if they still do
00327         $wgTitle = $titleObj;
00328 
00329         $articleContext = new RequestContext;
00330         $articleContext->setRequest( $req );
00331         $articleContext->setWikiPage( $pageObj );
00332         $articleContext->setUser( $this->getUser() );
00333 
00335         $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
00336 
00337         $ep = new EditPage( $articleObject );
00338 
00339         // allow editing of non-textual content.
00340         $ep->allowNonTextContent = true;
00341 
00342         $ep->setContextTitle( $titleObj );
00343         $ep->importFormData( $req );
00344         $content = $ep->textbox1;
00345 
00346         // The following is needed to give the hook the full content of the
00347         // new revision rather than just the current section. (Bug 52077)
00348         if ( !is_null( $params['section'] ) &&
00349             $contentHandler->supportsSections() && $titleObj->exists()
00350         ) {
00351             // If sectiontitle is set, use it, otherwise use the summary as the section title (for
00352             // backwards compatibility with old forms/bots).
00353             if ( $ep->sectiontitle !== '' ) {
00354                 $sectionTitle = $ep->sectiontitle;
00355             } else {
00356                 $sectionTitle = $ep->summary;
00357             }
00358 
00359             $contentObj = $contentHandler->unserializeContent( $content, $contentFormat );
00360 
00361             $fullContentObj = $articleObject->replaceSectionContent(
00362                 $params['section'],
00363                 $contentObj,
00364                 $sectionTitle
00365             );
00366             if ( $fullContentObj ) {
00367                 $content = $fullContentObj->serialize( $contentFormat );
00368             } else {
00369                 // This most likely means we have an edit conflict which means that the edit
00370                 // wont succeed anyway.
00371                 $this->dieUsageMsg( 'editconflict' );
00372             }
00373         }
00374 
00375         // Run hooks
00376         // Handle APIEditBeforeSave parameters
00377         $r = array();
00378         if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $content, &$r ) ) ) {
00379             if ( count( $r ) ) {
00380                 $r['result'] = 'Failure';
00381                 $apiResult->addValue( null, $this->getModuleName(), $r );
00382 
00383                 return;
00384             }
00385 
00386             $this->dieUsageMsg( 'hookaborted' );
00387         }
00388 
00389         // Do the actual save
00390         $oldRevId = $articleObject->getRevIdFetched();
00391         $result = null;
00392         // Fake $wgRequest for some hooks inside EditPage
00393         // @todo FIXME: This interface SUCKS
00394         $oldRequest = $wgRequest;
00395         $wgRequest = $req;
00396 
00397         $status = $ep->internalAttemptSave( $result, $user->isAllowed( 'bot' ) && $params['bot'] );
00398         $wgRequest = $oldRequest;
00399         global $wgMaxArticleSize;
00400 
00401         switch ( $status->value ) {
00402             case EditPage::AS_HOOK_ERROR:
00403             case EditPage::AS_HOOK_ERROR_EXPECTED:
00404                 $this->dieUsageMsg( 'hookaborted' );
00405 
00406             case EditPage::AS_PARSE_ERROR:
00407                 $this->dieUsage( $status->getMessage(), 'parseerror' );
00408 
00409             case EditPage::AS_IMAGE_REDIRECT_ANON:
00410                 $this->dieUsageMsg( 'noimageredirect-anon' );
00411 
00412             case EditPage::AS_IMAGE_REDIRECT_LOGGED:
00413                 $this->dieUsageMsg( 'noimageredirect-logged' );
00414 
00415             case EditPage::AS_SPAM_ERROR:
00416                 $this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) );
00417 
00418             case EditPage::AS_BLOCKED_PAGE_FOR_USER:
00419                 $this->dieUsageMsg( 'blockedtext' );
00420 
00421             case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
00422             case EditPage::AS_CONTENT_TOO_BIG:
00423                 $this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
00424 
00425             case EditPage::AS_READ_ONLY_PAGE_ANON:
00426                 $this->dieUsageMsg( 'noedit-anon' );
00427 
00428             case EditPage::AS_READ_ONLY_PAGE_LOGGED:
00429                 $this->dieUsageMsg( 'noedit' );
00430 
00431             case EditPage::AS_READ_ONLY_PAGE:
00432                 $this->dieReadOnly();
00433 
00434             case EditPage::AS_RATE_LIMITED:
00435                 $this->dieUsageMsg( 'actionthrottledtext' );
00436 
00437             case EditPage::AS_ARTICLE_WAS_DELETED:
00438                 $this->dieUsageMsg( 'wasdeleted' );
00439 
00440             case EditPage::AS_NO_CREATE_PERMISSION:
00441                 $this->dieUsageMsg( 'nocreate-loggedin' );
00442 
00443             case EditPage::AS_BLANK_ARTICLE:
00444                 $this->dieUsageMsg( 'blankpage' );
00445 
00446             case EditPage::AS_CONFLICT_DETECTED:
00447                 $this->dieUsageMsg( 'editconflict' );
00448 
00449             // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary
00450             case EditPage::AS_TEXTBOX_EMPTY:
00451                 $this->dieUsageMsg( 'emptynewsection' );
00452 
00453             case EditPage::AS_SUCCESS_NEW_ARTICLE:
00454                 $r['new'] = '';
00455                 // fall-through
00456 
00457             case EditPage::AS_SUCCESS_UPDATE:
00458                 $r['result'] = 'Success';
00459                 $r['pageid'] = intval( $titleObj->getArticleID() );
00460                 $r['title'] = $titleObj->getPrefixedText();
00461                 $r['contentmodel'] = $titleObj->getContentModel();
00462                 $newRevId = $articleObject->getLatest();
00463                 if ( $newRevId == $oldRevId ) {
00464                     $r['nochange'] = '';
00465                 } else {
00466                     $r['oldrevid'] = intval( $oldRevId );
00467                     $r['newrevid'] = intval( $newRevId );
00468                     $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
00469                         $pageObj->getTimestamp() );
00470                 }
00471                 break;
00472 
00473             case EditPage::AS_SUMMARY_NEEDED:
00474                 $this->dieUsageMsg( 'summaryrequired' );
00475 
00476             case EditPage::AS_END:
00477             default:
00478                 // $status came from WikiPage::doEdit()
00479                 $errors = $status->getErrorsArray();
00480                 $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
00481                 break;
00482         }
00483         $apiResult->addValue( null, $this->getModuleName(), $r );
00484     }
00485 
00486     public function mustBePosted() {
00487         return true;
00488     }
00489 
00490     public function isWriteMode() {
00491         return true;
00492     }
00493 
00494     public function getDescription() {
00495         return 'Create and edit pages.';
00496     }
00497 
00498     public function getPossibleErrors() {
00499         global $wgMaxArticleSize;
00500 
00501         return array_merge( parent::getPossibleErrors(),
00502             $this->getTitleOrPageIdErrorMessage(),
00503             array(
00504                 array( 'missingtext' ),
00505                 array( 'createonly-exists' ),
00506                 array( 'nocreate-missing' ),
00507                 array( 'nosuchrevid', 'undo' ),
00508                 array( 'nosuchrevid', 'undoafter' ),
00509                 array( 'revwrongpage', 'id', 'text' ),
00510                 array( 'undo-failure' ),
00511                 array( 'hashcheckfailed' ),
00512                 array( 'hookaborted' ),
00513                 array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
00514                 array( 'noimageredirect-anon' ),
00515                 array( 'noimageredirect-logged' ),
00516                 array( 'spamdetected', 'spam' ),
00517                 array( 'summaryrequired' ),
00518                 array( 'blockedtext' ),
00519                 array( 'contenttoobig', $wgMaxArticleSize ),
00520                 array( 'noedit-anon' ),
00521                 array( 'noedit' ),
00522                 array( 'actionthrottledtext' ),
00523                 array( 'wasdeleted' ),
00524                 array( 'nocreate-loggedin' ),
00525                 array( 'blankpage' ),
00526                 array( 'editconflict' ),
00527                 array( 'emptynewsection' ),
00528                 array( 'unknownerror', 'retval' ),
00529                 array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
00530                 array(
00531                     'code' => 'invalidsection',
00532                     'info' => 'The section parameter must be set to an integer or \'new\''
00533                 ),
00534                 array(
00535                     'code' => 'sectionsnotsupported',
00536                     'info' => 'Sections are not supported for this type of page.'
00537                 ),
00538                 array(
00539                     'code' => 'editnotsupported',
00540                     'info' => 'Editing of this type of page is not supported using the text based edit API.'
00541                 ),
00542                 array(
00543                     'code' => 'appendnotsupported',
00544                     'info' => 'This type of page can not be edited by appending or prepending text.' ),
00545                 array(
00546                     'code' => 'badformat',
00547                     'info' => 'The requested serialization format can not be applied to the page\'s content model'
00548                 ),
00549                 array( 'customcssprotected' ),
00550                 array( 'customjsprotected' ),
00551             )
00552         );
00553     }
00554 
00555     public function getAllowedParams() {
00556         return array(
00557             'title' => array(
00558                 ApiBase::PARAM_TYPE => 'string',
00559             ),
00560             'pageid' => array(
00561                 ApiBase::PARAM_TYPE => 'integer',
00562             ),
00563             'section' => null,
00564             'sectiontitle' => array(
00565                 ApiBase::PARAM_TYPE => 'string',
00566             ),
00567             'text' => null,
00568             'token' => array(
00569                 ApiBase::PARAM_TYPE => 'string',
00570                 ApiBase::PARAM_REQUIRED => true
00571             ),
00572             'summary' => null,
00573             'minor' => false,
00574             'notminor' => false,
00575             'bot' => false,
00576             'basetimestamp' => null,
00577             'starttimestamp' => null,
00578             'recreate' => false,
00579             'createonly' => false,
00580             'nocreate' => false,
00581             'watch' => array(
00582                 ApiBase::PARAM_DFLT => false,
00583                 ApiBase::PARAM_DEPRECATED => true,
00584             ),
00585             'unwatch' => array(
00586                 ApiBase::PARAM_DFLT => false,
00587                 ApiBase::PARAM_DEPRECATED => true,
00588             ),
00589             'watchlist' => array(
00590                 ApiBase::PARAM_DFLT => 'preferences',
00591                 ApiBase::PARAM_TYPE => array(
00592                     'watch',
00593                     'unwatch',
00594                     'preferences',
00595                     'nochange'
00596                 ),
00597             ),
00598             'md5' => null,
00599             'prependtext' => null,
00600             'appendtext' => null,
00601             'undo' => array(
00602                 ApiBase::PARAM_TYPE => 'integer'
00603             ),
00604             'undoafter' => array(
00605                 ApiBase::PARAM_TYPE => 'integer'
00606             ),
00607             'redirect' => array(
00608                 ApiBase::PARAM_TYPE => 'boolean',
00609                 ApiBase::PARAM_DFLT => false,
00610             ),
00611             'contentformat' => array(
00612                 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
00613             ),
00614             'contentmodel' => array(
00615                 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
00616             )
00617         );
00618     }
00619 
00620     public function getParamDescription() {
00621         $p = $this->getModulePrefix();
00622 
00623         return array(
00624             'title' => "Title of the page you want to edit. Cannot be used together with {$p}pageid",
00625             'pageid' => "Page ID of the page you want to edit. Cannot be used together with {$p}title",
00626             'section' => 'Section number. 0 for the top section, \'new\' for a new section',
00627             'sectiontitle' => 'The title for a new section',
00628             'text' => 'Page content',
00629             'token' => array(
00630                 'Edit token. You can get one of these through prop=info.',
00631                 "The token should always be sent as the last parameter, or at " .
00632                     "least, after the {$p}text parameter"
00633             ),
00634             'summary'
00635                 => "Edit summary. Also section title when {$p}section=new and {$p}sectiontitle is not set",
00636             'minor' => 'Minor edit',
00637             'notminor' => 'Non-minor edit',
00638             'bot' => 'Mark this edit as bot',
00639             'basetimestamp' => array(
00640                 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
00641                 'Used to detect edit conflicts; leave unset to ignore conflicts'
00642             ),
00643             'starttimestamp' => array(
00644                 'Timestamp when you obtained the edit token.',
00645                 'Used to detect edit conflicts; leave unset to ignore conflicts'
00646             ),
00647             'recreate' => 'Override any errors about the article having been deleted in the meantime',
00648             'createonly' => 'Don\'t edit the page if it exists already',
00649             'nocreate' => 'Throw an error if the page doesn\'t exist',
00650             'watch' => 'Add the page to your watchlist',
00651             'unwatch' => 'Remove the page from your watchlist',
00652             'watchlist' => 'Unconditionally add or remove the page from your ' .
00653                 'watchlist, use preferences or do not change watch',
00654             'md5' => array(
00655                 "The MD5 hash of the {$p}text parameter, or the {$p}prependtext " .
00656                     "and {$p}appendtext parameters concatenated.",
00657                 'If set, the edit won\'t be done unless the hash is correct'
00658             ),
00659             'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
00660             'appendtext' => array( "Add this text to the end of the page. Overrides {$p}text.",
00661                 "Use {$p}section=new to append a new section" ),
00662             'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
00663             'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
00664             'redirect' => 'Automatically resolve redirects',
00665             'contentformat' => 'Content serialization format used for the input text',
00666             'contentmodel' => 'Content model of the new content',
00667         );
00668     }
00669 
00670     public function getResultProperties() {
00671         return array(
00672             '' => array(
00673                 'new' => 'boolean',
00674                 'result' => array(
00675                     ApiBase::PROP_TYPE => array(
00676                         'Success',
00677                         'Failure'
00678                     ),
00679                 ),
00680                 'pageid' => array(
00681                     ApiBase::PROP_TYPE => 'integer',
00682                     ApiBase::PROP_NULLABLE => true
00683                 ),
00684                 'title' => array(
00685                     ApiBase::PROP_TYPE => 'string',
00686                     ApiBase::PROP_NULLABLE => true
00687                 ),
00688                 'nochange' => 'boolean',
00689                 'oldrevid' => array(
00690                     ApiBase::PROP_TYPE => 'integer',
00691                     ApiBase::PROP_NULLABLE => true
00692                 ),
00693                 'newrevid' => array(
00694                     ApiBase::PROP_TYPE => 'integer',
00695                     ApiBase::PROP_NULLABLE => true
00696                 ),
00697                 'newtimestamp' => array(
00698                     ApiBase::PROP_TYPE => 'string',
00699                     ApiBase::PROP_NULLABLE => true
00700                 )
00701             )
00702         );
00703     }
00704 
00705     public function needsToken() {
00706         return true;
00707     }
00708 
00709     public function getTokenSalt() {
00710         return '';
00711     }
00712 
00713     public function getExamples() {
00714         return array(
00715             'api.php?action=edit&title=Test&summary=test%20summary&' .
00716             'text=article%20content&basetimestamp=20070824123454&token=%2B\\'
00717                 => 'Edit a page (anonymous user)',
00718             'api.php?action=edit&title=Test&summary=NOTOC&minor=&' .
00719                 'prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
00720                 => 'Prepend __NOTOC__ to a page (anonymous user)',
00721             'api.php?action=edit&title=Test&undo=13585&undoafter=13579&' .
00722                 'basetimestamp=20070824123454&token=%2B\\'
00723                 => 'Undo r13579 through r13585 with autosummary (anonymous user)',
00724         );
00725     }
00726 
00727     public function getHelpUrls() {
00728         return 'https://www.mediawiki.org/wiki/API:Edit';
00729     }
00730 }