3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
23 namespace MediaWiki\Api
;
25 use MediaWiki\CommentFormatter\CommentFormatter
;
26 use MediaWiki\Content\Content
;
27 use MediaWiki\Content\IContentHandlerFactory
;
28 use MediaWiki\Content\Renderer\ContentRenderer
;
29 use MediaWiki\Content\Transform\ContentTransformer
;
30 use MediaWiki\Content\WikitextContent
;
31 use MediaWiki\Context\DerivativeContext
;
32 use MediaWiki\Logger\LoggerFactory
;
33 use MediaWiki\MainConfigNames
;
34 use MediaWiki\MediaWikiServices
;
35 use MediaWiki\Parser\Parser
;
36 use MediaWiki\Parser\ParserFactory
;
37 use MediaWiki\Parser\ParserOptions
;
38 use MediaWiki\Revision\RevisionAccessException
;
39 use MediaWiki\Revision\RevisionRecord
;
40 use MediaWiki\Revision\RevisionStore
;
41 use MediaWiki\Revision\SlotRecord
;
42 use MediaWiki\Revision\SlotRoleRegistry
;
43 use MediaWiki\Title\Title
;
44 use MediaWiki\User\TempUser\TempUserCreator
;
45 use MediaWiki\User\UserFactory
;
46 use MediaWiki\User\UserNameUtils
;
48 use Wikimedia\ParamValidator\ParamValidator
;
49 use Wikimedia\ParamValidator\TypeDef\EnumDef
;
50 use Wikimedia\ParamValidator\TypeDef\IntegerDef
;
53 * A base class for functions common to producing a list of revisions.
59 abstract class ApiQueryRevisionsBase
extends ApiQueryGeneratorBase
{
61 // region Constants for internal use. Don't use externally.
62 /** @name Constants for internal use. Don't use externally. */
64 // Bits to indicate the results of the revdel permission check on a revision,
65 // see self::checkRevDel()
66 private const IS_DELETED
= 1; // Whether the field is revision-deleted
67 private const CANNOT_VIEW
= 2; // Whether the user cannot view the field due to revdel
69 private const LIMIT_PARSE
= 1;
73 /** @var int|string|null */
75 /** @var int|string|null */
77 /** @var string|null */
78 protected $difftotext;
80 protected $difftotextpst;
81 /** @var int|string|null */
82 protected $expandTemplates;
84 protected $generateXML;
85 /** @var int|string|null */
88 protected $parseContent;
90 protected $fetchContent;
92 protected $contentFormat;
93 protected bool $setParsedLimit = true;
94 protected ?
array $slotRoles = null;
96 protected $slotContentFormats;
100 protected bool $fld_ids = false;
101 protected bool $fld_flags = false;
102 protected bool $fld_timestamp = false;
103 protected bool $fld_size = false;
104 protected bool $fld_slotsize = false;
105 protected bool $fld_sha1 = false;
106 protected bool $fld_slotsha1 = false;
107 protected bool $fld_comment = false;
108 protected bool $fld_parsedcomment = false;
109 protected bool $fld_user = false;
110 protected bool $fld_userid = false;
111 protected bool $fld_content = false;
112 protected bool $fld_tags = false;
113 protected bool $fld_contentmodel = false;
114 protected bool $fld_roles = false;
115 protected bool $fld_parsetree = false;
118 * The number of uncached diffs that had to be generated for this request.
121 private $numUncachedDiffs = 0;
123 private RevisionStore
$revisionStore;
124 private IContentHandlerFactory
$contentHandlerFactory;
125 private ParserFactory
$parserFactory;
126 private SlotRoleRegistry
$slotRoleRegistry;
127 private ContentRenderer
$contentRenderer;
128 private ContentTransformer
$contentTransformer;
129 private CommentFormatter
$commentFormatter;
130 private TempUserCreator
$tempUserCreator;
131 private UserFactory
$userFactory;
132 private UserNameUtils
$userNameUtils;
135 * @since 1.37 Support injection of services
138 public function __construct(
139 ApiQuery
$queryModule,
141 string $paramPrefix = '',
142 ?RevisionStore
$revisionStore = null,
143 ?IContentHandlerFactory
$contentHandlerFactory = null,
144 ?ParserFactory
$parserFactory = null,
145 ?SlotRoleRegistry
$slotRoleRegistry = null,
146 ?ContentRenderer
$contentRenderer = null,
147 ?ContentTransformer
$contentTransformer = null,
148 ?CommentFormatter
$commentFormatter = null,
149 ?TempUserCreator
$tempUserCreator = null,
150 ?UserFactory
$userFactory = null,
151 ?UserNameUtils
$userNameUtils = null
153 parent
::__construct( $queryModule, $moduleName, $paramPrefix );
154 // This class is part of the stable interface and
155 // therefor fallback to global state, if services are not provided
156 $services = MediaWikiServices
::getInstance();
157 $this->revisionStore
= $revisionStore ??
$services->getRevisionStore();
158 $this->contentHandlerFactory
= $contentHandlerFactory ??
$services->getContentHandlerFactory();
159 $this->parserFactory
= $parserFactory ??
$services->getParserFactory();
160 $this->slotRoleRegistry
= $slotRoleRegistry ??
$services->getSlotRoleRegistry();
161 $this->contentRenderer
= $contentRenderer ??
$services->getContentRenderer();
162 $this->contentTransformer
= $contentTransformer ??
$services->getContentTransformer();
163 $this->commentFormatter
= $commentFormatter ??
$services->getCommentFormatter();
164 $this->tempUserCreator
= $tempUserCreator ??
$services->getTempUserCreator();
165 $this->userFactory
= $userFactory ??
$services->getUserFactory();
166 $this->userNameUtils
= $userNameUtils ??
$services->getUserNameUtils();
169 public function execute() {
173 public function executeGenerator( $resultPageSet ) {
174 $this->run( $resultPageSet );
178 * @param ApiPageSet|null $resultPageSet
181 abstract protected function run( ?ApiPageSet
$resultPageSet = null );
184 * Parse the parameters into the various instance fields.
186 * @param array $params
188 protected function parseParameters( $params ) {
189 $prop = array_fill_keys( $params['prop'], true );
191 $this->fld_ids
= isset( $prop['ids'] );
192 $this->fld_flags
= isset( $prop['flags'] );
193 $this->fld_timestamp
= isset( $prop['timestamp'] );
194 $this->fld_comment
= isset( $prop['comment'] );
195 $this->fld_parsedcomment
= isset( $prop['parsedcomment'] );
196 $this->fld_size
= isset( $prop['size'] );
197 $this->fld_slotsize
= isset( $prop['slotsize'] );
198 $this->fld_sha1
= isset( $prop['sha1'] );
199 $this->fld_slotsha1
= isset( $prop['slotsha1'] );
200 $this->fld_content
= isset( $prop['content'] );
201 $this->fld_contentmodel
= isset( $prop['contentmodel'] );
202 $this->fld_userid
= isset( $prop['userid'] );
203 $this->fld_user
= isset( $prop['user'] );
204 $this->fld_tags
= isset( $prop['tags'] );
205 $this->fld_roles
= isset( $prop['roles'] );
206 $this->fld_parsetree
= isset( $prop['parsetree'] );
208 $this->slotRoles
= $params['slots'];
210 if ( $this->slotRoles
!== null ) {
211 if ( $this->fld_parsetree
) {
212 $this->dieWithError( [
213 'apierror-invalidparammix-cannotusewith',
214 $this->encodeParamName( 'prop=parsetree' ),
215 $this->encodeParamName( 'slots' ),
216 ], 'invalidparammix' );
219 'expandtemplates', 'generatexml', 'parse', 'diffto', 'difftotext', 'difftotextpst',
222 if ( $params[$p] !== null && $params[$p] !== false ) {
223 $this->dieWithError( [
224 'apierror-invalidparammix-cannotusewith',
225 $this->encodeParamName( $p ),
226 $this->encodeParamName( 'slots' ),
227 ], 'invalidparammix' );
230 $this->slotContentFormats
= [];
231 foreach ( $this->slotRoles
as $slotRole ) {
232 if ( isset( $params['contentformat-' . $slotRole] ) ) {
233 $this->slotContentFormats
[$slotRole] = $params['contentformat-' . $slotRole];
238 if ( !empty( $params['contentformat'] ) ) {
239 $this->contentFormat
= $params['contentformat'];
242 $this->limit
= $params['limit'];
244 if ( $params['difftotext'] !== null ) {
245 $this->difftotext
= $params['difftotext'];
246 $this->difftotextpst
= $params['difftotextpst'];
247 } elseif ( $params['diffto'] !== null ) {
248 if ( $params['diffto'] == 'cur' ) {
249 $params['diffto'] = 0;
251 if ( ( !ctype_digit( $params['diffto'] ) ||
$params['diffto'] < 0 )
252 && $params['diffto'] != 'prev' && $params['diffto'] != 'next'
254 $p = $this->getModulePrefix();
255 $this->dieWithError( [ 'apierror-baddiffto', $p ], 'diffto' );
257 // Check whether the revision exists and is readable,
258 // DifferenceEngine returns a rather ambiguous empty
259 // string if that's not the case
260 if ( is_numeric( $params['diffto'] ) && $params['diffto'] != 0 ) {
261 $difftoRev = $this->revisionStore
->getRevisionById( $params['diffto'] );
263 $this->dieWithError( [ 'apierror-nosuchrevid', $params['diffto'] ] );
265 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable T240141
266 $revDel = $this->checkRevDel( $difftoRev, RevisionRecord
::DELETED_TEXT
);
267 if ( $revDel & self
::CANNOT_VIEW
) {
268 $this->addWarning( [ 'apiwarn-difftohidden', $difftoRev->getId() ] );
269 $params['diffto'] = null;
272 $this->diffto
= $params['diffto'];
275 $this->fetchContent
= $this->fld_content ||
$this->diffto
!== null
276 ||
$this->difftotext
!== null ||
$this->fld_parsetree
;
279 if ( $this->fetchContent
) {
281 $this->expandTemplates
= $params['expandtemplates'];
282 $this->generateXML
= $params['generatexml'];
283 $this->parseContent
= $params['parse'];
284 if ( $this->parseContent
) {
285 // Must manually initialize unset limit
286 $this->limit ??
= self
::LIMIT_PARSE
;
288 $this->section
= $params['section'] ??
false;
291 $userMax = $this->parseContent ? self
::LIMIT_PARSE
:
292 ( $smallLimit ? ApiBase
::LIMIT_SML1
: ApiBase
::LIMIT_BIG1
);
293 $botMax = $this->parseContent ? self
::LIMIT_PARSE
:
294 ( $smallLimit ? ApiBase
::LIMIT_SML2
: ApiBase
::LIMIT_BIG2
);
295 if ( $this->limit
== 'max' ) {
296 $this->limit
= $this->getMain()->canApiHighLimits() ?
$botMax : $userMax;
297 if ( $this->setParsedLimit
) {
298 $this->getResult()->addParsedLimit( $this->getModuleName(), $this->limit
);
302 $this->limit
= $this->getMain()->getParamValidator()->validateValue(
303 $this, 'limit', $this->limit ??
10, [
304 ParamValidator
::PARAM_TYPE
=> 'limit',
305 IntegerDef
::PARAM_MIN
=> 1,
306 IntegerDef
::PARAM_MAX
=> $userMax,
307 IntegerDef
::PARAM_MAX2
=> $botMax,
308 IntegerDef
::PARAM_IGNORE_RANGE
=> true,
312 $this->needSlots
= $this->fetchContent ||
$this->fld_contentmodel ||
313 $this->fld_slotsize ||
$this->fld_slotsha1
;
314 if ( $this->needSlots
&& $this->slotRoles
=== null ) {
315 $encParam = $this->encodeParamName( 'slots' );
316 $name = $this->getModuleName();
317 $parent = $this->getParent();
318 $parentParam = $parent->encodeParamName( $parent->getModuleManager()->getModuleGroup( $name ) );
319 $this->addDeprecation(
320 [ 'apiwarn-deprecation-missingparam', $encParam ],
321 "action=query&{$parentParam}={$name}&!{$encParam}"
327 * Test revision deletion status
328 * @param RevisionRecord $revision Revision to check
329 * @param int $field One of the RevisionRecord::DELETED_* constants
330 * @return int Revision deletion status flags. Bitwise OR of
331 * self::IS_DELETED and self::CANNOT_VIEW, as appropriate.
333 private function checkRevDel( RevisionRecord
$revision, $field ) {
334 $ret = $revision->isDeleted( $field ) ? self
::IS_DELETED
: 0;
336 $canSee = $revision->userCan( $field, $this->getAuthority() );
337 $ret |
= ( $canSee ?
0 : self
::CANNOT_VIEW
);
343 * Extract information from the RevisionRecord
345 * @since 1.32, takes a RevisionRecord instead of a Revision
346 * @param RevisionRecord $revision
347 * @param stdClass $row Should have a field 'ts_tags' if $this->fld_tags is set
350 protected function extractRevisionInfo( RevisionRecord
$revision, $row ) {
354 if ( $this->fld_ids
) {
355 $vals['revid'] = (int)$revision->getId();
356 if ( $revision->getParentId() !== null ) {
357 $vals['parentid'] = (int)$revision->getParentId();
361 if ( $this->fld_flags
) {
362 $vals['minor'] = $revision->isMinor();
365 if ( $this->fld_user ||
$this->fld_userid
) {
366 $revDel = $this->checkRevDel( $revision, RevisionRecord
::DELETED_USER
);
367 if ( $revDel & self
::IS_DELETED
) {
368 $vals['userhidden'] = true;
371 if ( !( $revDel & self
::CANNOT_VIEW
) ) {
372 $u = $revision->getUser( RevisionRecord
::RAW
);
373 if ( $this->fld_user
) {
374 $vals['user'] = $u->getName();
376 if ( $this->userNameUtils
->isTemp( $u->getName() ) ) {
377 $vals['temp'] = true;
379 if ( !$u->isRegistered() ) {
380 $vals['anon'] = true;
383 if ( $this->fld_userid
) {
384 $vals['userid'] = $u->getId();
389 if ( $this->fld_timestamp
) {
390 $vals['timestamp'] = wfTimestamp( TS_ISO_8601
, $revision->getTimestamp() );
393 if ( $this->fld_size
) {
395 $vals['size'] = (int)$revision->getSize();
396 } catch ( RevisionAccessException
$e ) {
397 // Back compat: If there's no size, return 0.
398 // @todo: Gergő says to mention T198099 as a "todo" here.
403 if ( $this->fld_sha1
) {
404 $revDel = $this->checkRevDel( $revision, RevisionRecord
::DELETED_TEXT
);
405 if ( $revDel & self
::IS_DELETED
) {
406 $vals['sha1hidden'] = true;
409 if ( !( $revDel & self
::CANNOT_VIEW
) ) {
411 $vals['sha1'] = \Wikimedia\base_convert
( $revision->getSha1(), 36, 16, 40 );
412 } catch ( RevisionAccessException
$e ) {
413 // Back compat: If there's no sha1, return empty string.
414 // @todo: Gergő says to mention T198099 as a "todo" here.
421 if ( $this->fld_roles
) {
422 $vals['roles'] = $revision->getSlotRoles();
425 if ( $this->needSlots
) {
426 $revDel = $this->checkRevDel( $revision, RevisionRecord
::DELETED_TEXT
);
427 if ( ( $this->fld_slotsha1 ||
$this->fetchContent
) && ( $revDel & self
::IS_DELETED
) ) {
430 $vals = array_merge( $vals, $this->extractAllSlotInfo( $revision, $revDel ) );
432 } catch ( RevisionAccessException
$ex ) {
433 // This is here so T212428 doesn't spam the log.
434 // TODO: find out why T212428 happens in the first place!
435 $vals['slotsmissing'] = true;
437 LoggerFactory
::getInstance( 'api-warning' )->error(
438 'Failed to access revision slots',
439 [ 'revision' => $revision->getId(), 'exception' => $ex, ]
443 if ( $this->fld_comment ||
$this->fld_parsedcomment
) {
444 $revDel = $this->checkRevDel( $revision, RevisionRecord
::DELETED_COMMENT
);
445 if ( $revDel & self
::IS_DELETED
) {
446 $vals['commenthidden'] = true;
449 if ( !( $revDel & self
::CANNOT_VIEW
) ) {
450 $comment = $revision->getComment( RevisionRecord
::RAW
);
451 $comment = $comment->text ??
'';
453 if ( $this->fld_comment
) {
454 $vals['comment'] = $comment;
457 if ( $this->fld_parsedcomment
) {
458 $vals['parsedcomment'] = $this->commentFormatter
->format(
459 $comment, Title
::newFromLinkTarget( $revision->getPageAsLinkTarget() )
465 if ( $this->fld_tags
) {
466 if ( $row->ts_tags
) {
467 $tags = explode( ',', $row->ts_tags
);
468 ApiResult
::setIndexedTagName( $tags, 'tag' );
469 $vals['tags'] = $tags;
475 if ( $anyHidden && $revision->isDeleted( RevisionRecord
::DELETED_RESTRICTED
) ) {
476 $vals['suppressed'] = true;
483 * Extracts information about all relevant slots.
485 * @param RevisionRecord $revision
489 * @throws ApiUsageException
491 private function extractAllSlotInfo( RevisionRecord
$revision, $revDel ): array {
494 if ( $this->slotRoles
=== null ) {
496 $slot = $revision->getSlot( SlotRecord
::MAIN
, RevisionRecord
::RAW
);
497 } catch ( RevisionAccessException
$e ) {
498 // Back compat: If there's no slot, there's no content, so set 'textmissing'
499 // @todo: Gergő says to mention T198099 as a "todo" here.
500 $vals['textmissing'] = true;
506 $vals +
= $this->extractSlotInfo( $slot, $revDel, $content );
507 if ( !empty( $vals['nosuchsection'] ) ) {
510 'apierror-nosuchsection-what',
511 wfEscapeWikiText( $this->section
),
512 $this->msg( 'revid', $revision->getId() )
518 $vals +
= $this->extractDeprecatedContent( $content, $revision );
522 $roles = array_intersect( $this->slotRoles
, $revision->getSlotRoles() );
524 ApiResult
::META_KVP_MERGE
=> true,
526 foreach ( $roles as $role ) {
528 $slot = $revision->getSlot( $role, RevisionRecord
::RAW
);
529 } catch ( RevisionAccessException
$e ) {
530 // Don't error out here so the client can still process other slots/revisions.
531 // @todo: Gergő says to mention T198099 as a "todo" here.
532 $vals['slots'][$role]['missing'] = true;
536 $vals['slots'][$role] = $this->extractSlotInfo( $slot, $revDel, $content );
537 // @todo Move this into extractSlotInfo() (and remove its $content parameter)
538 // when extractDeprecatedContent() is no more.
540 /** @var Content $content */
541 $model = $content->getModel();
542 $format = $this->slotContentFormats
[$role] ??
$content->getDefaultFormat();
543 if ( !$content->isSupportedFormat( $format ) ) {
545 'apierror-badformat',
548 $this->msg( 'revid', $revision->getId() )
550 $vals['slots'][$role]['badcontentformat'] = true;
552 $vals['slots'][$role]['contentmodel'] = $model;
553 $vals['slots'][$role]['contentformat'] = $format;
554 ApiResult
::setContentValue(
555 $vals['slots'][$role],
557 $content->serialize( $format )
562 ApiResult
::setArrayType( $vals['slots'], 'kvp', 'role' );
563 ApiResult
::setIndexedTagName( $vals['slots'], 'slot' );
569 * Extract information from the SlotRecord
571 * @param SlotRecord $slot
572 * @param int $revDel Revdel status flags, from self::checkRevDel()
573 * @param Content|null &$content Set to the slot's content, if available
574 * and $this->fetchContent is true
577 private function extractSlotInfo( SlotRecord
$slot, $revDel, &$content = null ) {
579 ApiResult
::setArrayType( $vals, 'assoc' );
581 if ( $this->fld_slotsize
) {
582 $vals['size'] = (int)$slot->getSize();
585 if ( $this->fld_slotsha1
) {
586 if ( $revDel & self
::IS_DELETED
) {
587 $vals['sha1hidden'] = true;
589 if ( !( $revDel & self
::CANNOT_VIEW
) ) {
590 if ( $slot->getSha1() != '' ) {
591 $vals['sha1'] = \Wikimedia\base_convert
( $slot->getSha1(), 36, 16, 40 );
598 if ( $this->fld_contentmodel
) {
599 $vals['contentmodel'] = $slot->getModel();
603 if ( $this->fetchContent
) {
604 if ( $revDel & self
::IS_DELETED
) {
605 $vals['texthidden'] = true;
607 if ( !( $revDel & self
::CANNOT_VIEW
) ) {
609 $content = $slot->getContent();
610 } catch ( RevisionAccessException
$e ) {
611 // @todo: Gergő says to mention T198099 as a "todo" here.
612 $vals['textmissing'] = true;
614 // Expand templates after getting section content because
615 // template-added sections don't count and Parser::preprocess()
616 // will have less input
617 if ( $content && $this->section
!== false ) {
618 $content = $content->getSection( $this->section
);
620 $vals['nosuchsection'] = true;
630 * Format a Content using deprecated options
631 * @param Content $content Content to format
632 * @param RevisionRecord $revision Revision being processed
635 private function extractDeprecatedContent( Content
$content, RevisionRecord
$revision ) {
637 $title = Title
::newFromLinkTarget( $revision->getPageAsLinkTarget() );
639 if ( $this->fld_parsetree ||
( $this->fld_content
&& $this->generateXML
) ) {
640 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT
) {
641 /** @var WikitextContent $content */
642 '@phan-var WikitextContent $content';
643 $t = $content->getText(); # note: don't set $text
645 $parser = $this->parserFactory
->create();
646 $parser->startExternalParse(
648 ParserOptions
::newFromContext( $this->getContext() ),
649 Parser
::OT_PREPROCESS
651 $dom = $parser->preprocessToDom( $t );
652 // @phan-suppress-next-line PhanUndeclaredMethodInCallable
653 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
654 // @phan-suppress-next-line PhanUndeclaredMethod
655 $xml = $dom->saveXML();
657 // @phan-suppress-next-line PhanUndeclaredMethod
658 $xml = $dom->__toString();
660 $vals['parsetree'] = $xml;
662 $vals['badcontentformatforparsetree'] = true;
665 'apierror-parsetree-notwikitext-title',
666 wfEscapeWikiText( $title->getPrefixedText() ),
669 'parsetree-notwikitext'
674 if ( $this->fld_content
) {
677 if ( $this->expandTemplates
&& !$this->parseContent
) {
678 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT
) {
679 /** @var WikitextContent $content */
680 '@phan-var WikitextContent $content';
681 $text = $content->getText();
683 $text = $this->parserFactory
->create()->preprocess(
686 ParserOptions
::newFromContext( $this->getContext() )
690 'apierror-templateexpansion-notwikitext',
691 wfEscapeWikiText( $title->getPrefixedText() ),
694 $vals['badcontentformat'] = true;
698 if ( $this->parseContent
) {
699 $popts = ParserOptions
::newFromContext( $this->getContext() );
700 $po = $this->contentRenderer
->getParserOutput(
706 // TODO T371004 move runOutputPipeline out of $parserOutput
707 $text = $po->runOutputPipeline( $popts, [] )->getContentHolderText();
710 if ( $text === null ) {
711 $format = $this->contentFormat ?
: $content->getDefaultFormat();
712 $model = $content->getModel();
714 if ( !$content->isSupportedFormat( $format ) ) {
715 $name = wfEscapeWikiText( $title->getPrefixedText() );
716 $this->addWarning( [ 'apierror-badformat', $this->contentFormat
, $model, $name ] );
717 $vals['badcontentformat'] = true;
720 $text = $content->serialize( $format );
721 // always include format and model.
722 // Format is needed to deserialize, model is needed to interpret.
723 $vals['contentformat'] = $format;
724 $vals['contentmodel'] = $model;
728 if ( $text !== false ) {
729 ApiResult
::setContentValue( $vals, 'content', $text );
733 if ( $content && ( $this->diffto
!== null ||
$this->difftotext
!== null ) ) {
734 if ( $this->numUncachedDiffs
< $this->getConfig()->get( MainConfigNames
::APIMaxUncachedDiffs
) ) {
736 $context = new DerivativeContext( $this->getContext() );
737 $context->setTitle( $title );
738 $handler = $content->getContentHandler();
740 if ( $this->difftotext
!== null ) {
741 $model = $title->getContentModel();
743 if ( $this->contentFormat
744 && !$this->contentHandlerFactory
->getContentHandler( $model )
745 ->isSupportedFormat( $this->contentFormat
)
747 $name = wfEscapeWikiText( $title->getPrefixedText() );
748 $this->addWarning( [ 'apierror-badformat', $this->contentFormat
, $model, $name ] );
749 $vals['diff']['badcontentformat'] = true;
752 $difftocontent = $this->contentHandlerFactory
->getContentHandler( $model )
753 ->unserializeContent( $this->difftotext
, $this->contentFormat
);
755 if ( $this->difftotextpst
) {
756 $popts = ParserOptions
::newFromContext( $this->getContext() );
757 $difftocontent = $this->contentTransformer
->preSaveTransform(
760 $this->getUserForPreview(),
765 $engine = $handler->createDifferenceEngine( $context );
766 $engine->setContent( $content, $difftocontent );
769 $engine = $handler->createDifferenceEngine( $context, $revision->getId(), $this->diffto
);
770 $vals['diff']['from'] = $engine->getOldid();
771 $vals['diff']['to'] = $engine->getNewid();
774 $difftext = $engine->getDiffBody();
775 ApiResult
::setContentValue( $vals['diff'], 'body', $difftext );
776 if ( !$engine->wasCacheHit() ) {
777 $this->numUncachedDiffs++
;
779 foreach ( $engine->getRevisionLoadErrors() as $msg ) {
780 $this->addWarning( $msg );
784 $vals['diff']['notcached'] = true;
791 private function getUserForPreview() {
792 $user = $this->getUser();
793 if ( $this->tempUserCreator
->shouldAutoCreate( $user, 'edit' ) ) {
794 return $this->userFactory
->newUnsavedTempUser(
795 $this->tempUserCreator
->getStashedName( $this->getRequest()->getSession() )
802 * @stable to override
803 * @param array $params
807 public function getCacheMode( $params ) {
808 if ( $this->userCanSeeRevDel() ) {
816 * @stable to override
819 public function getAllowedParams() {
820 $slotRoles = $this->slotRoleRegistry
->getKnownRoles();
821 sort( $slotRoles, SORT_STRING
);
822 $smallLimit = $this->getMain()->canApiHighLimits() ? ApiBase
::LIMIT_SML2
: ApiBase
::LIMIT_SML1
;
826 ParamValidator
::PARAM_ISMULTI
=> true,
827 ParamValidator
::PARAM_DEFAULT
=> 'ids|timestamp|flags|comment|user',
828 ParamValidator
::PARAM_TYPE
=> [
846 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-prop',
847 ApiBase
::PARAM_HELP_MSG_PER_VALUE
=> [
848 'ids' => 'apihelp-query+revisions+base-paramvalue-prop-ids',
849 'flags' => 'apihelp-query+revisions+base-paramvalue-prop-flags',
850 'timestamp' => 'apihelp-query+revisions+base-paramvalue-prop-timestamp',
851 'user' => 'apihelp-query+revisions+base-paramvalue-prop-user',
852 'userid' => 'apihelp-query+revisions+base-paramvalue-prop-userid',
853 'size' => 'apihelp-query+revisions+base-paramvalue-prop-size',
854 'slotsize' => 'apihelp-query+revisions+base-paramvalue-prop-slotsize',
855 'sha1' => 'apihelp-query+revisions+base-paramvalue-prop-sha1',
856 'slotsha1' => 'apihelp-query+revisions+base-paramvalue-prop-slotsha1',
857 'contentmodel' => 'apihelp-query+revisions+base-paramvalue-prop-contentmodel',
858 'comment' => 'apihelp-query+revisions+base-paramvalue-prop-comment',
859 'parsedcomment' => 'apihelp-query+revisions+base-paramvalue-prop-parsedcomment',
860 'content' => [ 'apihelp-query+revisions+base-paramvalue-prop-content', $smallLimit ],
861 'tags' => 'apihelp-query+revisions+base-paramvalue-prop-tags',
862 'roles' => 'apihelp-query+revisions+base-paramvalue-prop-roles',
863 'parsetree' => [ 'apihelp-query+revisions+base-paramvalue-prop-parsetree',
864 CONTENT_MODEL_WIKITEXT
, $smallLimit ],
866 EnumDef
::PARAM_DEPRECATED_VALUES
=> [
871 ParamValidator
::PARAM_TYPE
=> $slotRoles,
872 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-slots',
873 ParamValidator
::PARAM_ISMULTI
=> true,
874 ParamValidator
::PARAM_ALL
=> true,
876 'contentformat-{slot}' => [
877 ApiBase
::PARAM_TEMPLATE_VARS
=> [ 'slot' => 'slots' ],
878 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-contentformat-slot',
879 ParamValidator
::PARAM_TYPE
=> $this->contentHandlerFactory
->getAllContentFormats(),
882 ParamValidator
::PARAM_TYPE
=> 'limit',
883 IntegerDef
::PARAM_MIN
=> 1,
884 IntegerDef
::PARAM_MAX
=> ApiBase
::LIMIT_BIG1
,
885 IntegerDef
::PARAM_MAX2
=> ApiBase
::LIMIT_BIG2
,
886 ApiBase
::PARAM_HELP_MSG
=> [ 'apihelp-query+revisions+base-param-limit',
887 $smallLimit, self
::LIMIT_PARSE
],
889 'expandtemplates' => [
890 ParamValidator
::PARAM_DEFAULT
=> false,
891 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-expandtemplates',
892 ParamValidator
::PARAM_DEPRECATED
=> true,
895 ParamValidator
::PARAM_DEFAULT
=> false,
896 ParamValidator
::PARAM_DEPRECATED
=> true,
897 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-generatexml',
900 ParamValidator
::PARAM_DEFAULT
=> false,
901 ApiBase
::PARAM_HELP_MSG
=> [ 'apihelp-query+revisions+base-param-parse', self
::LIMIT_PARSE
],
902 ParamValidator
::PARAM_DEPRECATED
=> true,
905 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-section',
908 ApiBase
::PARAM_HELP_MSG
=> [ 'apihelp-query+revisions+base-param-diffto', $smallLimit ],
909 ParamValidator
::PARAM_DEPRECATED
=> true,
912 ApiBase
::PARAM_HELP_MSG
=> [ 'apihelp-query+revisions+base-param-difftotext', $smallLimit ],
913 ParamValidator
::PARAM_DEPRECATED
=> true,
916 ParamValidator
::PARAM_DEFAULT
=> false,
917 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-difftotextpst',
918 ParamValidator
::PARAM_DEPRECATED
=> true,
921 ParamValidator
::PARAM_TYPE
=> $this->contentHandlerFactory
->getAllContentFormats(),
922 ApiBase
::PARAM_HELP_MSG
=> 'apihelp-query+revisions+base-param-contentformat',
923 ParamValidator
::PARAM_DEPRECATED
=> true,
929 /** @deprecated class alias since 1.43 */
930 class_alias( ApiQueryRevisionsBase
::class, 'ApiQueryRevisionsBase' );