52 private const IS_DELETED = 1;
53 private const CANNOT_VIEW = 2;
71 private $numUncachedDiffs = 0;
74 private $revisionStore;
77 private $contentHandlerFactory;
80 private $parserFactory;
83 private $slotRoleRegistry;
86 private $contentRenderer;
89 private $contentTransformer;
115 parent::__construct( $queryModule, $moduleName, $paramPrefix );
118 $services = MediaWikiServices::getInstance();
119 $this->revisionStore = $revisionStore ?? $services->getRevisionStore();
120 $this->contentHandlerFactory = $contentHandlerFactory ?? $services->getContentHandlerFactory();
121 $this->parserFactory = $parserFactory ?? $services->getParserFactory();
122 $this->slotRoleRegistry = $slotRoleRegistry ?? $services->getSlotRoleRegistry();
123 $this->contentRenderer = $contentRenderer ?? $services->getContentRenderer();
124 $this->contentTransformer = $contentTransformer ?? $services->getContentTransformer();
132 $this->
run( $resultPageSet );
147 $prop = array_fill_keys( $params[
'prop'],
true );
149 $this->fld_ids = isset( $prop[
'ids'] );
150 $this->fld_flags = isset( $prop[
'flags'] );
151 $this->fld_timestamp = isset( $prop[
'timestamp'] );
152 $this->fld_comment = isset( $prop[
'comment'] );
153 $this->fld_parsedcomment = isset( $prop[
'parsedcomment'] );
154 $this->fld_size = isset( $prop[
'size'] );
155 $this->fld_slotsize = isset( $prop[
'slotsize'] );
156 $this->fld_sha1 = isset( $prop[
'sha1'] );
157 $this->fld_slotsha1 = isset( $prop[
'slotsha1'] );
158 $this->fld_content = isset( $prop[
'content'] );
159 $this->fld_contentmodel = isset( $prop[
'contentmodel'] );
160 $this->fld_userid = isset( $prop[
'userid'] );
161 $this->fld_user = isset( $prop[
'user'] );
162 $this->fld_tags = isset( $prop[
'tags'] );
163 $this->fld_roles = isset( $prop[
'roles'] );
164 $this->fld_parsetree = isset( $prop[
'parsetree'] );
166 $this->slotRoles = $params[
'slots'];
168 if ( $this->slotRoles !==
null ) {
169 if ( $this->fld_parsetree ) {
171 'apierror-invalidparammix-cannotusewith',
174 ],
'invalidparammix' );
177 'expandtemplates',
'generatexml',
'parse',
'diffto',
'difftotext',
'difftotextpst',
180 if ( $params[$p] !==
null && $params[$p] !==
false ) {
182 'apierror-invalidparammix-cannotusewith',
185 ],
'invalidparammix' );
190 if ( !empty( $params[
'contentformat'] ) ) {
191 $this->contentFormat = $params[
'contentformat'];
194 $this->limit = $params[
'limit'];
196 if ( $params[
'difftotext'] !==
null ) {
197 $this->difftotext = $params[
'difftotext'];
198 $this->difftotextpst = $params[
'difftotextpst'];
199 } elseif ( $params[
'diffto'] !==
null ) {
200 if ( $params[
'diffto'] ==
'cur' ) {
201 $params[
'diffto'] = 0;
203 if ( ( !ctype_digit( $params[
'diffto'] ) || $params[
'diffto'] < 0 )
204 && $params[
'diffto'] !=
'prev' && $params[
'diffto'] !=
'next'
207 $this->
dieWithError( [
'apierror-baddiffto', $p ],
'diffto' );
212 if ( is_numeric( $params[
'diffto'] ) && $params[
'diffto'] != 0 ) {
213 $difftoRev = $this->revisionStore->getRevisionById( $params[
'diffto'] );
215 $this->
dieWithError( [
'apierror-nosuchrevid', $params[
'diffto'] ] );
218 $revDel = $this->checkRevDel( $difftoRev, RevisionRecord::DELETED_TEXT );
219 if ( $revDel & self::CANNOT_VIEW ) {
220 $this->
addWarning( [
'apiwarn-difftohidden', $difftoRev->getId() ] );
221 $params[
'diffto'] =
null;
224 $this->diffto = $params[
'diffto'];
227 $this->fetchContent = $this->fld_content || $this->diffto !==
null
231 if ( $this->fetchContent ) {
233 $this->expandTemplates = $params[
'expandtemplates'];
234 $this->generateXML = $params[
'generatexml'];
235 $this->parseContent = $params[
'parse'];
236 if ( $this->parseContent ) {
238 if ( $this->limit ===
null ) {
242 $this->section = $params[
'section'] ??
false;
247 if ( $this->limit ==
'max' ) {
248 $this->limit = $this->
getMain()->canApiHighLimits() ? $botMax : $userMax;
249 if ( $this->setParsedLimit ) {
254 $this->limit = $this->
getMain()->getParamValidator()->validateValue(
255 $this,
'limit', $this->limit ?? 10, [
256 ParamValidator::PARAM_TYPE =>
'limit',
257 IntegerDef::PARAM_MIN => 1,
258 IntegerDef::PARAM_MAX => $userMax,
259 IntegerDef::PARAM_MAX2 => $botMax,
260 IntegerDef::PARAM_IGNORE_RANGE =>
true,
264 $this->needSlots = $this->fetchContent || $this->fld_contentmodel ||
266 if ( $this->needSlots && $this->slotRoles ===
null ) {
270 $parentParam = $parent->encodeParamName( $parent->getModuleManager()->getModuleGroup( $name ) );
272 [
'apiwarn-deprecation-missingparam', $encParam ],
273 "action=query&{$parentParam}={$name}&!{$encParam}"
285 private function checkRevDel(
RevisionRecord $revision, $field ) {
286 $ret = $revision->
isDeleted( $field ) ? self::IS_DELETED : 0;
289 $ret |= ( $canSee ? 0 : self::CANNOT_VIEW );
306 if ( $this->fld_ids ) {
307 $vals[
'revid'] = (int)$revision->
getId();
313 if ( $this->fld_flags ) {
314 $vals[
'minor'] = $revision->
isMinor();
317 if ( $this->fld_user || $this->fld_userid ) {
318 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_USER );
319 if ( $revDel & self::IS_DELETED ) {
320 $vals[
'userhidden'] =
true;
323 if ( !( $revDel & self::CANNOT_VIEW ) ) {
324 $u = $revision->
getUser( RevisionRecord::RAW );
325 if ( $this->fld_user ) {
326 $vals[
'user'] = $u->getName();
328 if ( !$u->isRegistered() ) {
329 $vals[
'anon'] =
true;
332 if ( $this->fld_userid ) {
333 $vals[
'userid'] = $u->getId();
338 if ( $this->fld_timestamp ) {
342 if ( $this->fld_size ) {
344 $vals[
'size'] = (int)$revision->
getSize();
352 if ( $this->fld_sha1 ) {
353 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT );
354 if ( $revDel & self::IS_DELETED ) {
355 $vals[
'sha1hidden'] =
true;
358 if ( !( $revDel & self::CANNOT_VIEW ) ) {
360 $vals[
'sha1'] = Wikimedia\base_convert( $revision->
getSha1(), 36, 16, 40 );
370 if ( $this->fld_roles ) {
374 if ( $this->needSlots ) {
375 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_TEXT );
376 if ( ( $this->fld_slotsha1 || $this->fetchContent ) && ( $revDel & self::IS_DELETED ) ) {
379 $vals = array_merge( $vals, $this->extractAllSlotInfo( $revision, $revDel ) );
384 $vals[
'slotsmissing'] =
true;
386 LoggerFactory::getInstance(
'api-warning' )->error(
387 'Failed to access revision slots',
388 [
'revision' => $revision->
getId(),
'exception' => $ex, ]
392 if ( $this->fld_comment || $this->fld_parsedcomment ) {
393 $revDel = $this->checkRevDel( $revision, RevisionRecord::DELETED_COMMENT );
394 if ( $revDel & self::IS_DELETED ) {
395 $vals[
'commenthidden'] =
true;
398 if ( !( $revDel & self::CANNOT_VIEW ) ) {
399 $comment = $revision->
getComment( RevisionRecord::RAW );
400 $comment = $comment->text ??
'';
402 if ( $this->fld_comment ) {
403 $vals[
'comment'] = $comment;
406 if ( $this->fld_parsedcomment ) {
414 if ( $this->fld_tags ) {
415 if ( $row->ts_tags ) {
416 $tags = explode(
',', $row->ts_tags );
417 ApiResult::setIndexedTagName( $tags,
'tag' );
418 $vals[
'tags'] = $tags;
424 if ( $anyHidden && $revision->
isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
425 $vals[
'suppressed'] =
true;
440 private function extractAllSlotInfo(
RevisionRecord $revision, $revDel ): array {
443 if ( $this->slotRoles ===
null ) {
445 $slot = $revision->
getSlot( SlotRecord::MAIN, RevisionRecord::RAW );
449 $vals[
'textmissing'] =
true;
455 $vals += $this->extractSlotInfo( $slot, $revDel,
$content );
456 if ( !empty( $vals[
'nosuchsection'] ) ) {
459 'apierror-nosuchsection-what',
461 $this->
msg(
'revid', $revision->
getId() )
467 $vals += $this->extractDeprecatedContent(
$content, $revision );
471 $roles = array_intersect( $this->slotRoles, $revision->
getSlotRoles() );
475 foreach ( $roles as $role ) {
477 $slot = $revision->
getSlot( $role, RevisionRecord::RAW );
481 $vals[
'slots'][$role][
'missing'] =
true;
485 $vals[
'slots'][$role] = $this->extractSlotInfo( $slot, $revDel,
$content );
490 $vals[
'slots'][$role][
'contentmodel'] =
$content->getModel();
491 $vals[
'slots'][$role][
'contentformat'] =
$content->getDefaultFormat();
493 $vals[
'slots'][$role],
518 if ( $this->fld_slotsize ) {
519 $vals[
'size'] = (int)$slot->
getSize();
522 if ( $this->fld_slotsha1 ) {
523 if ( $revDel & self::IS_DELETED ) {
524 $vals[
'sha1hidden'] =
true;
526 if ( !( $revDel & self::CANNOT_VIEW ) ) {
527 if ( $slot->
getSha1() !=
'' ) {
528 $vals[
'sha1'] = Wikimedia\base_convert( $slot->
getSha1(), 36, 16, 40 );
535 if ( $this->fld_contentmodel ) {
536 $vals[
'contentmodel'] = $slot->
getModel();
540 if ( $this->fetchContent ) {
541 if ( $revDel & self::IS_DELETED ) {
542 $vals[
'texthidden'] =
true;
544 if ( !( $revDel & self::CANNOT_VIEW ) ) {
549 $vals[
'textmissing'] =
true;
554 if (
$content && $this->section !==
false ) {
557 $vals[
'nosuchsection'] =
true;
576 if ( $this->fld_parsetree || ( $this->fld_content && $this->generateXML ) ) {
579 '@phan-var WikitextContent $content';
580 $t =
$content->getText(); # note: don
't set $text
582 $parser = $this->parserFactory->create();
583 $parser->startExternalParse(
585 ParserOptions::newFromContext( $this->getContext() ),
586 Parser::OT_PREPROCESS
588 $dom = $parser->preprocessToDom( $t );
589 // @phan-suppress-next-line PhanUndeclaredMethodInCallable
590 if ( is_callable( [ $dom, 'saveXML
' ] ) ) {
591 // @phan-suppress-next-line PhanUndeclaredMethod
592 $xml = $dom->saveXML();
594 // @phan-suppress-next-line PhanUndeclaredMethod
595 $xml = $dom->__toString();
597 $vals['parsetree
'] = $xml;
599 $vals['badcontentformatforparsetree
'] = true;
602 'apierror-parsetree-notwikitext-title
',
603 wfEscapeWikiText( $title->getPrefixedText() ),
606 'parsetree-notwikitext
'
611 if ( $this->fld_content ) {
614 if ( $this->expandTemplates && !$this->parseContent ) {
615 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
618 $text = $content->getText();
620 $text = $this->parserFactory->create()->preprocess(
623 ParserOptions::newFromContext( $this->getContext() )
627 'apierror-templateexpansion-notwikitext
',
628 wfEscapeWikiText( $title->getPrefixedText() ),
631 $vals['badcontentformat
'] = true;
635 if ( $this->parseContent ) {
636 $po = $this->contentRenderer->getParserOutput(
640 ParserOptions::newFromContext( $this->getContext() )
642 $text = $po->getText();
645 if ( $text === null ) {
646 $format = $this->contentFormat ?: $content->getDefaultFormat();
647 $model = $content->getModel();
649 if ( !$content->isSupportedFormat( $format ) ) {
650 $name = wfEscapeWikiText( $title->getPrefixedText() );
651 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
652 $vals['badcontentformat
'] = true;
655 $text = $content->serialize( $format );
656 // always include format and model.
657 // Format is needed to deserialize, model is needed to interpret.
658 $vals['contentformat
'] = $format;
659 $vals['contentmodel
'] = $model;
663 if ( $text !== false ) {
664 ApiResult::setContentValue( $vals, 'content
', $text );
668 if ( $content && ( $this->diffto !== null || $this->difftotext !== null ) ) {
669 if ( $this->numUncachedDiffs < $this->getConfig()->get( MainConfigNames::APIMaxUncachedDiffs ) ) {
671 $context = new DerivativeContext( $this->getContext() );
672 $context->setTitle( $title );
673 $handler = $content->getContentHandler();
675 if ( $this->difftotext !== null ) {
676 $model = $title->getContentModel();
678 if ( $this->contentFormat
679 && !$this->contentHandlerFactory->getContentHandler( $model )
680 ->isSupportedFormat( $this->contentFormat )
682 $name = wfEscapeWikiText( $title->getPrefixedText() );
683 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
684 $vals['diff
']['badcontentformat
'] = true;
687 $difftocontent = $this->contentHandlerFactory->getContentHandler( $model )
688 ->unserializeContent( $this->difftotext, $this->contentFormat );
690 if ( $this->difftotextpst ) {
691 $popts = ParserOptions::newFromContext( $this->getContext() );
692 $difftocontent = $this->contentTransformer->preSaveTransform(
700 $engine = $handler->createDifferenceEngine( $context );
701 $engine->setContent( $content, $difftocontent );
704 $engine = $handler->createDifferenceEngine( $context, $revision->getId(), $this->diffto );
705 $vals['diff
']['from
'] = $engine->getOldid();
706 $vals['diff
']['to
'] = $engine->getNewid();
709 $difftext = $engine->getDiffBody();
710 ApiResult::setContentValue( $vals['diff
'], 'body
', $difftext );
711 if ( !$engine->wasCacheHit() ) {
712 $this->numUncachedDiffs++;
716 $vals['diff
']['notcached
'] = true;
729 public function getCacheMode( $params ) {
730 if ( $this->userCanSeeRevDel() ) {
742 public function getAllowedParams() {
743 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
744 sort( $slotRoles, SORT_STRING );
748 ParamValidator::PARAM_ISMULTI => true,
749 ParamValidator::PARAM_DEFAULT => 'ids|timestamp|flags|comment|user
',
750 ParamValidator::PARAM_TYPE => [
768 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop
',
769 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
770 'ids
' => 'apihelp-query+revisions+base-paramvalue-prop-ids
',
771 'flags
' => 'apihelp-query+revisions+base-paramvalue-prop-flags
',
772 'timestamp
' => 'apihelp-query+revisions+base-paramvalue-prop-timestamp
',
773 'user
' => 'apihelp-query+revisions+base-paramvalue-prop-user
',
774 'userid
' => 'apihelp-query+revisions+base-paramvalue-prop-userid
',
775 'size
' => 'apihelp-query+revisions+base-paramvalue-prop-size
',
776 'slotsize
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsize
',
777 'sha1
' => 'apihelp-query+revisions+base-paramvalue-prop-sha1
',
778 'slotsha1
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsha1
',
779 'contentmodel
' => 'apihelp-query+revisions+base-paramvalue-prop-contentmodel
',
780 'comment
' => 'apihelp-query+revisions+base-paramvalue-prop-comment
',
781 'parsedcomment
' => 'apihelp-query+revisions+base-paramvalue-prop-parsedcomment
',
782 'content
' => 'apihelp-query+revisions+base-paramvalue-prop-content
',
783 'tags
' => 'apihelp-query+revisions+base-paramvalue-prop-tags
',
784 'roles
' => 'apihelp-query+revisions+base-paramvalue-prop-roles
',
785 'parsetree
' => [ 'apihelp-query+revisions+base-paramvalue-prop-parsetree
',
786 CONTENT_MODEL_WIKITEXT ],
788 EnumDef::PARAM_DEPRECATED_VALUES => [
793 ParamValidator::PARAM_TYPE => $slotRoles,
794 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-slots
',
795 ParamValidator::PARAM_ISMULTI => true,
796 ParamValidator::PARAM_ALL => true,
799 ParamValidator::PARAM_TYPE => 'limit
',
800 IntegerDef::PARAM_MIN => 1,
801 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
802 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
803 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit
',
805 'expandtemplates
' => [
806 ParamValidator::PARAM_DEFAULT => false,
807 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates
',
808 ParamValidator::PARAM_DEPRECATED => true,
811 ParamValidator::PARAM_DEFAULT => false,
812 ParamValidator::PARAM_DEPRECATED => true,
813 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml
',
816 ParamValidator::PARAM_DEFAULT => false,
817 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse
',
818 ParamValidator::PARAM_DEPRECATED => true,
821 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section
',
824 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto
',
825 ParamValidator::PARAM_DEPRECATED => true,
828 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext
',
829 ParamValidator::PARAM_DEPRECATED => true,
832 ParamValidator::PARAM_DEFAULT => false,
833 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotextpst
',
834 ParamValidator::PARAM_DEPRECATED => true,
837 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
838 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat
',
839 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.
$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.
__construct(ApiQuery $queryModule, $moduleName, $paramPrefix='', RevisionStore $revisionStore=null, IContentHandlerFactory $contentHandlerFactory=null, ParserFactory $parserFactory=null, SlotRoleRegistry $slotRoleRegistry=null, ContentRenderer $contentRenderer=null, ContentTransformer $contentTransformer=null)
$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()
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
A service to render content.
A service to transform content.
A class containing constants representing the names of configuration variables.
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Content object for wiki text pages.
Base interface for content objects.
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...