129 $titleObj = $pageObj->getTitle();
133 if ( $params[
'redirect'] ) {
134 if ( $params[
'prependtext'] ===
null
135 && $params[
'appendtext'] ===
null
136 && $params[
'section'] !==
'new'
140 if ( $titleObj->isRedirect() ) {
141 $oldTarget = $titleObj;
142 $redirTarget = $this->redirectLookup->getRedirectTarget( $oldTarget );
143 $redirTarget = Title::castFromLinkTarget( $redirTarget );
146 'from' => $titleObj->getPrefixedText(),
147 'to' => $redirTarget->getPrefixedText()
151 if ( $redirTarget->isExternal() || !$redirTarget->canExist() ) {
152 $redirValues[
'to'] = $redirTarget->getFullText();
155 'apierror-edit-invalidredirect',
159 'edit-invalidredirect',
160 [
'redirects' => $redirValues ]
165 $apiResult->addValue(
null,
'redirects', $redirValues );
168 $pageObj = $this->wikiPageFactory->newFromTitle( $redirTarget );
169 $titleObj = $pageObj->getTitle();
175 if ( $params[
'contentmodel'] ) {
176 $contentHandler = $this->contentHandlerFactory->getContentHandler( $params[
'contentmodel'] );
178 $contentHandler = $pageObj->getContentHandler();
180 $contentModel = $contentHandler->getModelID();
182 $name = $titleObj->getPrefixedDBkey();
184 if ( $params[
'undo'] > 0 ) {
186 } elseif ( $contentHandler->supportsDirectApiEditing() ===
false ) {
187 $this->
dieWithError( [
'apierror-no-direct-editing', $contentModel, $name ] );
190 $contentFormat = $params[
'contentformat'] ?: $contentHandler->getDefaultFormat();
192 if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
193 $this->
dieWithError( [
'apierror-badformat', $contentFormat, $contentModel, $name ] );
196 if ( $params[
'createonly'] && $titleObj->exists() ) {
199 if ( $params[
'nocreate'] && !$titleObj->exists() ) {
207 [
'autoblock' =>
true,
'user' => $this->getUserForPermissions() ]
210 $toMD5 = $params[
'text'];
211 if ( $params[
'appendtext'] !==
null || $params[
'prependtext'] !==
null ) {
212 $content = $pageObj->getContent();
216 # If this is a MediaWiki:x message, then load the messages
217 # and return the message value for x.
218 $text = $titleObj->getDefaultMessageText();
219 if ( $text ===
false ) {
224 $content = ContentHandler::makeContent( $text, $titleObj );
227 'wrap' =>
ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
231 # Otherwise, make a new empty content.
232 $content = $contentHandler->makeEmptyContent();
239 $this->
dieWithError( [
'apierror-appendnotsupported', $contentModel ] );
242 if ( $params[
'section'] !==
null ) {
243 if ( !$contentHandler->supportsSections() ) {
244 $this->
dieWithError( [
'apierror-sectionsnotsupported', $contentModel ] );
247 if ( $params[
'section'] ==
'new' ) {
252 $section = $params[
'section'];
253 $content = $content->getSection( $section );
264 $text = $content->serialize( $contentFormat );
267 $params[
'text'] = $params[
'prependtext'] . $text . $params[
'appendtext'];
268 $toMD5 = $params[
'prependtext'] . $params[
'appendtext'];
271 if ( $params[
'undo'] > 0 ) {
272 $undoRev = $this->revisionLookup->getRevisionById( $params[
'undo'] );
273 if ( $undoRev ===
null || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
274 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undo'] ] );
277 if ( $params[
'undoafter'] > 0 ) {
278 $undoafterRev = $this->revisionLookup->getRevisionById( $params[
'undoafter'] );
281 $undoafterRev = $this->revisionLookup->getPreviousRevision( $undoRev );
283 if ( $undoafterRev ===
null || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
284 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undoafter'] ] );
287 if ( $undoRev->getPageId() != $pageObj->getId() ) {
288 $this->
dieWithError( [
'apierror-revwrongpage', $undoRev->getId(),
289 $titleObj->getPrefixedText() ] );
291 if ( $undoafterRev->getPageId() != $pageObj->getId() ) {
292 $this->
dieWithError( [
'apierror-revwrongpage', $undoafterRev->getId(),
293 $titleObj->getPrefixedText() ] );
296 $newContent = $contentHandler->getUndoContent(
298 $pageObj->getRevisionRecord()->getContent( SlotRecord::MAIN ),
300 $undoRev->getContent( SlotRecord::MAIN ),
302 $undoafterRev->getContent( SlotRecord::MAIN ),
303 $pageObj->getRevisionRecord()->getId() === $undoRev->getId()
306 if ( !$newContent ) {
309 if ( !$params[
'contentmodel'] && !$params[
'contentformat'] ) {
315 if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
316 $undoafterRevMainSlot = $undoafterRev->getSlot(
320 $contentFormat = $undoafterRevMainSlot->getFormat();
321 if ( !$contentFormat ) {
324 $contentFormat = $this->contentHandlerFactory
325 ->getContentHandler( $undoafterRevMainSlot->getModel() )
326 ->getDefaultFormat();
330 $contentModel = $newContent->getModel();
331 $undoContentModel =
true;
333 $params[
'text'] = $newContent->serialize( $contentFormat );
337 if ( $params[
'summary'] ===
null ) {
338 $nextRev = $this->revisionLookup->getNextRevision( $undoafterRev );
339 if ( $nextRev && $nextRev->getId() == $params[
'undo'] ) {
340 $undoRevUser = $undoRev->getUser();
341 $params[
'summary'] = $this->
msg(
'undo-summary' )
342 ->params( $params[
'undo'], $undoRevUser ? $undoRevUser->getName() :
'' )
343 ->inContentLanguage()->text();
349 if ( $params[
'md5'] !==
null && md5( $toMD5 ) !== $params[
'md5'] ) {
357 'wpTextbox1' => $params[
'text'],
358 'format' => $contentFormat,
359 'model' => $contentModel,
360 'wpEditToken' => $params[
'token'],
361 'wpIgnoreBlankSummary' =>
true,
362 'wpIgnoreBlankArticle' =>
true,
363 'wpIgnoreProblematicRedirects' =>
true,
364 'bot' => $params[
'bot'],
365 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
369 if ( $params[
'summary'] !==
null ) {
370 $requestArray[
'wpSummary'] = $params[
'summary'];
373 if ( $params[
'sectiontitle'] !==
null ) {
374 $requestArray[
'wpSectionTitle'] = $params[
'sectiontitle'];
377 if ( $params[
'undo'] > 0 ) {
378 $requestArray[
'wpUndidRevision'] = $params[
'undo'];
380 if ( $params[
'undoafter'] > 0 ) {
381 $requestArray[
'wpUndoAfter'] = $params[
'undoafter'];
385 if ( !empty( $params[
'baserevid'] ) ) {
386 $requestArray[
'editRevId'] = $params[
'baserevid'];
391 if ( $params[
'basetimestamp'] !==
null && (
bool)$this->
getMain()->getVal(
'basetimestamp' ) ) {
392 $requestArray[
'wpEdittime'] = $params[
'basetimestamp'];
393 } elseif ( empty( $params[
'baserevid'] ) ) {
396 $requestArray[
'wpEdittime'] = $pageObj->getTimestamp();
399 if ( $params[
'starttimestamp'] !==
null ) {
400 $requestArray[
'wpStarttime'] = $params[
'starttimestamp'];
405 if ( $params[
'minor'] || ( !$params[
'notminor'] &&
406 $this->userOptionsLookup->getOption( $user,
'minordefault' ) )
408 $requestArray[
'wpMinoredit'] =
'';
411 if ( $params[
'recreate'] ) {
412 $requestArray[
'wpRecreate'] =
'';
415 if ( $params[
'section'] !==
null ) {
416 $section = $params[
'section'];
417 if ( !preg_match(
'/^((T-)?\d+|new)$/', $section ) ) {
420 $content = $pageObj->getContent();
421 if ( $section !==
'0'
423 && ( !$content || !$content->getSection( $section ) )
425 $this->
dieWithError( [
'apierror-nosuchsection', $section ] );
427 $requestArray[
'wpSection'] = $params[
'section'];
429 $requestArray[
'wpSection'] =
'';
435 if ( $params[
'watch'] ) {
437 } elseif ( $params[
'unwatch'] ) {
442 $requestArray[
'wpWatchthis'] =
true;
443 $prefName =
'watchdefault-expiry';
444 if ( !$pageObj->exists() ) {
445 $prefName =
'watchcreations-expiry';
449 if ( $watchlistExpiry ) {
450 $requestArray[
'wpWatchlistExpiry'] = $watchlistExpiry;
455 if ( $params[
'tags'] ) {
456 $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params[
'tags'], $this->
getAuthority() );
457 if ( $tagStatus->isOK() ) {
458 $requestArray[
'wpChangeTags'] = implode(
',', $params[
'tags'] );
466 $requestArray += $this->
getRequest()->getValues();
479 $articleContext->setWikiPage( $pageObj );
480 $articleContext->setUser( $this->
getUser() );
483 $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
485 $ep =
new EditPage( $articleObject );
487 $ep->setApiEditOverride(
true );
488 $ep->setContextTitle( $titleObj );
489 $ep->importFormData( $req );
490 $tempUserCreateStatus = $ep->maybeActivateTempUserCreate(
true );
491 if ( !$tempUserCreateStatus->isOK() ) {
492 $this->
dieWithError(
'apierror-tempuseracquirefailed',
'tempuseracquirefailed' );
497 $editRevId = $requestArray[
'editRevId'] ??
false;
498 $baseRev = $this->revisionLookup->getRevisionByTitle( $titleObj, $editRevId );
499 $baseContentModel =
null;
502 $baseContent = $baseRev->getContent( SlotRecord::MAIN );
503 $baseContentModel = $baseContent ? $baseContent->getModel() :
null;
506 $baseContentModel ??= $pageObj->getContentModel();
510 $contentModelsCanDiffer = $params[
'contentmodel'] || isset( $undoContentModel );
512 if ( !$contentModelsCanDiffer && $contentModel !== $baseContentModel ) {
513 $this->
dieWithError( [
'apierror-contentmodel-mismatch', $contentModel, $baseContentModel ] );
517 $oldRevId = $articleObject->getRevIdFetched();
526 $status = $ep->attemptSave( $result );
527 $statusValue = is_int( $status->value ) ? $status->value : 0;
532 switch ( $statusValue ) {
533 case EditPage::AS_HOOK_ERROR:
534 case EditPage::AS_HOOK_ERROR_EXPECTED:
535 if ( $status->statusData !==
null ) {
536 $r = $status->statusData;
537 $r[
'result'] =
'Failure';
541 if ( !$status->getMessages() ) {
544 $status->fatal(
'hookaborted' );
553 case EditPage::AS_BLOCKED_PAGE_FOR_USER:
558 case EditPage::AS_READ_ONLY_PAGE:
562 case EditPage::AS_SUCCESS_NEW_ARTICLE:
566 case EditPage::AS_SUCCESS_UPDATE:
567 $r[
'result'] =
'Success';
568 $r[
'pageid'] = (int)$titleObj->getArticleID();
569 $r[
'title'] = $titleObj->getPrefixedText();
570 $r[
'contentmodel'] = $articleObject->getPage()->getContentModel();
571 $newRevId = $articleObject->getPage()->getLatest();
572 if ( $newRevId == $oldRevId ) {
573 $r[
'nochange'] =
true;
575 $r[
'oldrevid'] = (int)$oldRevId;
576 $r[
'newrevid'] = (int)$newRevId;
578 $pageObj->getTimestamp() );
582 $r[
'watched'] =
true;
585 $this->watchedItemStore,
590 if ( $watchlistExpiry ) {
591 $r[
'watchlistexpiry'] = $watchlistExpiry;
594 $this->persistGlobalSession();
604 $shouldRedirectForTempUser = isset( $result[
'savedTempUser'] ) ||
605 ( $user->isTemp() && ( $user->getEditCount() === 0 ) );
606 if ( $shouldRedirectForTempUser ) {
607 $r[
'tempusercreated'] =
true;
608 $params[
'returnto'] ??= $titleObj->getPrefixedDBkey();
609 $redirectUrl = $this->getTempUserRedirectUrl(
611 $result[
'savedTempUser'] ?? $user
613 if ( $redirectUrl ) {
614 $r[
'tempusercreatedredirect'] = $redirectUrl;
621 if ( !$status->getMessages() ) {
624 switch ( $statusValue ) {
626 case EditPage::AS_IMAGE_REDIRECT_ANON:
627 $status->fatal(
'apierror-noimageredirect-anon' );
629 case EditPage::AS_IMAGE_REDIRECT_LOGGED:
630 $status->fatal(
'apierror-noimageredirect' );
632 case EditPage::AS_READ_ONLY_PAGE_ANON:
633 $status->fatal(
'apierror-noedit-anon' );
635 case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
636 $status->fatal(
'apierror-cantchangecontentmodel' );
638 case EditPage::AS_ARTICLE_WAS_DELETED:
639 $status->fatal(
'apierror-pagedeleted' );
641 case EditPage::AS_CONFLICT_DETECTED:
642 $status->fatal(
'edit-conflict' );
649 case EditPage::AS_SPAM_ERROR:
651 $status->fatal(
'apierror-spamdetected', $result[
'spam'] );
653 case EditPage::AS_READ_ONLY_PAGE_LOGGED:
654 $status->fatal(
'apierror-noedit' );
656 case EditPage::AS_RATE_LIMITED:
657 $status->fatal(
'apierror-ratelimited' );
659 case EditPage::AS_NO_CREATE_PERMISSION:
660 $status->fatal(
'nocreate-loggedin' );
662 case EditPage::AS_BLANK_ARTICLE:
663 $status->fatal(
'apierror-emptypage' );
665 case EditPage::AS_TEXTBOX_EMPTY:
666 $status->fatal(
'apierror-emptynewsection' );
668 case EditPage::AS_SUMMARY_NEEDED:
669 $status->fatal(
'apierror-summaryrequired' );
672 wfWarn( __METHOD__ .
": Unknown EditPage code $statusValue with no message" );
673 $status->fatal(
'apierror-unknownerror-editpage', $statusValue );
697 ParamValidator::PARAM_TYPE =>
'string',
700 ParamValidator::PARAM_TYPE =>
'integer',
704 ParamValidator::PARAM_TYPE =>
'string',
707 ParamValidator::PARAM_TYPE =>
'text',
711 ParamValidator::PARAM_TYPE =>
'tags',
712 ParamValidator::PARAM_ISMULTI =>
true,
718 ParamValidator::PARAM_TYPE =>
'integer',
721 ParamValidator::PARAM_TYPE =>
'timestamp',
723 'starttimestamp' => [
724 ParamValidator::PARAM_TYPE =>
'timestamp',
727 'createonly' =>
false,
730 ParamValidator::PARAM_DEFAULT =>
false,
731 ParamValidator::PARAM_DEPRECATED =>
true,
734 ParamValidator::PARAM_DEFAULT =>
false,
735 ParamValidator::PARAM_DEPRECATED =>
true,
746 ParamValidator::PARAM_TYPE =>
'text',
749 ParamValidator::PARAM_TYPE =>
'text',
752 ParamValidator::PARAM_TYPE =>
'integer',
753 IntegerDef::PARAM_MIN => 0,
757 ParamValidator::PARAM_TYPE =>
'integer',
758 IntegerDef::PARAM_MIN => 0,
762 ParamValidator::PARAM_TYPE =>
'boolean',
763 ParamValidator::PARAM_DEFAULT =>
false,
766 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
769 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
777 $params += $this->getCreateTempUserParams();