132 $titleObj = $pageObj->getTitle();
136 if ( $params[
'redirect'] ) {
137 if ( $params[
'prependtext'] ===
null
138 && $params[
'appendtext'] ===
null
139 && $params[
'section'] !==
'new'
143 if ( $titleObj->isRedirect() ) {
144 $oldTarget = $titleObj;
145 $redirTarget = $this->redirectLookup->getRedirectTarget( $oldTarget );
146 $redirTarget = Title::castFromLinkTarget( $redirTarget );
149 'from' => $titleObj->getPrefixedText(),
150 'to' => $redirTarget->getPrefixedText()
154 if ( $redirTarget->isExternal() || !$redirTarget->canExist() ) {
155 $redirValues[
'to'] = $redirTarget->getFullText();
158 'apierror-edit-invalidredirect',
162 'edit-invalidredirect',
163 [
'redirects' => $redirValues ]
168 $apiResult->addValue(
null,
'redirects', $redirValues );
171 $pageObj = $this->wikiPageFactory->newFromTitle( $redirTarget );
172 $titleObj = $pageObj->getTitle();
178 if ( $params[
'contentmodel'] ) {
179 $contentHandler = $this->contentHandlerFactory->getContentHandler( $params[
'contentmodel'] );
181 $contentHandler = $pageObj->getContentHandler();
183 $contentModel = $contentHandler->getModelID();
185 $name = $titleObj->getPrefixedDBkey();
187 if ( $params[
'undo'] > 0 ) {
189 } elseif ( $contentHandler->supportsDirectApiEditing() ===
false ) {
190 $this->
dieWithError( [
'apierror-no-direct-editing', $contentModel, $name ] );
193 $contentFormat = $params[
'contentformat'] ?: $contentHandler->getDefaultFormat();
195 if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
196 $this->
dieWithError( [
'apierror-badformat', $contentFormat, $contentModel, $name ] );
199 if ( $params[
'createonly'] && $titleObj->exists() ) {
202 if ( $params[
'nocreate'] && !$titleObj->exists() ) {
210 [
'autoblock' =>
true,
'user' => $this->getUserForPermissions() ]
213 $toMD5 = $params[
'text'];
214 if ( $params[
'appendtext'] !==
null || $params[
'prependtext'] !==
null ) {
216 $content = $pageObj->getContent()
217 ?? $this->shadowPageLoader->get( $titleObj )?->getPreloadContent()
218 ?? $contentHandler->makeEmptyContent();
221 'wrap' =>
ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
228 $this->
dieWithError( [
'apierror-appendnotsupported', $contentModel ] );
231 if ( $params[
'section'] !==
null ) {
232 if ( !$contentHandler->supportsSections() ) {
233 $this->
dieWithError( [
'apierror-sectionsnotsupported', $contentModel ] );
236 if ( $params[
'section'] ==
'new' ) {
241 $section = $params[
'section'];
242 $content = $content->getSection( $section );
253 $text = $content->serialize( $contentFormat );
256 $params[
'text'] = $params[
'prependtext'] . $text . $params[
'appendtext'];
257 $toMD5 = $params[
'prependtext'] . $params[
'appendtext'];
260 if ( $params[
'undo'] > 0 ) {
261 $undoRev = $this->revisionLookup->getRevisionById( $params[
'undo'] );
262 if ( $undoRev ===
null || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
263 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undo'] ] );
266 if ( $params[
'undoafter'] > 0 ) {
267 $undoafterRev = $this->revisionLookup->getRevisionById( $params[
'undoafter'] );
270 $undoafterRev = $this->revisionLookup->getPreviousRevision( $undoRev );
272 if ( $undoafterRev ===
null || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
273 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'undoafter'] ] );
276 if ( $undoRev->getPageId() != $pageObj->getId() ) {
277 $this->
dieWithError( [
'apierror-revwrongpage', $undoRev->getId(),
278 $titleObj->getPrefixedText() ] );
280 if ( $undoafterRev->getPageId() != $pageObj->getId() ) {
281 $this->
dieWithError( [
'apierror-revwrongpage', $undoafterRev->getId(),
282 $titleObj->getPrefixedText() ] );
285 $newContent = $contentHandler->getUndoContent(
287 $pageObj->getRevisionRecord()->getContent( SlotRecord::MAIN ),
289 $undoRev->getContent( SlotRecord::MAIN ),
291 $undoafterRev->getContent( SlotRecord::MAIN ),
292 $pageObj->getRevisionRecord()->getId() === $undoRev->getId()
295 if ( !$newContent ) {
298 if ( !$params[
'contentmodel'] && !$params[
'contentformat'] ) {
304 if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
305 $undoafterRevMainSlot = $undoafterRev->getSlot(
309 $contentFormat = $undoafterRevMainSlot->getFormat();
310 if ( !$contentFormat ) {
313 $contentFormat = $this->contentHandlerFactory
314 ->getContentHandler( $undoafterRevMainSlot->getModel() )
315 ->getDefaultFormat();
319 $contentModel = $newContent->getModel();
320 $undoContentModel =
true;
322 $params[
'text'] = $newContent->serialize( $contentFormat );
326 if ( $params[
'summary'] ===
null ) {
327 $nextRev = $this->revisionLookup->getNextRevision( $undoafterRev );
328 if ( $nextRev && $nextRev->getId() == $params[
'undo'] ) {
329 $undoRevUser = $undoRev->getUser();
330 $params[
'summary'] = $this->
msg(
'undo-summary' )
331 ->params( $params[
'undo'], $undoRevUser ? $undoRevUser->getName() :
'' )
332 ->inContentLanguage()->text();
338 if ( $params[
'md5'] !==
null && md5( $toMD5 ) !== $params[
'md5'] ) {
345 'wpTextbox1' => $params[
'text'],
346 'format' => $contentFormat,
347 'model' => $contentModel,
348 'wpEditToken' => $params[
'token'],
349 'wpIgnoreBlankSummary' =>
true,
350 'wpIgnoreBlankArticle' =>
true,
351 'wpIgnoreProblematicRedirects' =>
true,
352 'bot' => $params[
'bot'],
353 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
356 if ( $params[
'summary'] !==
null ) {
357 $requestArray[
'wpSummary'] = $params[
'summary'];
360 if ( $params[
'sectiontitle'] !==
null ) {
361 $requestArray[
'wpSectionTitle'] = $params[
'sectiontitle'];
364 if ( $params[
'undo'] > 0 ) {
365 $requestArray[
'wpUndidRevision'] = $params[
'undo'];
367 if ( $params[
'undoafter'] > 0 ) {
368 $requestArray[
'wpUndoAfter'] = $params[
'undoafter'];
372 if ( !empty( $params[
'baserevid'] ) ) {
373 $requestArray[
'editRevId'] = $params[
'baserevid'];
378 if ( $params[
'basetimestamp'] !==
null && (
bool)$this->
getMain()->getVal(
'basetimestamp' ) ) {
379 $requestArray[
'wpEdittime'] = $params[
'basetimestamp'];
380 } elseif ( empty( $params[
'baserevid'] ) ) {
383 $requestArray[
'wpEdittime'] = $pageObj->getTimestamp();
386 if ( $params[
'starttimestamp'] !==
null ) {
387 $requestArray[
'wpStarttime'] = $params[
'starttimestamp'];
392 if ( $params[
'minor'] || ( !$params[
'notminor'] &&
393 $this->userOptionsLookup->getOption( $user,
'minordefault' ) )
395 $requestArray[
'wpMinoredit'] =
'';
398 if ( $params[
'recreate'] ) {
399 $requestArray[
'wpRecreate'] =
'';
402 if ( $params[
'section'] !==
null ) {
403 $section = $params[
'section'];
404 if ( !preg_match(
'/^((T-)?\d+|new)$/', $section ) ) {
407 $content = $pageObj->getContent();
408 if ( $section !==
'0'
410 && ( !$content || !$content->getSection( $section ) )
412 $this->
dieWithError( [
'apierror-nosuchsection', $section ] );
414 $requestArray[
'wpSection'] = $params[
'section'];
416 $requestArray[
'wpSection'] =
'';
422 if ( $params[
'watch'] ) {
424 } elseif ( $params[
'unwatch'] ) {
429 $requestArray[
'wpWatchthis'] =
true;
430 $prefName =
'watchdefault-expiry';
431 if ( !$pageObj->exists() ) {
432 $prefName =
'watchcreations-expiry';
436 if ( $watchlistExpiry ) {
437 $requestArray[
'wpWatchlistExpiry'] = $watchlistExpiry;
442 if ( $params[
'tags'] ) {
443 $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params[
'tags'], $this->
getAuthority() );
444 if ( $tagStatus->isOK() ) {
445 $requestArray[
'wpChangeTags'] = implode(
',', $params[
'tags'] );
453 $requestArray += $this->
getRequest()->getValues();
466 $articleContext->setWikiPage( $pageObj );
467 $articleContext->setUser( $this->
getUser() );
470 $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
472 $ep =
new EditPage( $articleObject );
474 $ep->setApiEditOverride(
true );
475 $ep->setContextTitle( $titleObj );
476 $ep->importFormData();
477 $tempUserCreateStatus = $ep->maybeActivateTempUserCreate(
true );
478 if ( !$tempUserCreateStatus->isOK() ) {
479 $this->
dieWithError(
'apierror-tempuseracquirefailed',
'tempuseracquirefailed' );
484 $editRevId = $requestArray[
'editRevId'] ??
false;
485 $baseRev = $this->revisionLookup->getRevisionByTitle( $titleObj, $editRevId );
486 $baseContentModel =
null;
489 $baseContent = $baseRev->getContent( SlotRecord::MAIN );
490 $baseContentModel = $baseContent ? $baseContent->getModel() :
null;
493 $baseContentModel ??= $pageObj->getContentModel();
497 $contentModelsCanDiffer = $params[
'contentmodel'] || isset( $undoContentModel );
499 if ( !$contentModelsCanDiffer && $contentModel !== $baseContentModel ) {
500 $this->
dieWithError( [
'apierror-contentmodel-mismatch', $contentModel, $baseContentModel ] );
504 $oldRevId = $articleObject->getRevIdFetched();
513 $status = $ep->attemptSave( $result );
514 $statusValue = is_int( $status->value ) ? $status->value : 0;
519 switch ( $statusValue ) {
520 case EditPage::AS_HOOK_ERROR:
521 case EditPage::AS_HOOK_ERROR_EXPECTED:
522 if ( $status->statusData !==
null ) {
523 $r = $status->statusData;
524 $r[
'result'] =
'Failure';
528 if ( !$status->getMessages() ) {
531 $status->fatal(
'hookaborted' );
540 case EditPage::AS_BLOCKED_PAGE_FOR_USER:
545 case EditPage::AS_READ_ONLY_PAGE:
549 case EditPage::AS_SUCCESS_NEW_ARTICLE:
553 case EditPage::AS_SUCCESS_UPDATE:
554 $r[
'result'] =
'Success';
555 $r[
'pageid'] = (int)$titleObj->getArticleID();
556 $r[
'title'] = $titleObj->getPrefixedText();
557 $r[
'contentmodel'] = $articleObject->getPage()->getContentModel();
558 $newRevId = $articleObject->getPage()->getLatest();
559 if ( $newRevId == $oldRevId ) {
560 $r[
'nochange'] =
true;
562 $r[
'oldrevid'] = (int)$oldRevId;
563 $r[
'newrevid'] = (int)$newRevId;
565 $pageObj->getTimestamp() );
569 $r[
'watched'] =
true;
572 $this->watchedItemStore,
577 if ( $watchlistExpiry ) {
578 $r[
'watchlistexpiry'] = $watchlistExpiry;
581 $this->persistGlobalSession();
591 $shouldRedirectForTempUser = isset( $result[
'savedTempUser'] ) ||
592 ( $user->isTemp() && ( $user->getEditCount() === 0 ) );
593 if ( $shouldRedirectForTempUser ) {
594 $r[
'tempusercreated'] =
true;
595 $params[
'returnto'] ??= $titleObj->getPrefixedDBkey();
596 $redirectUrl = $this->getTempUserRedirectUrl(
598 $result[
'savedTempUser'] ?? $user
600 if ( $redirectUrl ) {
601 $r[
'tempusercreatedredirect'] = $redirectUrl;
608 if ( !$status->getMessages() ) {
611 switch ( $statusValue ) {
613 case EditPage::AS_IMAGE_REDIRECT_ANON:
614 $status->fatal(
'apierror-noimageredirect-anon' );
616 case EditPage::AS_IMAGE_REDIRECT_LOGGED:
617 $status->fatal(
'apierror-noimageredirect' );
619 case EditPage::AS_READ_ONLY_PAGE_ANON:
620 $status->fatal(
'apierror-noedit-anon' );
622 case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
623 $status->fatal(
'apierror-cantchangecontentmodel' );
625 case EditPage::AS_CONFLICT_DETECTED:
626 $status->fatal(
'edit-conflict' );
633 case EditPage::AS_SPAM_ERROR:
634 $status->fatal(
'apierror-spamdetected', $result[
'spam'] ??
'' );
636 case EditPage::AS_READ_ONLY_PAGE_LOGGED:
637 $status->fatal(
'apierror-noedit' );
639 case EditPage::AS_NO_CREATE_PERMISSION:
640 $status->fatal(
'nocreate-loggedin' );
642 case EditPage::AS_BLANK_ARTICLE:
643 $status->fatal(
'apierror-emptypage' );
645 case EditPage::AS_TEXTBOX_EMPTY:
646 $status->fatal(
'apierror-emptynewsection' );
648 case EditPage::AS_SUMMARY_NEEDED:
649 $status->fatal(
'apierror-summaryrequired' );
652 wfWarn( __METHOD__ .
": Unknown EditPage code $statusValue with no message" );
653 $status->fatal(
'apierror-unknownerror-editpage', $statusValue );
677 ParamValidator::PARAM_TYPE =>
'string',
680 ParamValidator::PARAM_TYPE =>
'integer',
684 ParamValidator::PARAM_TYPE =>
'string',
687 ParamValidator::PARAM_TYPE =>
'text',
691 ParamValidator::PARAM_TYPE =>
'tags',
692 ParamValidator::PARAM_ISMULTI =>
true,
698 ParamValidator::PARAM_TYPE =>
'integer',
701 ParamValidator::PARAM_TYPE =>
'timestamp',
703 'starttimestamp' => [
704 ParamValidator::PARAM_TYPE =>
'timestamp',
707 'createonly' =>
false,
710 ParamValidator::PARAM_DEFAULT =>
false,
711 ParamValidator::PARAM_DEPRECATED =>
true,
714 ParamValidator::PARAM_DEFAULT =>
false,
715 ParamValidator::PARAM_DEPRECATED =>
true,
726 ParamValidator::PARAM_TYPE =>
'text',
729 ParamValidator::PARAM_TYPE =>
'text',
732 ParamValidator::PARAM_TYPE =>
'integer',
733 IntegerDef::PARAM_MIN => 0,
737 ParamValidator::PARAM_TYPE =>
'integer',
738 IntegerDef::PARAM_MIN => 0,
742 ParamValidator::PARAM_TYPE =>
'boolean',
743 ParamValidator::PARAM_DEFAULT =>
false,
746 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
749 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
757 $params += $this->getCreateTempUserParams();