150 $titleObj = $pageObj->getTitle();
155 if (
$params[
'prependtext'] ===
null
156 &&
$params[
'appendtext'] ===
null
157 &&
$params[
'section'] !==
'new'
161 if ( $titleObj->isRedirect() ) {
162 $oldTarget = $titleObj;
163 $redirTarget = $this->redirectLookup->getRedirectTarget( $oldTarget );
164 $redirTarget = Title::castFromLinkTarget( $redirTarget );
167 'from' => $titleObj->getPrefixedText(),
168 'to' => $redirTarget->getPrefixedText()
172 if ( $redirTarget->isExternal() || !$redirTarget->canExist() ) {
173 $redirValues[
'to'] = $redirTarget->getFullText();
176 'apierror-edit-invalidredirect',
177 Message::plaintextParam( $oldTarget->getPrefixedText() ),
178 Message::plaintextParam( $redirTarget->getFullText() ),
180 'edit-invalidredirect',
181 [
'redirects' => $redirValues ]
185 ApiResult::setIndexedTagName( $redirValues,
'r' );
186 $apiResult->addValue(
null,
'redirects', $redirValues );
189 $pageObj = $this->wikiPageFactory->newFromTitle( $redirTarget );
190 $titleObj = $pageObj->getTitle();
196 if (
$params[
'contentmodel'] ) {
197 $contentHandler = $this->contentHandlerFactory->getContentHandler(
$params[
'contentmodel'] );
199 $contentHandler = $pageObj->getContentHandler();
201 $contentModel = $contentHandler->getModelID();
203 $name = $titleObj->getPrefixedDBkey();
207 } elseif ( $contentHandler->supportsDirectApiEditing() ===
false ) {
208 $this->
dieWithError( [
'apierror-no-direct-editing', $contentModel, $name ] );
211 $contentFormat =
$params[
'contentformat'] ?: $contentHandler->getDefaultFormat();
213 if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
214 $this->
dieWithError( [
'apierror-badformat', $contentFormat, $contentModel, $name ] );
217 if (
$params[
'createonly'] && $titleObj->exists() ) {
220 if (
$params[
'nocreate'] && !$titleObj->exists() ) {
228 [
'autoblock' =>
true,
'user' => $this->getUserForPermissions() ]
232 if (
$params[
'appendtext'] !==
null ||
$params[
'prependtext'] !==
null ) {
233 $content = $pageObj->getContent();
237 # If this is a MediaWiki:x message, then load the messages
238 # and return the message value for x.
239 $text = $titleObj->getDefaultMessageText();
240 if ( $text ===
false ) {
245 $content = ContentHandler::makeContent( $text, $titleObj );
248 'wrap' => ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
252 # Otherwise, make a new empty content.
253 $content = $contentHandler->makeEmptyContent();
260 $this->
dieWithError( [
'apierror-appendnotsupported', $contentModel ] );
263 if (
$params[
'section'] !==
null ) {
264 if ( !$contentHandler->supportsSections() ) {
265 $this->
dieWithError( [
'apierror-sectionsnotsupported', $contentModel ] );
268 if (
$params[
'section'] ==
'new' ) {
274 $content = $content->getSection( $section );
285 $text = $content->serialize( $contentFormat );
293 $undoRev = $this->revisionLookup->getRevisionById(
$params[
'undo'] );
294 if ( $undoRev ===
null || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
298 if (
$params[
'undoafter'] > 0 ) {
299 $undoafterRev = $this->revisionLookup->getRevisionById(
$params[
'undoafter'] );
302 $undoafterRev = $this->revisionLookup->getPreviousRevision( $undoRev );
304 if ( $undoafterRev ===
null || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
308 if ( $undoRev->getPageId() != $pageObj->getId() ) {
309 $this->
dieWithError( [
'apierror-revwrongpage', $undoRev->getId(),
310 $titleObj->getPrefixedText() ] );
312 if ( $undoafterRev->getPageId() != $pageObj->getId() ) {
313 $this->
dieWithError( [
'apierror-revwrongpage', $undoafterRev->getId(),
314 $titleObj->getPrefixedText() ] );
317 $newContent = $contentHandler->getUndoContent(
319 $pageObj->getRevisionRecord()->getContent( SlotRecord::MAIN ),
321 $undoRev->getContent( SlotRecord::MAIN ),
323 $undoafterRev->getContent( SlotRecord::MAIN ),
324 $pageObj->getRevisionRecord()->getId() === $undoRev->getId()
327 if ( !$newContent ) {
336 if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
337 $undoafterRevMainSlot = $undoafterRev->getSlot(
341 $contentFormat = $undoafterRevMainSlot->getFormat();
342 if ( !$contentFormat ) {
345 $contentFormat = $this->contentHandlerFactory
346 ->getContentHandler( $undoafterRevMainSlot->getModel() )
347 ->getDefaultFormat();
351 $contentModel = $newContent->getModel();
352 $undoContentModel =
true;
354 $params[
'text'] = $newContent->serialize( $contentFormat );
358 if (
$params[
'summary'] ===
null ) {
359 $nextRev = $this->revisionLookup->getNextRevision( $undoafterRev );
360 if ( $nextRev && $nextRev->getId() ==
$params[
'undo'] ) {
361 $undoRevUser = $undoRev->getUser();
362 $params[
'summary'] = $this->
msg(
'undo-summary' )
363 ->params(
$params[
'undo'], $undoRevUser ? $undoRevUser->getName() :
'' )
364 ->inContentLanguage()->text();
370 if (
$params[
'md5'] !==
null && md5( $toMD5 ) !==
$params[
'md5'] ) {
378 'wpTextbox1' =>
$params[
'text'],
379 'format' => $contentFormat,
380 'model' => $contentModel,
381 'wpEditToken' =>
$params[
'token'],
382 'wpIgnoreBlankSummary' =>
true,
383 'wpIgnoreBlankArticle' =>
true,
384 'wpIgnoreSelfRedirect' =>
true,
386 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
390 if (
$params[
'summary'] !==
null ) {
391 $requestArray[
'wpSummary'] =
$params[
'summary'];
394 if (
$params[
'sectiontitle'] !==
null ) {
395 $requestArray[
'wpSectionTitle'] =
$params[
'sectiontitle'];
399 $requestArray[
'wpUndidRevision'] =
$params[
'undo'];
401 if (
$params[
'undoafter'] > 0 ) {
402 $requestArray[
'wpUndoAfter'] =
$params[
'undoafter'];
406 if ( !empty(
$params[
'baserevid'] ) ) {
407 $requestArray[
'editRevId'] =
$params[
'baserevid'];
412 if (
$params[
'basetimestamp'] !==
null && (
bool)$this->
getMain()->getVal(
'basetimestamp' ) ) {
413 $requestArray[
'wpEdittime'] =
$params[
'basetimestamp'];
414 } elseif ( empty(
$params[
'baserevid'] ) ) {
417 $requestArray[
'wpEdittime'] = $pageObj->getTimestamp();
420 if (
$params[
'starttimestamp'] !==
null ) {
421 $requestArray[
'wpStarttime'] =
$params[
'starttimestamp'];
427 $this->userOptionsLookup->getOption( $user,
'minordefault' ) )
429 $requestArray[
'wpMinoredit'] =
'';
433 $requestArray[
'wpRecreate'] =
'';
436 if (
$params[
'section'] !==
null ) {
438 if ( !preg_match(
'/^((T-)?\d+|new)$/', $section ) ) {
441 $content = $pageObj->getContent();
442 if ( $section !==
'0'
444 && ( !$content || !$content->getSection( $section ) )
446 $this->
dieWithError( [
'apierror-nosuchsection', $section ] );
448 $requestArray[
'wpSection'] =
$params[
'section'];
450 $requestArray[
'wpSection'] =
'';
458 } elseif (
$params[
'unwatch'] ) {
463 $requestArray[
'wpWatchthis'] =
true;
466 if ( $watchlistExpiry ) {
467 $requestArray[
'wpWatchlistExpiry'] = $watchlistExpiry;
474 if ( $tagStatus->isOK() ) {
475 $requestArray[
'wpChangeTags'] = implode(
',',
$params[
'tags'] );
483 $requestArray += $this->
getRequest()->getValues();
496 $articleContext->setWikiPage( $pageObj );
497 $articleContext->setUser( $this->
getUser() );
500 $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
502 $ep =
new EditPage( $articleObject );
504 $ep->setApiEditOverride(
true );
505 $ep->setContextTitle( $titleObj );
506 $ep->importFormData( $req );
507 $tempUserCreateStatus = $ep->maybeActivateTempUserCreate(
true );
508 if ( !$tempUserCreateStatus->isOK() ) {
509 $this->
dieWithError(
'apierror-tempuseracquirefailed',
'tempuseracquirefailed' );
514 $editRevId = $requestArray[
'editRevId'] ??
false;
515 $baseRev = $this->revisionLookup->getRevisionByTitle( $titleObj, $editRevId );
516 $baseContentModel =
null;
519 $baseContent = $baseRev->getContent( SlotRecord::MAIN );
520 $baseContentModel = $baseContent ? $baseContent->getModel() :
null;
523 $baseContentModel ??= $pageObj->getContentModel();
527 $contentModelsCanDiffer =
$params[
'contentmodel'] || isset( $undoContentModel );
529 if ( !$contentModelsCanDiffer && $contentModel !== $baseContentModel ) {
530 $this->
dieWithError( [
'apierror-contentmodel-mismatch', $contentModel, $baseContentModel ] );
534 $oldRevId = $articleObject->getRevIdFetched();
543 $status = $ep->attemptSave( $result );
544 $statusValue = is_int( $status->value ) ? $status->value : 0;
549 switch ( $statusValue ) {
550 case EditPage::AS_HOOK_ERROR:
551 case EditPage::AS_HOOK_ERROR_EXPECTED:
552 if ( $status->statusData !==
null ) {
553 $r = $status->statusData;
554 $r[
'result'] =
'Failure';
558 if ( !$status->getMessages() ) {
561 $status->fatal(
'hookaborted' );
570 case EditPage::AS_BLOCKED_PAGE_FOR_USER:
575 case EditPage::AS_READ_ONLY_PAGE:
579 case EditPage::AS_SUCCESS_NEW_ARTICLE:
583 case EditPage::AS_SUCCESS_UPDATE:
584 $r[
'result'] =
'Success';
585 $r[
'pageid'] = (int)$titleObj->getArticleID();
586 $r[
'title'] = $titleObj->getPrefixedText();
587 $r[
'contentmodel'] = $articleObject->getPage()->getContentModel();
588 $newRevId = $articleObject->getPage()->getLatest();
589 if ( $newRevId == $oldRevId ) {
590 $r[
'nochange'] =
true;
592 $r[
'oldrevid'] = (int)$oldRevId;
593 $r[
'newrevid'] = (int)$newRevId;
595 $pageObj->getTimestamp() );
599 $r[
'watched'] =
true;
602 $this->watchedItemStore,
607 if ( $watchlistExpiry ) {
608 $r[
'watchlistexpiry'] = $watchlistExpiry;
611 $this->persistGlobalSession();
621 $shouldRedirectForTempUser = isset( $result[
'savedTempUser'] ) ||
622 ( $user->isTemp() && ( $user->getEditCount() === 0 ) );
623 if ( $shouldRedirectForTempUser ) {
624 $r[
'tempusercreated'] =
true;
625 $params[
'returnto'] ??= $titleObj->getPrefixedDBkey();
626 $redirectUrl = $this->getTempUserRedirectUrl(
628 $result[
'savedTempUser'] ?? $user
630 if ( $redirectUrl ) {
631 $r[
'tempusercreatedredirect'] = $redirectUrl;
638 if ( !$status->getMessages() ) {
641 switch ( $statusValue ) {
643 case EditPage::AS_IMAGE_REDIRECT_ANON:
644 $status->fatal(
'apierror-noimageredirect-anon' );
646 case EditPage::AS_IMAGE_REDIRECT_LOGGED:
647 $status->fatal(
'apierror-noimageredirect' );
649 case EditPage::AS_CONTENT_TOO_BIG:
650 case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
651 $status->fatal(
'apierror-contenttoobig',
652 $this->
getConfig()->
get( MainConfigNames::MaxArticleSize ) );
654 case EditPage::AS_READ_ONLY_PAGE_ANON:
655 $status->fatal(
'apierror-noedit-anon' );
657 case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
658 $status->fatal(
'apierror-cantchangecontentmodel' );
660 case EditPage::AS_ARTICLE_WAS_DELETED:
661 $status->fatal(
'apierror-pagedeleted' );
663 case EditPage::AS_CONFLICT_DETECTED:
664 $status->fatal(
'edit-conflict' );
671 case EditPage::AS_SPAM_ERROR:
673 $status->fatal(
'apierror-spamdetected', $result[
'spam'] );
675 case EditPage::AS_READ_ONLY_PAGE_LOGGED:
676 $status->fatal(
'apierror-noedit' );
678 case EditPage::AS_RATE_LIMITED:
679 $status->fatal(
'apierror-ratelimited' );
681 case EditPage::AS_NO_CREATE_PERMISSION:
682 $status->fatal(
'nocreate-loggedin' );
684 case EditPage::AS_BLANK_ARTICLE:
685 $status->fatal(
'apierror-emptypage' );
687 case EditPage::AS_TEXTBOX_EMPTY:
688 $status->fatal(
'apierror-emptynewsection' );
690 case EditPage::AS_SUMMARY_NEEDED:
691 $status->fatal(
'apierror-summaryrequired' );
694 wfWarn( __METHOD__ .
": Unknown EditPage code $statusValue with no message" );
695 $status->fatal(
'apierror-unknownerror-editpage', $statusValue );
716 ParamValidator::PARAM_TYPE =>
'string',
719 ParamValidator::PARAM_TYPE =>
'integer',
723 ParamValidator::PARAM_TYPE =>
'string',
726 ParamValidator::PARAM_TYPE =>
'text',
730 ParamValidator::PARAM_TYPE =>
'tags',
731 ParamValidator::PARAM_ISMULTI =>
true,
737 ParamValidator::PARAM_TYPE =>
'integer',
740 ParamValidator::PARAM_TYPE =>
'timestamp',
742 'starttimestamp' => [
743 ParamValidator::PARAM_TYPE =>
'timestamp',
746 'createonly' =>
false,
749 ParamValidator::PARAM_DEFAULT =>
false,
750 ParamValidator::PARAM_DEPRECATED =>
true,
753 ParamValidator::PARAM_DEFAULT =>
false,
754 ParamValidator::PARAM_DEPRECATED =>
true,
765 ParamValidator::PARAM_TYPE =>
'text',
768 ParamValidator::PARAM_TYPE =>
'text',
771 ParamValidator::PARAM_TYPE =>
'integer',
772 IntegerDef::PARAM_MIN => 0,
776 ParamValidator::PARAM_TYPE =>
'integer',
777 IntegerDef::PARAM_MIN => 0,
781 ParamValidator::PARAM_TYPE =>
'boolean',
782 ParamValidator::PARAM_DEFAULT =>
false,
785 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
788 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
796 $params += $this->getCreateTempUserParams();