Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiQueryRevisionsBase.php
bloba08d3b970d29d38ceeab355b6f09e251ad9635b4
1 <?php
2 /**
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
20 * @file
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;
47 use stdClass;
48 use Wikimedia\ParamValidator\ParamValidator;
49 use Wikimedia\ParamValidator\TypeDef\EnumDef;
50 use Wikimedia\ParamValidator\TypeDef\IntegerDef;
52 /**
53 * A base class for functions common to producing a list of revisions.
55 * @stable to extend
57 * @ingroup API
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;
71 // endregion
73 /** @var int|string|null */
74 protected $limit;
75 /** @var int|string|null */
76 protected $diffto;
77 /** @var string|null */
78 protected $difftotext;
79 /** @var bool */
80 protected $difftotextpst;
81 /** @var int|string|null */
82 protected $expandTemplates;
83 /** @var bool */
84 protected $generateXML;
85 /** @var int|string|null */
86 protected $section;
87 /** @var bool */
88 protected $parseContent;
89 /** @var bool */
90 protected $fetchContent;
91 /** @var string */
92 protected $contentFormat;
93 protected bool $setParsedLimit = true;
94 protected ?array $slotRoles = null;
95 /** @var string[] */
96 protected $slotContentFormats;
97 /** @var bool */
98 protected $needSlots;
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.
119 * @var int
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
136 * @stable to call
138 public function __construct(
139 ApiQuery $queryModule,
140 string $moduleName,
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() {
170 $this->run();
173 public function executeGenerator( $resultPageSet ) {
174 $this->run( $resultPageSet );
178 * @param ApiPageSet|null $resultPageSet
179 * @return void
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' );
218 foreach ( [
219 'expandtemplates', 'generatexml', 'parse', 'diffto', 'difftotext', 'difftotextpst',
220 'contentformat'
221 ] as $p ) {
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'] );
262 if ( !$difftoRev ) {
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;
278 $smallLimit = false;
279 if ( $this->fetchContent ) {
280 $smallLimit = true;
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;
335 if ( $ret ) {
336 $canSee = $revision->userCan( $field, $this->getAuthority() );
337 $ret |= ( $canSee ? 0 : self::CANNOT_VIEW );
339 return $ret;
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
348 * @return array
350 protected function extractRevisionInfo( RevisionRecord $revision, $row ) {
351 $vals = [];
352 $anyHidden = false;
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;
369 $anyHidden = 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 ) {
394 try {
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.
399 $vals['size'] = 0;
403 if ( $this->fld_sha1 ) {
404 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT );
405 if ( $revDel & self::IS_DELETED ) {
406 $vals['sha1hidden'] = true;
407 $anyHidden = true;
409 if ( !( $revDel & self::CANNOT_VIEW ) ) {
410 try {
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.
415 $vals['sha1'] = '';
420 try {
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 ) ) {
428 $anyHidden = true;
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;
447 $anyHidden = 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;
470 } else {
471 $vals['tags'] = [];
475 if ( $anyHidden && $revision->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
476 $vals['suppressed'] = true;
479 return $vals;
483 * Extracts information about all relevant slots.
485 * @param RevisionRecord $revision
486 * @param int $revDel
488 * @return array
489 * @throws ApiUsageException
491 private function extractAllSlotInfo( RevisionRecord $revision, $revDel ): array {
492 $vals = [];
494 if ( $this->slotRoles === null ) {
495 try {
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;
501 $slot = null;
504 if ( $slot ) {
505 $content = null;
506 $vals += $this->extractSlotInfo( $slot, $revDel, $content );
507 if ( !empty( $vals['nosuchsection'] ) ) {
508 $this->dieWithError(
510 'apierror-nosuchsection-what',
511 wfEscapeWikiText( $this->section ),
512 $this->msg( 'revid', $revision->getId() )
514 'nosuchsection'
517 if ( $content ) {
518 $vals += $this->extractDeprecatedContent( $content, $revision );
521 } else {
522 $roles = array_intersect( $this->slotRoles, $revision->getSlotRoles() );
523 $vals['slots'] = [
524 ApiResult::META_KVP_MERGE => true,
526 foreach ( $roles as $role ) {
527 try {
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;
533 continue;
535 $content = null;
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.
539 if ( $content ) {
540 /** @var Content $content */
541 $model = $content->getModel();
542 $format = $this->slotContentFormats[$role] ?? $content->getDefaultFormat();
543 if ( !$content->isSupportedFormat( $format ) ) {
544 $this->addWarning( [
545 'apierror-badformat',
546 $format,
547 $model,
548 $this->msg( 'revid', $revision->getId() )
549 ] );
550 $vals['slots'][$role]['badcontentformat'] = true;
551 } else {
552 $vals['slots'][$role]['contentmodel'] = $model;
553 $vals['slots'][$role]['contentformat'] = $format;
554 ApiResult::setContentValue(
555 $vals['slots'][$role],
556 'content',
557 $content->serialize( $format )
562 ApiResult::setArrayType( $vals['slots'], 'kvp', 'role' );
563 ApiResult::setIndexedTagName( $vals['slots'], 'slot' );
565 return $vals;
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
575 * @return array
577 private function extractSlotInfo( SlotRecord $slot, $revDel, &$content = null ) {
578 $vals = [];
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 );
592 } else {
593 $vals['sha1'] = '';
598 if ( $this->fld_contentmodel ) {
599 $vals['contentmodel'] = $slot->getModel();
602 $content = null;
603 if ( $this->fetchContent ) {
604 if ( $revDel & self::IS_DELETED ) {
605 $vals['texthidden'] = true;
607 if ( !( $revDel & self::CANNOT_VIEW ) ) {
608 try {
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 );
619 if ( !$content ) {
620 $vals['nosuchsection'] = true;
626 return $vals;
630 * Format a Content using deprecated options
631 * @param Content $content Content to format
632 * @param RevisionRecord $revision Revision being processed
633 * @return array
635 private function extractDeprecatedContent( Content $content, RevisionRecord $revision ) {
636 $vals = [];
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(
647 $title,
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();
656 } else {
657 // @phan-suppress-next-line PhanUndeclaredMethod
658 $xml = $dom->__toString();
660 $vals['parsetree'] = $xml;
661 } else {
662 $vals['badcontentformatforparsetree'] = true;
663 $this->addWarning(
665 'apierror-parsetree-notwikitext-title',
666 wfEscapeWikiText( $title->getPrefixedText() ),
667 $content->getModel()
669 'parsetree-notwikitext'
674 if ( $this->fld_content ) {
675 $text = null;
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(
684 $text,
685 $title,
686 ParserOptions::newFromContext( $this->getContext() )
688 } else {
689 $this->addWarning( [
690 'apierror-templateexpansion-notwikitext',
691 wfEscapeWikiText( $title->getPrefixedText() ),
692 $content->getModel()
693 ] );
694 $vals['badcontentformat'] = true;
695 $text = false;
698 if ( $this->parseContent ) {
699 $popts = ParserOptions::newFromContext( $this->getContext() );
700 $po = $this->contentRenderer->getParserOutput(
701 $content,
702 $title,
703 $revision,
704 $popts
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;
718 $text = false;
719 } else {
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 ) ) {
735 $vals['diff'] = [];
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;
750 $engine = null;
751 } else {
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(
758 $difftocontent,
759 $title,
760 $this->getUserForPreview(),
761 $popts
765 $engine = $handler->createDifferenceEngine( $context );
766 $engine->setContent( $content, $difftocontent );
768 } else {
769 $engine = $handler->createDifferenceEngine( $context, $revision->getId(), $this->diffto );
770 $vals['diff']['from'] = $engine->getOldid();
771 $vals['diff']['to'] = $engine->getNewid();
773 if ( $engine ) {
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 );
783 } else {
784 $vals['diff']['notcached'] = true;
788 return $vals;
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() )
798 return $user;
802 * @stable to override
803 * @param array $params
805 * @return string
807 public function getCacheMode( $params ) {
808 if ( $this->userCanSeeRevDel() ) {
809 return 'private';
812 return 'public';
816 * @stable to override
817 * @return array
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;
824 return [
825 'prop' => [
826 ParamValidator::PARAM_ISMULTI => true,
827 ParamValidator::PARAM_DEFAULT => 'ids|timestamp|flags|comment|user',
828 ParamValidator::PARAM_TYPE => [
829 'ids',
830 'flags',
831 'timestamp',
832 'user',
833 'userid',
834 'size',
835 'slotsize',
836 'sha1',
837 'slotsha1',
838 'contentmodel',
839 'comment',
840 'parsedcomment',
841 'content',
842 'tags',
843 'roles',
844 'parsetree',
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 => [
867 'parsetree' => true,
870 'slots' => [
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(),
881 'limit' => [
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,
894 'generatexml' => [
895 ParamValidator::PARAM_DEFAULT => false,
896 ParamValidator::PARAM_DEPRECATED => true,
897 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml',
899 'parse' => [
900 ParamValidator::PARAM_DEFAULT => false,
901 ApiBase::PARAM_HELP_MSG => [ 'apihelp-query+revisions+base-param-parse', self::LIMIT_PARSE ],
902 ParamValidator::PARAM_DEPRECATED => true,
904 'section' => [
905 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section',
907 'diffto' => [
908 ApiBase::PARAM_HELP_MSG => [ 'apihelp-query+revisions+base-param-diffto', $smallLimit ],
909 ParamValidator::PARAM_DEPRECATED => true,
911 'difftotext' => [
912 ApiBase::PARAM_HELP_MSG => [ 'apihelp-query+revisions+base-param-difftotext', $smallLimit ],
913 ParamValidator::PARAM_DEPRECATED => true,
915 'difftotextpst' => [
916 ParamValidator::PARAM_DEFAULT => false,
917 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotextpst',
918 ParamValidator::PARAM_DEPRECATED => true,
920 'contentformat' => [
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' );