54 private const IS_DELETED = 1;
55 private const CANNOT_VIEW = 2;
73 private $numUncachedDiffs = 0;
76 private $revisionStore;
79 private $contentHandlerFactory;
82 private $parserFactory;
85 private $slotRoleRegistry;
88 private $contentRenderer;
91 private $contentTransformer;
94 private $commentFormatter;
122 parent::__construct( $queryModule, $moduleName, $paramPrefix );
125 $services = MediaWikiServices::getInstance();
126 $this->revisionStore = $revisionStore ?? $services->getRevisionStore();
127 $this->contentHandlerFactory = $contentHandlerFactory ?? $services->getContentHandlerFactory();
128 $this->parserFactory = $parserFactory ?? $services->getParserFactory();
129 $this->slotRoleRegistry = $slotRoleRegistry ?? $services->getSlotRoleRegistry();
130 $this->contentRenderer = $contentRenderer ?? $services->getContentRenderer();
131 $this->contentTransformer = $contentTransformer ?? $services->getContentTransformer();
132 $this->commentFormatter = $commentFormatter ?? $services->getCommentFormatter();
140 $this->
run( $resultPageSet );
155 $prop = array_fill_keys( $params[
'prop'],
true );
157 $this->fld_ids = isset( $prop[
'ids'] );
158 $this->fld_flags = isset( $prop[
'flags'] );
159 $this->fld_timestamp = isset( $prop[
'timestamp'] );
160 $this->fld_comment = isset( $prop[
'comment'] );
161 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
162 $this->fld_size = isset( $prop[
'size'] );
163 $this->fld_slotsize = isset( $prop[
'slotsize'] );
164 $this->fld_sha1 = isset( $prop[
'sha1'] );
165 $this->fld_slotsha1 = isset( $prop[
'slotsha1'] );
166 $this->fld_content = isset( $prop[
'content'] );
167 $this->fld_contentmodel = isset( $prop[
'contentmodel'] );
168 $this->fld_userid = isset( $prop[
'userid'] );
169 $this->fld_user = isset( $prop[
'user'] );
170 $this->fld_tags = isset( $prop[
'tags'] );
171 $this->fld_roles = isset( $prop[
'roles'] );
172 $this->fld_parsetree = isset( $prop[
'parsetree'] );
174 $this->slotRoles = $params[
'slots'];
176 if ( $this->slotRoles !==
null ) {
177 if ( $this->fld_parsetree ) {
179 'apierror-invalidparammix-cannotusewith',
182 ],
'invalidparammix' );
185 'expandtemplates',
'generatexml',
'parse',
'diffto',
'difftotext',
'difftotextpst',
188 if ( $params[$p] !==
null && $params[$p] !==
false ) {
190 'apierror-invalidparammix-cannotusewith',
193 ],
'invalidparammix' );
198 if ( !empty( $params[
'contentformat'] ) ) {
199 $this->contentFormat = $params[
'contentformat'];
202 $this->limit = $params[
'limit'];
204 if ( $params[
'difftotext'] !==
null ) {
205 $this->difftotext = $params[
'difftotext'];
206 $this->difftotextpst = $params[
'difftotextpst'];
207 } elseif ( $params[
'diffto'] !==
null ) {
208 if ( $params[
'diffto'] ==
'cur' ) {
209 $params[
'diffto'] = 0;
211 if ( ( !ctype_digit( $params[
'diffto'] ) || $params[
'diffto'] < 0 )
212 && $params[
'diffto'] !=
'prev' && $params[
'diffto'] !=
'next'
215 $this->
dieWithError( [
'apierror-baddiffto', $p ],
'diffto' );
220 if ( is_numeric( $params[
'diffto'] ) && $params[
'diffto'] != 0 ) {
221 $difftoRev = $this->revisionStore->getRevisionById( $params[
'diffto'] );
223 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'diffto'] ] );
226 $revDel = $this->checkRevDel( $difftoRev, RevisionRecord::DELETED_TEXT );
227 if ( $revDel & self::CANNOT_VIEW ) {
228 $this->
addWarning( [
'apiwarn-difftohidden', $difftoRev->getId() ] );
229 $params[
'diffto'] =
null;
232 $this->diffto = $params[
'diffto'];
235 $this->fetchContent = $this->fld_content || $this->diffto !==
null
239 if ( $this->fetchContent ) {
241 $this->expandTemplates = $params[
'expandtemplates'];
242 $this->generateXML = $params[
'generatexml'];
243 $this->parseContent = $params[
'parse'];
244 if ( $this->parseContent ) {
248 $this->section = $params[
'section'] ??
false;
253 if ( $this->limit ==
'max' ) {
254 $this->limit = $this->
getMain()->canApiHighLimits() ? $botMax : $userMax;
255 if ( $this->setParsedLimit ) {
260 $this->limit = $this->
getMain()->getParamValidator()->validateValue(
261 $this,
'limit', $this->limit ?? 10, [
262 ParamValidator::PARAM_TYPE =>
'limit',
263 IntegerDef::PARAM_MIN => 1,
264 IntegerDef::PARAM_MAX => $userMax,
265 IntegerDef::PARAM_MAX2 => $botMax,
266 IntegerDef::PARAM_IGNORE_RANGE =>
true,
270 $this->needSlots = $this->fetchContent || $this->fld_contentmodel ||
272 if ( $this->needSlots && $this->slotRoles ===
null ) {
276 $parentParam = $parent->encodeParamName( $parent->getModuleManager()->getModuleGroup( $name ) );
278 [
'apiwarn-deprecation-missingparam', $encParam ],
279 "action=query&{$parentParam}={$name}&!{$encParam}"
291 private function checkRevDel(
RevisionRecord $revision, $field ) {
292 $ret = $revision->
isDeleted( $field ) ? self::IS_DELETED : 0;
295 $ret |= ( $canSee ? 0 : self::CANNOT_VIEW );
312 if ( $this->fld_ids ) {
313 $vals[
'revid'] = (int)$revision->
getId();
319 if ( $this->fld_flags ) {
320 $vals[
'minor'] = $revision->
isMinor();
323 if ( $this->fld_user || $this->fld_userid ) {
324 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_USER );
325 if ( $revDel & self::IS_DELETED ) {
326 $vals[
'userhidden'] =
true;
329 if ( !( $revDel & self::CANNOT_VIEW ) ) {
330 $u = $revision->
getUser( RevisionRecord::RAW );
331 if ( $this->fld_user ) {
332 $vals[
'user'] = $u->getName();
334 if ( !$u->isRegistered() ) {
335 $vals[
'anon'] =
true;
338 if ( $this->fld_userid ) {
339 $vals[
'userid'] = $u->getId();
344 if ( $this->fld_timestamp ) {
348 if ( $this->fld_size ) {
350 $vals[
'size'] = (int)$revision->
getSize();
358 if ( $this->fld_sha1 ) {
359 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT );
360 if ( $revDel & self::IS_DELETED ) {
361 $vals[
'sha1hidden'] =
true;
364 if ( !( $revDel & self::CANNOT_VIEW ) ) {
366 $vals[
'sha1'] = Wikimedia\base_convert( $revision->
getSha1(), 36, 16, 40 );
376 if ( $this->fld_roles ) {
380 if ( $this->needSlots ) {
381 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT );
382 if ( ( $this->fld_slotsha1 || $this->fetchContent ) && ( $revDel & self::IS_DELETED ) ) {
385 $vals = array_merge( $vals, $this->extractAllSlotInfo( $revision, $revDel ) );
390 $vals[
'slotsmissing'] =
true;
392 LoggerFactory::getInstance(
'api-warning' )->error(
393 'Failed to access revision slots',
394 [
'revision' => $revision->
getId(),
'exception' => $ex, ]
398 if ( $this->fld_comment || $this->fld_parsedcomment ) {
399 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_COMMENT );
400 if ( $revDel & self::IS_DELETED ) {
401 $vals[
'commenthidden'] =
true;
404 if ( !( $revDel & self::CANNOT_VIEW ) ) {
405 $comment = $revision->
getComment( RevisionRecord::RAW );
406 $comment = $comment->text ??
'';
408 if ( $this->fld_comment ) {
409 $vals[
'comment'] = $comment;
412 if ( $this->fld_parsedcomment ) {
413 $vals[
'parsedcomment'] = $this->commentFormatter->format(
420 if ( $this->fld_tags ) {
421 if ( $row->ts_tags ) {
422 $tags = explode(
',', $row->ts_tags );
423 ApiResult::setIndexedTagName( $tags,
'tag' );
424 $vals[
'tags'] = $tags;
430 if ( $anyHidden && $revision->
isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
431 $vals[
'suppressed'] =
true;
446 private function extractAllSlotInfo(
RevisionRecord $revision, $revDel ): array {
449 if ( $this->slotRoles ===
null ) {
451 $slot = $revision->
getSlot( SlotRecord::MAIN, RevisionRecord::RAW );
455 $vals[
'textmissing'] =
true;
461 $vals += $this->extractSlotInfo( $slot, $revDel,
$content );
462 if ( !empty( $vals[
'nosuchsection'] ) ) {
465 'apierror-nosuchsection-what',
467 $this->
msg(
'revid', $revision->
getId() )
473 $vals += $this->extractDeprecatedContent(
$content, $revision );
477 $roles = array_intersect( $this->slotRoles, $revision->
getSlotRoles() );
481 foreach ( $roles as $role ) {
483 $slot = $revision->
getSlot( $role, RevisionRecord::RAW );
487 $vals[
'slots'][$role][
'missing'] =
true;
491 $vals[
'slots'][$role] = $this->extractSlotInfo( $slot, $revDel,
$content );
496 $vals[
'slots'][$role][
'contentmodel'] =
$content->getModel();
497 $vals[
'slots'][$role][
'contentformat'] =
$content->getDefaultFormat();
499 $vals[
'slots'][$role],
524 if ( $this->fld_slotsize ) {
525 $vals[
'size'] = (int)$slot->
getSize();
528 if ( $this->fld_slotsha1 ) {
529 if ( $revDel & self::IS_DELETED ) {
530 $vals[
'sha1hidden'] =
true;
532 if ( !( $revDel & self::CANNOT_VIEW ) ) {
533 if ( $slot->
getSha1() !=
'' ) {
534 $vals[
'sha1'] = Wikimedia\base_convert( $slot->
getSha1(), 36, 16, 40 );
541 if ( $this->fld_contentmodel ) {
542 $vals[
'contentmodel'] = $slot->
getModel();
546 if ( $this->fetchContent ) {
547 if ( $revDel & self::IS_DELETED ) {
548 $vals[
'texthidden'] =
true;
550 if ( !( $revDel & self::CANNOT_VIEW ) ) {
555 $vals[
'textmissing'] =
true;
560 if (
$content && $this->section !==
false ) {
563 $vals[
'nosuchsection'] =
true;
582 if ( $this->fld_parsetree || ( $this->fld_content && $this->generateXML ) ) {
585 '@phan-var WikitextContent $content';
586 $t =
$content->getText(); # note: don
't set $text
588 $parser = $this->parserFactory->create();
589 $parser->startExternalParse(
591 ParserOptions::newFromContext( $this->getContext() ),
592 Parser::OT_PREPROCESS
594 $dom = $parser->preprocessToDom( $t );
595 // @phan-suppress-next-line PhanUndeclaredMethodInCallable
596 if ( is_callable( [ $dom, 'saveXML
' ] ) ) {
597 // @phan-suppress-next-line PhanUndeclaredMethod
598 $xml = $dom->saveXML();
600 // @phan-suppress-next-line PhanUndeclaredMethod
601 $xml = $dom->__toString();
603 $vals['parsetree
'] = $xml;
605 $vals['badcontentformatforparsetree
'] = true;
608 'apierror-parsetree-notwikitext-title
',
609 wfEscapeWikiText( $title->getPrefixedText() ),
612 'parsetree-notwikitext
'
617 if ( $this->fld_content ) {
620 if ( $this->expandTemplates && !$this->parseContent ) {
621 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
624 $text = $content->getText();
626 $text = $this->parserFactory->create()->preprocess(
629 ParserOptions::newFromContext( $this->getContext() )
633 'apierror-templateexpansion-notwikitext
',
634 wfEscapeWikiText( $title->getPrefixedText() ),
637 $vals['badcontentformat
'] = true;
641 if ( $this->parseContent ) {
642 $po = $this->contentRenderer->getParserOutput(
646 ParserOptions::newFromContext( $this->getContext() )
648 $text = $po->getText();
651 if ( $text === null ) {
652 $format = $this->contentFormat ?: $content->getDefaultFormat();
653 $model = $content->getModel();
655 if ( !$content->isSupportedFormat( $format ) ) {
656 $name = wfEscapeWikiText( $title->getPrefixedText() );
657 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
658 $vals['badcontentformat
'] = true;
661 $text = $content->serialize( $format );
662 // always include format and model.
663 // Format is needed to deserialize, model is needed to interpret.
664 $vals['contentformat
'] = $format;
665 $vals['contentmodel
'] = $model;
669 if ( $text !== false ) {
670 ApiResult::setContentValue( $vals, 'content
', $text );
674 if ( $content && ( $this->diffto !== null || $this->difftotext !== null ) ) {
675 if ( $this->numUncachedDiffs < $this->getConfig()->get( MainConfigNames::APIMaxUncachedDiffs ) ) {
677 $context = new DerivativeContext( $this->getContext() );
678 $context->setTitle( $title );
679 $handler = $content->getContentHandler();
681 if ( $this->difftotext !== null ) {
682 $model = $title->getContentModel();
684 if ( $this->contentFormat
685 && !$this->contentHandlerFactory->getContentHandler( $model )
686 ->isSupportedFormat( $this->contentFormat )
688 $name = wfEscapeWikiText( $title->getPrefixedText() );
689 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
690 $vals['diff
']['badcontentformat
'] = true;
693 $difftocontent = $this->contentHandlerFactory->getContentHandler( $model )
694 ->unserializeContent( $this->difftotext, $this->contentFormat );
696 if ( $this->difftotextpst ) {
697 $popts = ParserOptions::newFromContext( $this->getContext() );
698 $difftocontent = $this->contentTransformer->preSaveTransform(
706 $engine = $handler->createDifferenceEngine( $context );
707 $engine->setContent( $content, $difftocontent );
710 $engine = $handler->createDifferenceEngine( $context, $revision->getId(), $this->diffto );
711 $vals['diff
']['from
'] = $engine->getOldid();
712 $vals['diff
']['to
'] = $engine->getNewid();
715 $difftext = $engine->getDiffBody();
716 ApiResult::setContentValue( $vals['diff
'], 'body
', $difftext );
717 if ( !$engine->wasCacheHit() ) {
718 $this->numUncachedDiffs++;
720 foreach ( $engine->getRevisionLoadErrors() as $msg ) {
721 $this->addWarning( $msg );
725 $vals['diff
']['notcached
'] = true;
738 public function getCacheMode( $params ) {
739 if ( $this->userCanSeeRevDel() ) {
751 public function getAllowedParams() {
752 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
753 sort( $slotRoles, SORT_STRING );
757 ParamValidator::PARAM_ISMULTI => true,
758 ParamValidator::PARAM_DEFAULT => 'ids|timestamp|flags|comment|user
',
759 ParamValidator::PARAM_TYPE => [
777 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop
',
778 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
779 'ids
' => 'apihelp-query+revisions+base-paramvalue-prop-ids
',
780 'flags
' => 'apihelp-query+revisions+base-paramvalue-prop-flags
',
781 'timestamp
' => 'apihelp-query+revisions+base-paramvalue-prop-timestamp
',
782 'user
' => 'apihelp-query+revisions+base-paramvalue-prop-user
',
783 'userid
' => 'apihelp-query+revisions+base-paramvalue-prop-userid
',
784 'size
' => 'apihelp-query+revisions+base-paramvalue-prop-size
',
785 'slotsize
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsize
',
786 'sha1
' => 'apihelp-query+revisions+base-paramvalue-prop-sha1
',
787 'slotsha1
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsha1
',
788 'contentmodel
' => 'apihelp-query+revisions+base-paramvalue-prop-contentmodel
',
789 'comment
' => 'apihelp-query+revisions+base-paramvalue-prop-comment
',
790 'parsedcomment
' => 'apihelp-query+revisions+base-paramvalue-prop-parsedcomment
',
791 'content
' => 'apihelp-query+revisions+base-paramvalue-prop-content
',
792 'tags
' => 'apihelp-query+revisions+base-paramvalue-prop-tags
',
793 'roles
' => 'apihelp-query+revisions+base-paramvalue-prop-roles
',
794 'parsetree
' => [ 'apihelp-query+revisions+base-paramvalue-prop-parsetree
',
795 CONTENT_MODEL_WIKITEXT ],
797 EnumDef::PARAM_DEPRECATED_VALUES => [
802 ParamValidator::PARAM_TYPE => $slotRoles,
803 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-slots
',
804 ParamValidator::PARAM_ISMULTI => true,
805 ParamValidator::PARAM_ALL => true,
808 ParamValidator::PARAM_TYPE => 'limit
',
809 IntegerDef::PARAM_MIN => 1,
810 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
811 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
812 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit
',
814 'expandtemplates
' => [
815 ParamValidator::PARAM_DEFAULT => false,
816 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates
',
817 ParamValidator::PARAM_DEPRECATED => true,
820 ParamValidator::PARAM_DEFAULT => false,
821 ParamValidator::PARAM_DEPRECATED => true,
822 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml
',
825 ParamValidator::PARAM_DEFAULT => false,
826 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse
',
827 ParamValidator::PARAM_DEPRECATED => true,
830 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section
',
833 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto
',
834 ParamValidator::PARAM_DEPRECATED => true,
837 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext
',
838 ParamValidator::PARAM_DEPRECATED => true,
841 ParamValidator::PARAM_DEFAULT => false,
842 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotextpst
',
843 ParamValidator::PARAM_DEPRECATED => true,
846 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
847 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat
',
848 ParamValidator::PARAM_DEPRECATED => true,
const CONTENT_MODEL_WIKITEXT
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
getMain()
Get the main module.
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
const LIMIT_BIG1
Fast query, standard limit.
const LIMIT_SML2
Slow query, apihighlimits limit.
getResult()
Get the result object.
const LIMIT_SML1
Slow query, standard limit.
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
const LIMIT_BIG2
Fast query, apihighlimits limit.
getModuleName()
Get the name of the module being executed by this instance.
This class contains a list of pages that the client has requested.
getParent()
Get the parent of this module.to override 1.25 ApiBase|null
encodeParamName( $paramName)
Overrides ApiBase to prepend 'g' to every generator parameter.
A base class for functions common to producing a list of revisions.
$fld_userid
The number of uncached diffs that had to be generated for this request.
$diffto
The number of uncached diffs that had to be generated for this request.
executeGenerator( $resultPageSet)
The number of uncached diffs that had to be generated for this request.
$parseContent
The number of uncached diffs that had to be generated for this request.
$difftotext
The number of uncached diffs that had to be generated for this request.
$fld_sha1
The number of uncached diffs that had to be generated for this request.
$fld_ids
The number of uncached diffs that had to be generated for this request.
$fld_size
The number of uncached diffs that had to be generated for this request.
$fld_tags
The number of uncached diffs that had to be generated for this request.
$generateXML
The number of uncached diffs that had to be generated for this request.
$limit
The number of uncached diffs that had to be generated for this request.
$fld_comment
The number of uncached diffs that had to be generated for this request.
parseParameters( $params)
Parse the parameters into the various instance fields.
$needSlots
The number of uncached diffs that had to be generated for this request.
$expandTemplates
The number of uncached diffs that had to be generated for this request.
$fld_parsetree
The number of uncached diffs that had to be generated for this request.
$fld_slotsha1
The number of uncached diffs that had to be generated for this request.
$fetchContent
The number of uncached diffs that had to be generated for this request.
$fld_user
The number of uncached diffs that had to be generated for this request.
$fld_roles
The number of uncached diffs that had to be generated for this request.
execute()
The number of uncached diffs that had to be generated for this request.
$section
The number of uncached diffs that had to be generated for this request.
$fld_contentmodel
The number of uncached diffs that had to be generated for this request.
$setParsedLimit
The number of uncached diffs that had to be generated for this request.
$difftotextpst
The number of uncached diffs that had to be generated for this request.
$fld_parsedcomment
The number of uncached diffs that had to be generated for this request.
__construct(ApiQuery $queryModule, $moduleName, $paramPrefix='', RevisionStore $revisionStore=null, IContentHandlerFactory $contentHandlerFactory=null, ParserFactory $parserFactory=null, SlotRoleRegistry $slotRoleRegistry=null, ContentRenderer $contentRenderer=null, ContentTransformer $contentTransformer=null, CommentFormatter $commentFormatter=null)
$fld_timestamp
The number of uncached diffs that had to be generated for this request.
$fld_content
The number of uncached diffs that had to be generated for this request.
$slotRoles
The number of uncached diffs that had to be generated for this request.
$fld_slotsize
The number of uncached diffs that had to be generated for this request.
run(ApiPageSet $resultPageSet=null)
$contentFormat
The number of uncached diffs that had to be generated for this request.
extractRevisionInfo(RevisionRecord $revision, $row)
Extract information from the RevisionRecord.
$fld_flags
The number of uncached diffs that had to be generated for this request.
This is the main query class.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
const META_KVP_MERGE
Key for the metadata item that indicates that the KVP key should be added into an assoc value,...
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
A service to render content.
A service to transform content.
A class containing constants representing the names of configuration variables.
Content object for wiki text pages.
Base interface for representing page content.
getModel()
Returns the ID of the content model used by this Content object.
getDefaultFormat()
Convenience method that returns the default serialization format for the content model that this Cont...