47 private $guessedTitle =
false;
81 parent::__construct( $mainModule, $moduleName );
82 $this->revisionStore = $revisionStore;
83 $this->archivedRevisionLookup = $archivedRevisionLookup;
84 $this->slotRoleRegistry = $slotRoleRegistry;
85 $this->contentHandlerFactory = $contentHandlerFactory;
86 $this->contentTransformer = $contentTransformer;
87 $this->commentFormatter = $commentFormatter;
88 $this->tempUserCreator = $tempUserCreator;
89 $this->userFactory = $userFactory;
98 $params,
'fromtitle',
'fromid',
'fromrev',
'fromtext',
'fromslots'
101 $params,
'totitle',
'toid',
'torev',
'totext',
'torelative',
'toslots'
104 $this->props = array_fill_keys(
$params[
'prop'],
true );
107 $this->
getMain()->setCacheMode(
'public' );
110 [ $fromRev, $fromRelRev, $fromValsRev ] = $this->getDiffRevision(
'from',
$params );
113 if (
$params[
'torelative'] !==
null ) {
114 if ( !$fromRelRev ) {
115 $this->
dieWithError(
'apierror-compare-relative-to-nothing' );
120 $this->
dieWithError( [
'apierror-compare-relative-to-deleted', $params[
'torelative'] ] );
122 switch (
$params[
'torelative'] ) {
125 [ $toRev, $toRelRev, $toValsRev ] = [ $fromRev, $fromRelRev, $fromValsRev ];
126 $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
127 $fromRelRev = $fromRev;
128 $fromValsRev = $fromRev;
130 $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
132 'apiwarn-compare-no-prev',
140 $title ?: $toRev->getPage()
144 $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
145 ->getContentHandler()
152 $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
156 $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
158 'apiwarn-compare-no-next',
165 $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
170 $title = $fromRelRev->getPageAsLinkTarget();
171 $toRev = $this->revisionStore->getRevisionByTitle( $title );
173 $title = Title::newFromLinkTarget( $title );
175 [
'apierror-missingrev-title',
wfEscapeWikiText( $title->getPrefixedText() ) ],
184 [ $toRev, $toRelRev, $toValsRev ] = $this->getDiffRevision(
'to',
$params );
190 if ( !$fromRev || !$toRev ) {
196 if ( !$fromRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
197 $this->
dieWithError( [
'apierror-missingcontent-revid', $fromRev->getId() ],
'missingcontent' );
199 if ( !$toRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
200 $this->
dieWithError( [
'apierror-missingcontent-revid', $toRev->getId() ],
'missingcontent' );
204 $context =
new DerivativeContext( $this->
getContext() );
205 if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
206 $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
208 } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
209 $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
211 $guessedTitle = $this->guessTitle();
212 if ( $guessedTitle ) {
213 $context->setTitle( $guessedTitle );
216 $this->differenceEngine->setContext( $context );
217 $this->differenceEngine->setSlotDiffOptions( [
'diff-type' =>
$params[
'difftype'] ] );
218 $this->differenceEngine->setRevisions( $fromRev, $toRev );
219 if (
$params[
'slots'] ===
null ) {
220 $difftext = $this->differenceEngine->getDiffBody();
221 if ( $difftext ===
false ) {
226 foreach (
$params[
'slots'] as $role ) {
227 $difftext[$role] = $this->differenceEngine->getDiffBodyForRole( $role );
230 foreach ( $this->differenceEngine->getRevisionLoadErrors() as $msg ) {
236 $this->setVals( $vals,
'from', $fromValsRev );
238 $this->setVals( $vals,
'to', $toValsRev );
240 if ( isset( $this->props[
'rel'] ) ) {
242 $rev = $this->revisionStore->getPreviousRevision( $fromRev );
244 $vals[
'prev'] = $rev->getId();
248 $rev = $this->revisionStore->getNextRevision( $toRev );
250 $vals[
'next'] = $rev->getId();
255 if ( isset( $this->props[
'diffsize'] ) ) {
256 $vals[
'diffsize'] = 0;
257 foreach ( (array)$difftext as $text ) {
258 $vals[
'diffsize'] += strlen( $text );
261 if ( isset( $this->props[
'diff'] ) ) {
262 if ( is_array( $difftext ) ) {
263 ApiResult::setArrayType( $difftext,
'kvp',
'diff' );
264 $vals[
'bodies'] = $difftext;
266 ApiResult::setContentValue( $vals,
'body', $difftext );
284 private function getRevisionById( $id ) {
285 $rev = $this->revisionStore->getRevisionById( $id );
286 if ( !$rev && $this->
getAuthority()->isAllowedAny(
'deletedtext',
'undelete' ) ) {
288 $rev = $this->archivedRevisionLookup->getArchivedRevisionRecord(
null, $id );
298 private function guessTitle() {
299 if ( $this->guessedTitle !==
false ) {
300 return $this->guessedTitle;
303 $this->guessedTitle =
null;
306 foreach ( [
'from',
'to' ] as $prefix ) {
307 if (
$params[
"{$prefix}rev"] !==
null ) {
308 $rev = $this->getRevisionById(
$params[
"{$prefix}rev"] );
310 $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
315 if (
$params[
"{$prefix}title"] !==
null ) {
316 $title = Title::newFromText(
$params[
"{$prefix}title"] );
317 if ( $title && !$title->isExternal() ) {
318 $this->guessedTitle = $title;
323 if (
$params[
"{$prefix}id"] !==
null ) {
324 $title = Title::newFromID(
$params[
"{$prefix}id"] );
326 $this->guessedTitle = $title;
332 return $this->guessedTitle;
340 private function guessModel( $role ) {
343 foreach ( [
'from',
'to' ] as $prefix ) {
344 if (
$params[
"{$prefix}rev"] !==
null ) {
345 $rev = $this->getRevisionById(
$params[
"{$prefix}rev"] );
346 if ( $rev && $rev->hasSlot( $role ) ) {
347 return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
352 $guessedTitle = $this->guessTitle();
353 if ( $guessedTitle ) {
354 return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
357 if ( isset(
$params[
"fromcontentmodel-$role"] ) ) {
358 return $params[
"fromcontentmodel-$role"];
360 if ( isset(
$params[
"tocontentmodel-$role"] ) ) {
361 return $params[
"tocontentmodel-$role"];
364 if ( $role === SlotRecord::MAIN ) {
365 if ( isset(
$params[
'fromcontentmodel'] ) ) {
366 return $params[
'fromcontentmodel'];
368 if ( isset(
$params[
'tocontentmodel'] ) ) {
369 return $params[
'tocontentmodel'];
391 private function getDiffRevision( $prefix, array
$params ) {
395 if (
$params[
"{$prefix}text"] !==
null ) {
396 $params[
"{$prefix}slots"] = [ SlotRecord::MAIN ];
398 $params[
"{$prefix}section-main"] =
null;
399 $params[
"{$prefix}contentmodel-main"] =
$params[
"{$prefix}contentmodel"];
400 $params[
"{$prefix}contentformat-main"] =
$params[
"{$prefix}contentformat"];
405 $suppliedContent =
$params[
"{$prefix}slots"] !==
null;
409 if (
$params[
"{$prefix}rev"] !==
null ) {
410 $revId =
$params[
"{$prefix}rev"];
411 } elseif (
$params[
"{$prefix}title"] !==
null ||
$params[
"{$prefix}id"] !==
null ) {
412 if (
$params[
"{$prefix}title"] !==
null ) {
413 $title = Title::newFromText(
$params[
"{$prefix}title"] );
414 if ( !$title || $title->isExternal() ) {
420 $title = Title::newFromID(
$params[
"{$prefix}id"] );
422 $this->
dieWithError( [
'apierror-nosuchpageid', $params[
"{$prefix}id"] ] );
425 $revId = $title->getLatestRevID();
429 if ( !$suppliedContent ) {
430 if ( $title->exists() ) {
432 [
'apierror-missingrev-title',
wfEscapeWikiText( $title->getPrefixedText() ) ],
437 [
'apierror-missingtitle-byname',
wfEscapeWikiText( $title->getPrefixedText() ) ],
444 if ( $revId !==
null ) {
445 $rev = $this->getRevisionById( $revId );
447 $this->
dieWithError( [
'apierror-nosuchrevid', $revId ] );
449 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
453 if ( !$suppliedContent ) {
457 if ( isset(
$params[
"{$prefix}section"] ) ) {
458 $section =
$params[
"{$prefix}section"];
460 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
461 $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
465 [
'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ],
'missingcontent'
468 $content = $content->getSection( $section );
471 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
472 "nosuch{$prefix}section"
476 $newRev->setContent( SlotRecord::MAIN, $content );
479 return [ $newRev, $rev, $rev ];
485 $title = $this->guessTitle();
488 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
492 foreach (
$params[
"{$prefix}slots"] as $role ) {
493 $text =
$params[
"{$prefix}text-{$role}"];
494 if ( $text ===
null ) {
496 if ( $role === SlotRecord::MAIN ) {
497 $this->
dieWithError( [
'apierror-compare-maintextrequired', $prefix ] );
502 foreach ( [
'section',
'contentmodel',
'contentformat' ] as $param ) {
503 if ( isset(
$params[
"{$prefix}{$param}-{$role}"] ) ) {
505 'apierror-compare-notext',
512 $newRev->removeSlot( $role );
516 $model =
$params[
"{$prefix}contentmodel-{$role}"];
517 $format =
$params[
"{$prefix}contentformat-{$role}"];
519 if ( !$model && $rev && $rev->hasSlot( $role ) ) {
520 $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
522 if ( !$model && $title && $role === SlotRecord::MAIN ) {
524 $model = $title->getContentModel();
527 $model = $this->guessModel( $role );
531 $this->
addWarning( [
'apiwarn-compare-nocontentmodel', $model ] );
535 $content = $this->contentHandlerFactory
536 ->getContentHandler( $model )
537 ->unserializeContent( $text, $format );
540 'wrap' => ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
544 if (
$params[
"{$prefix}pst"] ) {
549 $content = $this->contentTransformer->preSaveTransform(
553 $this->getUserForPreview(),
558 $section =
$params[
"{$prefix}section-{$role}"];
559 if ( $section !==
null && $section !==
'' ) {
561 $this->
dieWithError(
"apierror-compare-no{$prefix}revision" );
563 $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
564 if ( !$oldContent ) {
566 [
'apierror-missingcontent-revid-role', $rev->getId(),
wfEscapeWikiText( $role ) ],
570 if ( !$oldContent->getContentHandler()->supportsSections() ) {
571 $this->
dieWithError( [
'apierror-sectionsnotsupported', $content->getModel() ] );
575 $content = $oldContent->replaceSection( $section, $content,
'' );
576 }
catch ( TimeoutException $e ) {
578 }
catch ( Exception $ex ) {
583 $this->
dieWithError( [
'apierror-sectionreplacefailed' ] );
588 if ( $role === SlotRecord::MAIN && isset(
$params[
"{$prefix}section"] ) ) {
589 $section =
$params[
"{$prefix}section"];
590 $content = $content->getSection( $section );
593 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
594 "nosuch{$prefix}section"
600 $newRev->setContent( $role, $content );
602 return [ $newRev, $rev, null ];
612 private function setVals( &$vals, $prefix, $rev ) {
614 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
615 if ( isset( $this->props[
'ids'] ) ) {
616 $vals[
"{$prefix}id"] = $title->getArticleID();
617 $vals[
"{$prefix}revid"] = $rev->getId();
619 if ( isset( $this->props[
'title'] ) ) {
622 if ( isset( $this->props[
'size'] ) ) {
623 $vals[
"{$prefix}size"] = $rev->getSize();
625 if ( isset( $this->props[
'timestamp'] ) ) {
626 $revTimestamp = $rev->getTimestamp();
627 if ( $revTimestamp ) {
628 $vals[
"{$prefix}timestamp"] =
wfTimestamp( TS_ISO_8601, $revTimestamp );
633 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
634 $vals[
"{$prefix}texthidden"] =
true;
638 if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
639 $vals[
"{$prefix}userhidden"] =
true;
642 if ( isset( $this->props[
'user'] ) ) {
643 $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
645 $vals[
"{$prefix}user"] = $user->getName();
646 $vals[
"{$prefix}userid"] = $user->getId();
650 if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
651 $vals[
"{$prefix}commenthidden"] =
true;
654 if ( isset( $this->props[
'comment'] ) || isset( $this->props[
'parsedcomment'] ) ) {
655 $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->
getAuthority() );
656 if ( $comment !==
null ) {
657 if ( isset( $this->props[
'comment'] ) ) {
658 $vals[
"{$prefix}comment"] = $comment->text;
660 $vals[
"{$prefix}parsedcomment"] = $this->commentFormatter->format(
661 $comment->text, $title
667 $this->
getMain()->setCacheMode(
'private' );
668 if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
669 $vals[
"{$prefix}suppressed"] =
true;
674 $this->
getMain()->setCacheMode(
'private' );
675 $vals[
"{$prefix}archive"] =
true;
680 private function getUserForPreview() {
682 if ( $this->tempUserCreator->shouldAutoCreate( $user,
'edit' ) ) {
683 return $this->userFactory->newUnsavedTempUser(
684 $this->tempUserCreator->getStashedName( $this->getRequest()->getSession() )
691 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
692 sort( $slotRoles, SORT_STRING );
698 ParamValidator::PARAM_TYPE =>
'integer'
701 ParamValidator::PARAM_TYPE =>
'integer'
705 ParamValidator::PARAM_TYPE => $slotRoles,
706 ParamValidator::PARAM_ISMULTI =>
true,
710 ParamValidator::PARAM_TYPE =>
'text',
712 'section-{slot}' => [
714 ParamValidator::PARAM_TYPE =>
'string',
716 'contentformat-{slot}' => [
718 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
720 'contentmodel-{slot}' => [
722 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
727 ParamValidator::PARAM_TYPE =>
'text',
728 ParamValidator::PARAM_DEPRECATED =>
true,
731 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
732 ParamValidator::PARAM_DEPRECATED =>
true,
735 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
736 ParamValidator::PARAM_DEPRECATED =>
true,
739 ParamValidator::PARAM_DEFAULT =>
null,
740 ParamValidator::PARAM_DEPRECATED =>
true,
745 foreach ( $fromToParams as $k => $v ) {
751 foreach ( $fromToParams as $k => $v ) {
760 [
'torelative' => [ ParamValidator::PARAM_TYPE => [
'prev',
'next',
'cur' ], ] ],
765 ParamValidator::PARAM_DEFAULT =>
'diff|ids|title',
766 ParamValidator::PARAM_TYPE => [
778 ParamValidator::PARAM_ISMULTI =>
true,
783 ParamValidator::PARAM_TYPE => $slotRoles,
784 ParamValidator::PARAM_ISMULTI =>
true,
785 ParamValidator::PARAM_ALL =>
true,
789 ParamValidator::PARAM_TYPE => $this->differenceEngine->getSupportedFormats(),
790 ParamValidator::PARAM_DEFAULT =>
'table',
798 'action=compare&fromrev=1&torev=2'
799 =>
'apihelp-compare-example-1',
804 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Compare';
getHelpUrls()
Return links to more detailed help pages about the module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
__construct(ApiMain $mainModule, $moduleName, RevisionStore $revisionStore, ArchivedRevisionLookup $archivedRevisionLookup, SlotRoleRegistry $slotRoleRegistry, IContentHandlerFactory $contentHandlerFactory, ContentTransformer $contentTransformer, CommentFormatter $commentFormatter, TempUserCreator $tempUserCreator, UserFactory $userFactory)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
This is the main API class, used for both external and internal processing.