146 $titleObj = $pageObj->getTitle();
150 if ( $params[
'redirect'] ) {
151 if ( $params[
'prependtext'] ===
null
152 && $params[
'appendtext'] ===
null
153 && $params[
'section'] !==
'new'
157 if ( $titleObj->isRedirect() ) {
158 $oldTarget = $titleObj;
159 $redirTarget = $this->redirectLookup->getRedirectTarget( $oldTarget );
160 $redirTarget = Title::castFromLinkTarget( $redirTarget );
163 'from' => $titleObj->getPrefixedText(),
164 'to' => $redirTarget->getPrefixedText()
168 if ( $redirTarget->isExternal() || !$redirTarget->canExist() ) {
169 $redirValues[
'to'] = $redirTarget->getFullText();
172 'apierror-edit-invalidredirect',
176 'edit-invalidredirect',
177 [
'redirects' => $redirValues ]
181 ApiResult::setIndexedTagName( $redirValues,
'r' );
182 $apiResult->addValue(
null,
'redirects', $redirValues );
185 $pageObj = $this->wikiPageFactory->newFromTitle( $redirTarget );
186 $titleObj = $pageObj->getTitle();
192 if ( $params[
'contentmodel'] ) {
193 $contentHandler = $this->contentHandlerFactory->getContentHandler( $params[
'contentmodel'] );
195 $contentHandler = $pageObj->getContentHandler();
197 $contentModel = $contentHandler->getModelID();
199 $name = $titleObj->getPrefixedDBkey();
201 if ( $params[
'undo'] > 0 ) {
203 } elseif ( $contentHandler->supportsDirectApiEditing() ===
false ) {
204 $this->
dieWithError( [
'apierror-no-direct-editing', $contentModel, $name ] );
207 $contentFormat = $params[
'contentformat'] ?: $contentHandler->getDefaultFormat();
209 if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
210 $this->
dieWithError( [
'apierror-badformat', $contentFormat, $contentModel, $name ] );
213 if ( $params[
'createonly'] && $titleObj->exists() ) {
216 if ( $params[
'nocreate'] && !$titleObj->exists() ) {
224 [
'autoblock' =>
true,
'user' => $this->getUserForPermissions() ]
227 $toMD5 = $params[
'text'];
228 if ( $params[
'appendtext'] !==
null || $params[
'prependtext'] !==
null ) {
233 # If this is a MediaWiki:x message, then load the messages
234 # and return the message value for x.
235 $text = $titleObj->getDefaultMessageText();
236 if ( $text ===
false ) {
241 $content = ContentHandler::makeContent( $text, $titleObj );
244 'wrap' => ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
248 # Otherwise, make a new empty content.
249 $content = $contentHandler->makeEmptyContent();
256 $this->
dieWithError( [
'apierror-appendnotsupported', $contentModel ] );
259 if ( $params[
'section'] !==
null ) {
260 if ( !$contentHandler->supportsSections() ) {
261 $this->
dieWithError( [
'apierror-sectionsnotsupported', $contentModel ] );
264 if ( $params[
'section'] ==
'new' ) {
269 $section = $params[
'section'];
281 $text =
$content->serialize( $contentFormat );
284 $params[
'text'] = $params[
'prependtext'] . $text . $params[
'appendtext'];
285 $toMD5 = $params[
'prependtext'] . $params[
'appendtext'];
288 if ( $params[
'undo'] > 0 ) {
289 $undoRev = $this->revisionLookup->getRevisionById( $params[
'undo'] );
290 if ( $undoRev ===
null || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
291 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undo'] ] );
294 if ( $params[
'undoafter'] > 0 ) {
295 $undoafterRev = $this->revisionLookup->getRevisionById( $params[
'undoafter'] );
298 $undoafterRev = $this->revisionLookup->getPreviousRevision( $undoRev );
300 if ( $undoafterRev ===
null || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
301 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undoafter'] ] );
304 if ( $undoRev->getPageId() != $pageObj->getId() ) {
305 $this->
dieWithError( [
'apierror-revwrongpage', $undoRev->getId(),
306 $titleObj->getPrefixedText() ] );
308 if ( $undoafterRev->getPageId() != $pageObj->getId() ) {
309 $this->
dieWithError( [
'apierror-revwrongpage', $undoafterRev->getId(),
310 $titleObj->getPrefixedText() ] );
313 $newContent = $contentHandler->getUndoContent(
315 $pageObj->getRevisionRecord()->getContent( SlotRecord::MAIN ),
317 $undoRev->getContent( SlotRecord::MAIN ),
319 $undoafterRev->getContent( SlotRecord::MAIN ),
320 $pageObj->getRevisionRecord()->getId() === $undoRev->getId()
323 if ( !$newContent ) {
326 if ( !$params[
'contentmodel'] && !$params[
'contentformat'] ) {
332 if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
333 $undoafterRevMainSlot = $undoafterRev->getSlot(
337 $contentFormat = $undoafterRevMainSlot->getFormat();
338 if ( !$contentFormat ) {
341 $contentFormat = $this->contentHandlerFactory
342 ->getContentHandler( $undoafterRevMainSlot->getModel() )
343 ->getDefaultFormat();
347 $contentModel = $newContent->getModel();
348 $undoContentModel =
true;
350 $params[
'text'] = $newContent->serialize( $contentFormat );
354 if ( $params[
'summary'] ===
null ) {
355 $nextRev = $this->revisionLookup->getNextRevision( $undoafterRev );
356 if ( $nextRev && $nextRev->getId() == $params[
'undo'] ) {
357 $undoRevUser = $undoRev->getUser();
358 $params[
'summary'] = $this->
msg(
'undo-summary' )
359 ->params( $params[
'undo'], $undoRevUser ? $undoRevUser->getName() :
'' )
360 ->inContentLanguage()->text();
366 if ( $params[
'md5'] !==
null && md5( $toMD5 ) !== $params[
'md5'] ) {
374 'wpTextbox1' => $params[
'text'],
375 'format' => $contentFormat,
376 'model' => $contentModel,
377 'wpEditToken' => $params[
'token'],
378 'wpIgnoreBlankSummary' =>
true,
379 'wpIgnoreBlankArticle' =>
true,
380 'wpIgnoreSelfRedirect' =>
true,
381 'bot' => $params[
'bot'],
382 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
386 if ( $params[
'summary'] !==
null ) {
387 $requestArray[
'wpSummary'] = $params[
'summary'];
390 if ( $params[
'sectiontitle'] !==
null ) {
391 $requestArray[
'wpSectionTitle'] = $params[
'sectiontitle'];
394 if ( $params[
'undo'] > 0 ) {
395 $requestArray[
'wpUndidRevision'] = $params[
'undo'];
397 if ( $params[
'undoafter'] > 0 ) {
398 $requestArray[
'wpUndoAfter'] = $params[
'undoafter'];
402 if ( !empty( $params[
'baserevid'] ) ) {
403 $requestArray[
'editRevId'] = $params[
'baserevid'];
408 if ( $params[
'basetimestamp'] !==
null && (
bool)$this->
getMain()->getVal(
'basetimestamp' ) ) {
409 $requestArray[
'wpEdittime'] = $params[
'basetimestamp'];
410 } elseif ( empty( $params[
'baserevid'] ) ) {
413 $requestArray[
'wpEdittime'] = $pageObj->getTimestamp();
416 if ( $params[
'starttimestamp'] !==
null ) {
417 $requestArray[
'wpStarttime'] = $params[
'starttimestamp'];
422 if ( $params[
'minor'] || ( !$params[
'notminor'] &&
423 $this->userOptionsLookup->getOption( $user,
'minordefault' ) )
425 $requestArray[
'wpMinoredit'] =
'';
428 if ( $params[
'recreate'] ) {
429 $requestArray[
'wpRecreate'] =
'';
432 if ( $params[
'section'] !==
null ) {
433 $section = $params[
'section'];
434 if ( !preg_match(
'/^((T-)?\d+|new)$/', $section ) ) {
438 if ( $section !==
'0'
442 $this->
dieWithError( [
'apierror-nosuchsection', $section ] );
444 $requestArray[
'wpSection'] = $params[
'section'];
446 $requestArray[
'wpSection'] =
'';
452 if ( $params[
'watch'] ) {
454 } elseif ( $params[
'unwatch'] ) {
459 $requestArray[
'wpWatchthis'] =
true;
462 if ( $watchlistExpiry ) {
463 $requestArray[
'wpWatchlistExpiry'] = $watchlistExpiry;
468 if ( $params[
'tags'] ) {
470 if ( $tagStatus->isOK() ) {
471 $requestArray[
'wpChangeTags'] = implode(
',', $params[
'tags'] );
479 $requestArray += $this->
getRequest()->getValues();
491 $articleContext->setWikiPage( $pageObj );
492 $articleContext->setUser( $this->
getUser() );
495 $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
497 $ep =
new EditPage( $articleObject );
499 $ep->setApiEditOverride(
true );
500 $ep->setContextTitle( $titleObj );
501 $ep->importFormData( $req );
502 $ep->maybeActivateTempUserCreate(
true );
506 $editRevId = $requestArray[
'editRevId'] ??
false;
507 $baseRev = $this->revisionLookup->getRevisionByTitle( $titleObj, $editRevId );
508 $baseContentModel =
null;
511 $baseContent = $baseRev->getContent( SlotRecord::MAIN );
512 $baseContentModel = $baseContent ? $baseContent->getModel() :
null;
515 $baseContentModel ??= $pageObj->getContentModel();
519 $contentModelsCanDiffer = $params[
'contentmodel'] || isset( $undoContentModel );
521 if ( !$contentModelsCanDiffer && $contentModel !== $baseContentModel ) {
522 $this->
dieWithError( [
'apierror-contentmodel-mismatch', $contentModel, $baseContentModel ] );
526 $oldRevId = $articleObject->getRevIdFetched();
534 $status = $ep->attemptSave( $result );
535 $statusValue = is_int( $status->value ) ? $status->value : 0;
539 switch ( $statusValue ) {
540 case EditPage::AS_HOOK_ERROR:
541 case EditPage::AS_HOOK_ERROR_EXPECTED:
542 if ( $status->statusData !==
null ) {
543 $r = $status->statusData;
544 $r[
'result'] =
'Failure';
548 if ( !$status->getErrors() ) {
551 $status->fatal(
'hookaborted' );
560 case EditPage::AS_BLOCKED_PAGE_FOR_USER:
565 case EditPage::AS_READ_ONLY_PAGE:
569 case EditPage::AS_SUCCESS_NEW_ARTICLE:
573 case EditPage::AS_SUCCESS_UPDATE:
574 $r[
'result'] =
'Success';
575 $r[
'pageid'] = (int)$titleObj->getArticleID();
576 $r[
'title'] = $titleObj->getPrefixedText();
577 $r[
'contentmodel'] = $articleObject->getPage()->getContentModel();
578 $newRevId = $articleObject->getPage()->getLatest();
579 if ( $newRevId == $oldRevId ) {
580 $r[
'nochange'] =
true;
582 $r[
'oldrevid'] = (int)$oldRevId;
583 $r[
'newrevid'] = (int)$newRevId;
585 $pageObj->getTimestamp() );
589 $r[
'watched'] =
true;
592 $this->watchedItemStore,
597 if ( $watchlistExpiry ) {
598 $r[
'watchlistexpiry'] = $watchlistExpiry;
601 $this->persistGlobalSession();
603 if ( isset( $result[
'savedTempUser'] ) ) {
604 $r[
'tempusercreated'] =
true;
605 $params[
'returnto'] ??= $titleObj->getPrefixedDBkey();
606 $redirectUrl = $this->getTempUserRedirectUrl(
608 $result[
'savedTempUser']
610 if ( $redirectUrl ) {
611 $r[
'tempusercreatedredirect'] = $redirectUrl;
618 if ( !$status->getErrors() ) {
621 switch ( $statusValue ) {
623 case EditPage::AS_IMAGE_REDIRECT_ANON:
624 $status->fatal(
'apierror-noimageredirect-anon' );
626 case EditPage::AS_IMAGE_REDIRECT_LOGGED:
627 $status->fatal(
'apierror-noimageredirect' );
629 case EditPage::AS_CONTENT_TOO_BIG:
630 case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
631 $status->fatal(
'apierror-contenttoobig',
632 $this->
getConfig()->
get( MainConfigNames::MaxArticleSize ) );
634 case EditPage::AS_READ_ONLY_PAGE_ANON:
635 $status->fatal(
'apierror-noedit-anon' );
637 case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
638 $status->fatal(
'apierror-cantchangecontentmodel' );
640 case EditPage::AS_ARTICLE_WAS_DELETED:
641 $status->fatal(
'apierror-pagedeleted' );
643 case EditPage::AS_CONFLICT_DETECTED:
644 $status->fatal(
'edit-conflict' );
651 case EditPage::AS_SPAM_ERROR:
653 $status->fatal(
'apierror-spamdetected', $result[
'spam'] );
655 case EditPage::AS_READ_ONLY_PAGE_LOGGED:
656 $status->fatal(
'apierror-noedit' );
658 case EditPage::AS_RATE_LIMITED:
659 $status->fatal(
'apierror-ratelimited' );
661 case EditPage::AS_NO_CREATE_PERMISSION:
662 $status->fatal(
'nocreate-loggedin' );
664 case EditPage::AS_BLANK_ARTICLE:
665 $status->fatal(
'apierror-emptypage' );
667 case EditPage::AS_TEXTBOX_EMPTY:
668 $status->fatal(
'apierror-emptynewsection' );
670 case EditPage::AS_SUMMARY_NEEDED:
671 $status->fatal(
'apierror-summaryrequired' );
674 wfWarn( __METHOD__ .
": Unknown EditPage code $statusValue with no message" );
675 $status->fatal(
'apierror-unknownerror-editpage', $statusValue );
696 ParamValidator::PARAM_TYPE =>
'string',
699 ParamValidator::PARAM_TYPE =>
'integer',
703 ParamValidator::PARAM_TYPE =>
'string',
706 ParamValidator::PARAM_TYPE =>
'text',
710 ParamValidator::PARAM_TYPE =>
'tags',
711 ParamValidator::PARAM_ISMULTI =>
true,
717 ParamValidator::PARAM_TYPE =>
'integer',
720 ParamValidator::PARAM_TYPE =>
'timestamp',
722 'starttimestamp' => [
723 ParamValidator::PARAM_TYPE =>
'timestamp',
726 'createonly' =>
false,
729 ParamValidator::PARAM_DEFAULT =>
false,
730 ParamValidator::PARAM_DEPRECATED =>
true,
733 ParamValidator::PARAM_DEFAULT =>
false,
734 ParamValidator::PARAM_DEPRECATED =>
true,
745 ParamValidator::PARAM_TYPE =>
'text',
748 ParamValidator::PARAM_TYPE =>
'text',
751 ParamValidator::PARAM_TYPE =>
'integer',
752 IntegerDef::PARAM_MIN => 0,
756 ParamValidator::PARAM_TYPE =>
'integer',
757 IntegerDef::PARAM_MIN => 0,
761 ParamValidator::PARAM_TYPE =>
'boolean',
762 ParamValidator::PARAM_DEFAULT =>
false,
765 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
768 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
776 $params += $this->getCreateTempUserParams();