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( (
string)$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;
580 if ( $this->fld_parsetree || ( $this->fld_content && $this->generateXML ) ) {
583 '@phan-var WikitextContent $content';
584 $t =
$content->getText(); # note: don
't set $text
586 $parser = $this->parserFactory->create();
587 $parser->startExternalParse(
589 ParserOptions::newFromContext( $this->getContext() ),
590 Parser::OT_PREPROCESS
592 $dom = $parser->preprocessToDom( $t );
593 // @phan-suppress-next-line PhanUndeclaredMethodInCallable
594 if ( is_callable( [ $dom, 'saveXML
' ] ) ) {
595 // @phan-suppress-next-line PhanUndeclaredMethod
596 $xml = $dom->saveXML();
598 // @phan-suppress-next-line PhanUndeclaredMethod
599 $xml = $dom->__toString();
601 $vals['parsetree
'] = $xml;
603 $vals['badcontentformatforparsetree
'] = true;
606 'apierror-parsetree-notwikitext-title
',
607 wfEscapeWikiText( $title->getPrefixedText() ),
610 'parsetree-notwikitext
'
615 if ( $this->fld_content ) {
618 if ( $this->expandTemplates && !$this->parseContent ) {
619 if ( $content->getModel() === CONTENT_MODEL_WIKITEXT ) {
622 $text = $content->getText();
624 $text = $this->parserFactory->create()->preprocess(
627 ParserOptions::newFromContext( $this->getContext() )
631 'apierror-templateexpansion-notwikitext
',
632 wfEscapeWikiText( $title->getPrefixedText() ),
635 $vals['badcontentformat
'] = true;
639 if ( $this->parseContent ) {
640 $po = $this->contentRenderer->getParserOutput(
644 ParserOptions::newFromContext( $this->getContext() )
646 $text = $po->getText();
649 if ( $text === null ) {
650 $format = $this->contentFormat ?: $content->getDefaultFormat();
651 $model = $content->getModel();
653 if ( !$content->isSupportedFormat( $format ) ) {
654 $name = wfEscapeWikiText( $title->getPrefixedText() );
655 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
656 $vals['badcontentformat
'] = true;
659 $text = $content->serialize( $format );
660 // always include format and model.
661 // Format is needed to deserialize, model is needed to interpret.
662 $vals['contentformat
'] = $format;
663 $vals['contentmodel
'] = $model;
667 if ( $text !== false ) {
668 ApiResult::setContentValue( $vals, 'content
', $text );
672 if ( $content && ( $this->diffto !== null || $this->difftotext !== null ) ) {
673 if ( $this->numUncachedDiffs < $this->getConfig()->get( MainConfigNames::APIMaxUncachedDiffs ) ) {
675 $context = new DerivativeContext( $this->getContext() );
676 $context->setTitle( $title );
677 $handler = $content->getContentHandler();
679 if ( $this->difftotext !== null ) {
680 $model = $title->getContentModel();
682 if ( $this->contentFormat
683 && !$this->contentHandlerFactory->getContentHandler( $model )
684 ->isSupportedFormat( $this->contentFormat )
686 $name = wfEscapeWikiText( $title->getPrefixedText() );
687 $this->addWarning( [ 'apierror-badformat
', $this->contentFormat, $model, $name ] );
688 $vals['diff
']['badcontentformat
'] = true;
691 $difftocontent = $this->contentHandlerFactory->getContentHandler( $model )
692 ->unserializeContent( $this->difftotext, $this->contentFormat );
694 if ( $this->difftotextpst ) {
695 $popts = ParserOptions::newFromContext( $this->getContext() );
696 $difftocontent = $this->contentTransformer->preSaveTransform(
704 $engine = $handler->createDifferenceEngine( $context );
705 $engine->setContent( $content, $difftocontent );
708 $engine = $handler->createDifferenceEngine( $context, $revision->getId(), $this->diffto );
709 $vals['diff
']['from
'] = $engine->getOldid();
710 $vals['diff
']['to
'] = $engine->getNewid();
713 $difftext = $engine->getDiffBody();
714 ApiResult::setContentValue( $vals['diff
'], 'body
', $difftext );
715 if ( !$engine->wasCacheHit() ) {
716 $this->numUncachedDiffs++;
720 $vals['diff
']['notcached
'] = true;
733 public function getCacheMode( $params ) {
734 if ( $this->userCanSeeRevDel() ) {
746 public function getAllowedParams() {
747 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
748 sort( $slotRoles, SORT_STRING );
752 ParamValidator::PARAM_ISMULTI => true,
753 ParamValidator::PARAM_DEFAULT => 'ids|timestamp|flags|comment|user
',
754 ParamValidator::PARAM_TYPE => [
772 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-prop
',
773 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
774 'ids
' => 'apihelp-query+revisions+base-paramvalue-prop-ids
',
775 'flags
' => 'apihelp-query+revisions+base-paramvalue-prop-flags
',
776 'timestamp
' => 'apihelp-query+revisions+base-paramvalue-prop-timestamp
',
777 'user
' => 'apihelp-query+revisions+base-paramvalue-prop-user
',
778 'userid
' => 'apihelp-query+revisions+base-paramvalue-prop-userid
',
779 'size
' => 'apihelp-query+revisions+base-paramvalue-prop-size
',
780 'slotsize
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsize
',
781 'sha1
' => 'apihelp-query+revisions+base-paramvalue-prop-sha1
',
782 'slotsha1
' => 'apihelp-query+revisions+base-paramvalue-prop-slotsha1
',
783 'contentmodel
' => 'apihelp-query+revisions+base-paramvalue-prop-contentmodel
',
784 'comment
' => 'apihelp-query+revisions+base-paramvalue-prop-comment
',
785 'parsedcomment
' => 'apihelp-query+revisions+base-paramvalue-prop-parsedcomment
',
786 'content
' => 'apihelp-query+revisions+base-paramvalue-prop-content
',
787 'tags
' => 'apihelp-query+revisions+base-paramvalue-prop-tags
',
788 'roles
' => 'apihelp-query+revisions+base-paramvalue-prop-roles
',
789 'parsetree
' => [ 'apihelp-query+revisions+base-paramvalue-prop-parsetree
',
790 CONTENT_MODEL_WIKITEXT ],
792 EnumDef::PARAM_DEPRECATED_VALUES => [
797 ParamValidator::PARAM_TYPE => $slotRoles,
798 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-slots
',
799 ParamValidator::PARAM_ISMULTI => true,
800 ParamValidator::PARAM_ALL => true,
803 ParamValidator::PARAM_TYPE => 'limit
',
804 IntegerDef::PARAM_MIN => 1,
805 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
806 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
807 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-limit
',
809 'expandtemplates
' => [
810 ParamValidator::PARAM_DEFAULT => false,
811 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-expandtemplates
',
812 ParamValidator::PARAM_DEPRECATED => true,
815 ParamValidator::PARAM_DEFAULT => false,
816 ParamValidator::PARAM_DEPRECATED => true,
817 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-generatexml
',
820 ParamValidator::PARAM_DEFAULT => false,
821 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-parse
',
822 ParamValidator::PARAM_DEPRECATED => true,
825 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-section
',
828 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-diffto
',
829 ParamValidator::PARAM_DEPRECATED => true,
832 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotext
',
833 ParamValidator::PARAM_DEPRECATED => true,
836 ParamValidator::PARAM_DEFAULT => false,
837 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-difftotextpst
',
838 ParamValidator::PARAM_DEPRECATED => true,
841 ParamValidator::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
842 ApiBase::PARAM_HELP_MSG => 'apihelp-query+revisions+base-param-contentformat
',
843 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...