ApiEditPage.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. <?php
  2. /**
  3. * Copyright © 2007 Iker Labarga "<Firstname><Lastname>@gmail.com"
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. */
  22. use MediaWiki\MediaWikiServices;
  23. use MediaWiki\Revision\RevisionRecord;
  24. /**
  25. * A module that allows for editing and creating pages.
  26. *
  27. * Currently, this wraps around the EditPage class in an ugly way,
  28. * EditPage.php should be rewritten to provide a cleaner interface,
  29. * see T20654 if you're inspired to fix this.
  30. *
  31. * @ingroup API
  32. */
  33. class ApiEditPage extends ApiBase {
  34. public function execute() {
  35. $this->useTransactionalTimeLimit();
  36. $user = $this->getUser();
  37. $params = $this->extractRequestParams();
  38. $this->requireAtLeastOneParameter( $params, 'text', 'appendtext', 'prependtext', 'undo' );
  39. $pageObj = $this->getTitleOrPageId( $params );
  40. $titleObj = $pageObj->getTitle();
  41. $apiResult = $this->getResult();
  42. if ( $params['redirect'] ) {
  43. if ( $params['prependtext'] === null && $params['appendtext'] === null
  44. && $params['section'] !== 'new'
  45. ) {
  46. $this->dieWithError( 'apierror-redirect-appendonly' );
  47. }
  48. if ( $titleObj->isRedirect() ) {
  49. $oldTitle = $titleObj;
  50. $titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
  51. ->getContent( RevisionRecord::FOR_THIS_USER, $user )
  52. ->getRedirectChain();
  53. // array_shift( $titles );
  54. $redirValues = [];
  55. /** @var Title $newTitle */
  56. foreach ( $titles as $id => $newTitle ) {
  57. $titles[$id - 1] = $titles[$id - 1] ?? $oldTitle;
  58. $redirValues[] = [
  59. 'from' => $titles[$id - 1]->getPrefixedText(),
  60. 'to' => $newTitle->getPrefixedText()
  61. ];
  62. $titleObj = $newTitle;
  63. // T239428: Check whether the new title is valid
  64. if ( $titleObj->isExternal() || !$titleObj->canExist() ) {
  65. $redirValues[count( $redirValues ) - 1]['to'] = $titleObj->getFullText();
  66. $this->dieWithError(
  67. [
  68. 'apierror-edit-invalidredirect',
  69. Message::plaintextParam( $oldTitle->getPrefixedText() ),
  70. Message::plaintextParam( $titleObj->getFullText() ),
  71. ],
  72. 'edit-invalidredirect',
  73. [ 'redirects' => $redirValues ]
  74. );
  75. }
  76. }
  77. ApiResult::setIndexedTagName( $redirValues, 'r' );
  78. $apiResult->addValue( null, 'redirects', $redirValues );
  79. // Since the page changed, update $pageObj
  80. $pageObj = WikiPage::factory( $titleObj );
  81. }
  82. }
  83. if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
  84. $contentHandler = $pageObj->getContentHandler();
  85. } else {
  86. $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
  87. }
  88. $contentModel = $contentHandler->getModelID();
  89. $name = $titleObj->getPrefixedDBkey();
  90. $model = $contentHandler->getModelID();
  91. if ( $params['undo'] > 0 ) {
  92. // allow undo via api
  93. } elseif ( $contentHandler->supportsDirectApiEditing() === false ) {
  94. $this->dieWithError( [ 'apierror-no-direct-editing', $model, $name ] );
  95. }
  96. if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
  97. $contentFormat = $contentHandler->getDefaultFormat();
  98. } else {
  99. $contentFormat = $params['contentformat'];
  100. }
  101. if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
  102. $this->dieWithError( [ 'apierror-badformat', $contentFormat, $model, $name ] );
  103. }
  104. if ( $params['createonly'] && $titleObj->exists() ) {
  105. $this->dieWithError( 'apierror-articleexists' );
  106. }
  107. if ( $params['nocreate'] && !$titleObj->exists() ) {
  108. $this->dieWithError( 'apierror-missingtitle' );
  109. }
  110. // Now let's check whether we're even allowed to do this
  111. $this->checkTitleUserPermissions(
  112. $titleObj,
  113. $titleObj->exists() ? 'edit' : [ 'edit', 'create' ],
  114. [ 'autoblock' => true ]
  115. );
  116. $toMD5 = $params['text'];
  117. if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
  118. $content = $pageObj->getContent();
  119. if ( !$content ) {
  120. if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
  121. # If this is a MediaWiki:x message, then load the messages
  122. # and return the message value for x.
  123. $text = $titleObj->getDefaultMessageText();
  124. if ( $text === false ) {
  125. $text = '';
  126. }
  127. try {
  128. $content = ContentHandler::makeContent( $text, $titleObj );
  129. } catch ( MWContentSerializationException $ex ) {
  130. $this->dieWithException( $ex, [
  131. 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
  132. ] );
  133. return;
  134. }
  135. } else {
  136. # Otherwise, make a new empty content.
  137. $content = $contentHandler->makeEmptyContent();
  138. }
  139. }
  140. // @todo Add support for appending/prepending to the Content interface
  141. if ( !( $content instanceof TextContent ) ) {
  142. $modelName = $contentHandler->getModelID();
  143. $this->dieWithError( [ 'apierror-appendnotsupported', $modelName ] );
  144. }
  145. if ( !is_null( $params['section'] ) ) {
  146. if ( !$contentHandler->supportsSections() ) {
  147. $modelName = $contentHandler->getModelID();
  148. $this->dieWithError( [ 'apierror-sectionsnotsupported', $modelName ] );
  149. }
  150. if ( $params['section'] == 'new' ) {
  151. // DWIM if they're trying to prepend/append to a new section.
  152. $content = null;
  153. } else {
  154. // Process the content for section edits
  155. $section = $params['section'];
  156. $content = $content->getSection( $section );
  157. if ( !$content ) {
  158. $this->dieWithError( [ 'apierror-nosuchsection', wfEscapeWikiText( $section ) ] );
  159. }
  160. }
  161. }
  162. if ( !$content ) {
  163. $text = '';
  164. } else {
  165. $text = $content->serialize( $contentFormat );
  166. }
  167. $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
  168. $toMD5 = $params['prependtext'] . $params['appendtext'];
  169. }
  170. if ( $params['undo'] > 0 ) {
  171. if ( $params['undoafter'] > 0 ) {
  172. if ( $params['undo'] < $params['undoafter'] ) {
  173. list( $params['undo'], $params['undoafter'] ) =
  174. [ $params['undoafter'], $params['undo'] ];
  175. }
  176. $undoafterRev = Revision::newFromId( $params['undoafter'] );
  177. }
  178. $undoRev = Revision::newFromId( $params['undo'] );
  179. if ( is_null( $undoRev ) || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
  180. $this->dieWithError( [ 'apierror-nosuchrevid', $params['undo'] ] );
  181. }
  182. if ( $params['undoafter'] == 0 ) {
  183. $undoafterRev = $undoRev->getPrevious();
  184. }
  185. if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
  186. $this->dieWithError( [ 'apierror-nosuchrevid', $params['undoafter'] ] );
  187. }
  188. if ( $undoRev->getPage() != $pageObj->getId() ) {
  189. $this->dieWithError( [ 'apierror-revwrongpage', $undoRev->getId(),
  190. $titleObj->getPrefixedText() ] );
  191. }
  192. if ( $undoafterRev->getPage() != $pageObj->getId() ) {
  193. $this->dieWithError( [ 'apierror-revwrongpage', $undoafterRev->getId(),
  194. $titleObj->getPrefixedText() ] );
  195. }
  196. $newContent = $contentHandler->getUndoContent(
  197. $pageObj->getRevision(),
  198. $undoRev,
  199. $undoafterRev
  200. );
  201. if ( !$newContent ) {
  202. $this->dieWithError( 'undo-failure', 'undofailure' );
  203. }
  204. if ( empty( $params['contentmodel'] )
  205. && empty( $params['contentformat'] )
  206. ) {
  207. // If we are reverting content model, the new content model
  208. // might not support the current serialization format, in
  209. // which case go back to the old serialization format,
  210. // but only if the user hasn't specified a format/model
  211. // parameter.
  212. if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
  213. $contentFormat = $undoafterRev->getContentFormat();
  214. }
  215. // Override content model with model of undid revision.
  216. $contentModel = $newContent->getModel();
  217. }
  218. $params['text'] = $newContent->serialize( $contentFormat );
  219. // If no summary was given and we only undid one rev,
  220. // use an autosummary
  221. if ( is_null( $params['summary'] ) ) {
  222. $nextRev = MediaWikiServices::getInstance()->getRevisionLookup()
  223. ->getNextRevision( $undoafterRev->getRevisionRecord() );
  224. if ( $nextRev && $nextRev->getId() == $params['undo'] ) {
  225. $params['summary'] = wfMessage( 'undo-summary' )
  226. ->params( $params['undo'], $undoRev->getUserText() )
  227. ->inContentLanguage()->text();
  228. }
  229. }
  230. }
  231. // See if the MD5 hash checks out
  232. if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
  233. $this->dieWithError( 'apierror-badmd5' );
  234. }
  235. // EditPage wants to parse its stuff from a WebRequest
  236. // That interface kind of sucks, but it's workable
  237. $requestArray = [
  238. 'wpTextbox1' => $params['text'],
  239. 'format' => $contentFormat,
  240. 'model' => $contentModel,
  241. 'wpEditToken' => $params['token'],
  242. 'wpIgnoreBlankSummary' => true,
  243. 'wpIgnoreBlankArticle' => true,
  244. 'wpIgnoreSelfRedirect' => true,
  245. 'bot' => $params['bot'],
  246. 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
  247. ];
  248. if ( !is_null( $params['summary'] ) ) {
  249. $requestArray['wpSummary'] = $params['summary'];
  250. }
  251. if ( !is_null( $params['sectiontitle'] ) ) {
  252. $requestArray['wpSectionTitle'] = $params['sectiontitle'];
  253. }
  254. // TODO: Pass along information from 'undoafter' as well
  255. if ( $params['undo'] > 0 ) {
  256. $requestArray['wpUndidRevision'] = $params['undo'];
  257. }
  258. // Watch out for basetimestamp == '' or '0'
  259. // It gets treated as NOW, almost certainly causing an edit conflict
  260. if ( $params['basetimestamp'] !== null && (bool)$this->getMain()->getVal( 'basetimestamp' ) ) {
  261. $requestArray['wpEdittime'] = $params['basetimestamp'];
  262. } else {
  263. $requestArray['wpEdittime'] = $pageObj->getTimestamp();
  264. }
  265. if ( $params['starttimestamp'] !== null ) {
  266. $requestArray['wpStarttime'] = $params['starttimestamp'];
  267. } else {
  268. $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
  269. }
  270. if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
  271. $requestArray['wpMinoredit'] = '';
  272. }
  273. if ( $params['recreate'] ) {
  274. $requestArray['wpRecreate'] = '';
  275. }
  276. if ( !is_null( $params['section'] ) ) {
  277. $section = $params['section'];
  278. if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
  279. $this->dieWithError( 'apierror-invalidsection' );
  280. }
  281. $content = $pageObj->getContent();
  282. if ( $section !== '0' && $section != 'new'
  283. && ( !$content || !$content->getSection( $section ) )
  284. ) {
  285. $this->dieWithError( [ 'apierror-nosuchsection', $section ] );
  286. }
  287. $requestArray['wpSection'] = $params['section'];
  288. } else {
  289. $requestArray['wpSection'] = '';
  290. }
  291. $watch = $this->getWatchlistValue( $params['watchlist'], $titleObj );
  292. // Deprecated parameters
  293. if ( $params['watch'] ) {
  294. $watch = true;
  295. } elseif ( $params['unwatch'] ) {
  296. $watch = false;
  297. }
  298. if ( $watch ) {
  299. $requestArray['wpWatchthis'] = '';
  300. }
  301. // Apply change tags
  302. if ( $params['tags'] ) {
  303. $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
  304. if ( $tagStatus->isOK() ) {
  305. $requestArray['wpChangeTags'] = implode( ',', $params['tags'] );
  306. } else {
  307. $this->dieStatus( $tagStatus );
  308. }
  309. }
  310. // Pass through anything else we might have been given, to support extensions
  311. // This is kind of a hack but it's the best we can do to make extensions work
  312. $requestArray += $this->getRequest()->getValues();
  313. global $wgTitle, $wgRequest;
  314. $req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
  315. // Some functions depend on $wgTitle == $ep->mTitle
  316. // TODO: Make them not or check if they still do
  317. $wgTitle = $titleObj;
  318. $articleContext = new RequestContext;
  319. $articleContext->setRequest( $req );
  320. $articleContext->setWikiPage( $pageObj );
  321. $articleContext->setUser( $this->getUser() );
  322. /** @var Article $articleObject */
  323. $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
  324. $ep = new EditPage( $articleObject );
  325. $ep->setApiEditOverride( true );
  326. $ep->setContextTitle( $titleObj );
  327. $ep->importFormData( $req );
  328. $content = $ep->textbox1;
  329. // Do the actual save
  330. $oldRevId = $articleObject->getRevIdFetched();
  331. $result = null;
  332. // Fake $wgRequest for some hooks inside EditPage
  333. // @todo FIXME: This interface SUCKS
  334. $oldRequest = $wgRequest;
  335. $wgRequest = $req;
  336. $status = $ep->attemptSave( $result );
  337. $wgRequest = $oldRequest;
  338. $r = [];
  339. switch ( $status->value ) {
  340. case EditPage::AS_HOOK_ERROR:
  341. case EditPage::AS_HOOK_ERROR_EXPECTED:
  342. if ( isset( $status->apiHookResult ) ) {
  343. $r = $status->apiHookResult;
  344. $r['result'] = 'Failure';
  345. $apiResult->addValue( null, $this->getModuleName(), $r );
  346. return;
  347. }
  348. if ( !$status->getErrors() ) {
  349. // This appears to be unreachable right now, because all
  350. // code paths will set an error. Could change, though.
  351. $status->fatal( 'hookaborted' ); //@codeCoverageIgnore
  352. }
  353. $this->dieStatus( $status );
  354. // These two cases will normally have been caught earlier, and will
  355. // only occur if something blocks the user between the earlier
  356. // check and the check in EditPage (presumably a hook). It's not
  357. // obvious that this is even possible.
  358. // @codeCoverageIgnoreStart
  359. case EditPage::AS_BLOCKED_PAGE_FOR_USER:
  360. $this->dieBlocked( $user->getBlock() );
  361. case EditPage::AS_READ_ONLY_PAGE:
  362. $this->dieReadOnly();
  363. // @codeCoverageIgnoreEnd
  364. case EditPage::AS_SUCCESS_NEW_ARTICLE:
  365. $r['new'] = true;
  366. // fall-through
  367. case EditPage::AS_SUCCESS_UPDATE:
  368. $r['result'] = 'Success';
  369. $r['pageid'] = (int)$titleObj->getArticleID();
  370. $r['title'] = $titleObj->getPrefixedText();
  371. $r['contentmodel'] = $articleObject->getContentModel();
  372. $newRevId = $articleObject->getLatest();
  373. if ( $newRevId == $oldRevId ) {
  374. $r['nochange'] = true;
  375. } else {
  376. $r['oldrevid'] = (int)$oldRevId;
  377. $r['newrevid'] = (int)$newRevId;
  378. $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
  379. $pageObj->getTimestamp() );
  380. }
  381. break;
  382. default:
  383. if ( !$status->getErrors() ) {
  384. // EditPage sometimes only sets the status code without setting
  385. // any actual error messages. Supply defaults for those cases.
  386. switch ( $status->value ) {
  387. // Currently needed
  388. case EditPage::AS_IMAGE_REDIRECT_ANON:
  389. $status->fatal( 'apierror-noimageredirect-anon' );
  390. break;
  391. case EditPage::AS_IMAGE_REDIRECT_LOGGED:
  392. $status->fatal( 'apierror-noimageredirect' );
  393. break;
  394. case EditPage::AS_CONTENT_TOO_BIG:
  395. case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
  396. $status->fatal( 'apierror-contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) );
  397. break;
  398. case EditPage::AS_READ_ONLY_PAGE_ANON:
  399. $status->fatal( 'apierror-noedit-anon' );
  400. break;
  401. case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
  402. $status->fatal( 'apierror-cantchangecontentmodel' );
  403. break;
  404. case EditPage::AS_ARTICLE_WAS_DELETED:
  405. $status->fatal( 'apierror-pagedeleted' );
  406. break;
  407. case EditPage::AS_CONFLICT_DETECTED:
  408. $status->fatal( 'editconflict' );
  409. break;
  410. // Currently shouldn't be needed, but here in case
  411. // hooks use them without setting appropriate
  412. // errors on the status.
  413. // @codeCoverageIgnoreStart
  414. case EditPage::AS_SPAM_ERROR:
  415. $status->fatal( 'apierror-spamdetected', $result['spam'] );
  416. break;
  417. case EditPage::AS_READ_ONLY_PAGE_LOGGED:
  418. $status->fatal( 'apierror-noedit' );
  419. break;
  420. case EditPage::AS_RATE_LIMITED:
  421. $status->fatal( 'apierror-ratelimited' );
  422. break;
  423. case EditPage::AS_NO_CREATE_PERMISSION:
  424. $status->fatal( 'nocreate-loggedin' );
  425. break;
  426. case EditPage::AS_BLANK_ARTICLE:
  427. $status->fatal( 'apierror-emptypage' );
  428. break;
  429. case EditPage::AS_TEXTBOX_EMPTY:
  430. $status->fatal( 'apierror-emptynewsection' );
  431. break;
  432. case EditPage::AS_SUMMARY_NEEDED:
  433. $status->fatal( 'apierror-summaryrequired' );
  434. break;
  435. default:
  436. wfWarn( __METHOD__ . ": Unknown EditPage code {$status->value} with no message" );
  437. $status->fatal( 'apierror-unknownerror-editpage', $status->value );
  438. break;
  439. // @codeCoverageIgnoreEnd
  440. }
  441. }
  442. $this->dieStatus( $status );
  443. }
  444. $apiResult->addValue( null, $this->getModuleName(), $r );
  445. }
  446. public function mustBePosted() {
  447. return true;
  448. }
  449. public function isWriteMode() {
  450. return true;
  451. }
  452. public function getAllowedParams() {
  453. return [
  454. 'title' => [
  455. ApiBase::PARAM_TYPE => 'string',
  456. ],
  457. 'pageid' => [
  458. ApiBase::PARAM_TYPE => 'integer',
  459. ],
  460. 'section' => null,
  461. 'sectiontitle' => [
  462. ApiBase::PARAM_TYPE => 'string',
  463. ],
  464. 'text' => [
  465. ApiBase::PARAM_TYPE => 'text',
  466. ],
  467. 'summary' => null,
  468. 'tags' => [
  469. ApiBase::PARAM_TYPE => 'tags',
  470. ApiBase::PARAM_ISMULTI => true,
  471. ],
  472. 'minor' => false,
  473. 'notminor' => false,
  474. 'bot' => false,
  475. 'basetimestamp' => [
  476. ApiBase::PARAM_TYPE => 'timestamp',
  477. ],
  478. 'starttimestamp' => [
  479. ApiBase::PARAM_TYPE => 'timestamp',
  480. ],
  481. 'recreate' => false,
  482. 'createonly' => false,
  483. 'nocreate' => false,
  484. 'watch' => [
  485. ApiBase::PARAM_DFLT => false,
  486. ApiBase::PARAM_DEPRECATED => true,
  487. ],
  488. 'unwatch' => [
  489. ApiBase::PARAM_DFLT => false,
  490. ApiBase::PARAM_DEPRECATED => true,
  491. ],
  492. 'watchlist' => [
  493. ApiBase::PARAM_DFLT => 'preferences',
  494. ApiBase::PARAM_TYPE => [
  495. 'watch',
  496. 'unwatch',
  497. 'preferences',
  498. 'nochange'
  499. ],
  500. ],
  501. 'md5' => null,
  502. 'prependtext' => [
  503. ApiBase::PARAM_TYPE => 'text',
  504. ],
  505. 'appendtext' => [
  506. ApiBase::PARAM_TYPE => 'text',
  507. ],
  508. 'undo' => [
  509. ApiBase::PARAM_TYPE => 'integer',
  510. ApiBase::PARAM_MIN => 0,
  511. ApiBase::PARAM_RANGE_ENFORCE => true,
  512. ],
  513. 'undoafter' => [
  514. ApiBase::PARAM_TYPE => 'integer',
  515. ApiBase::PARAM_MIN => 0,
  516. ApiBase::PARAM_RANGE_ENFORCE => true,
  517. ],
  518. 'redirect' => [
  519. ApiBase::PARAM_TYPE => 'boolean',
  520. ApiBase::PARAM_DFLT => false,
  521. ],
  522. 'contentformat' => [
  523. ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
  524. ],
  525. 'contentmodel' => [
  526. ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
  527. ],
  528. 'token' => [
  529. // Standard definition automatically inserted
  530. ApiBase::PARAM_HELP_MSG_APPEND => [ 'apihelp-edit-param-token' ],
  531. ],
  532. ];
  533. }
  534. public function needsToken() {
  535. return 'csrf';
  536. }
  537. protected function getExamplesMessages() {
  538. return [
  539. 'action=edit&title=Test&summary=test%20summary&' .
  540. 'text=article%20content&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
  541. => 'apihelp-edit-example-edit',
  542. 'action=edit&title=Test&summary=NOTOC&minor=&' .
  543. 'prependtext=__NOTOC__%0A&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
  544. => 'apihelp-edit-example-prepend',
  545. 'action=edit&title=Test&undo=13585&undoafter=13579&' .
  546. 'basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
  547. => 'apihelp-edit-example-undo',
  548. ];
  549. }
  550. public function getHelpUrls() {
  551. return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Edit';
  552. }
  553. }