41 private const QUERY_PROP_MODULES = [
43 'class' => ApiQueryCategories::class,
46 'class' => ApiQueryCategoryInfo::class,
49 'class' => ApiQueryContributors::class,
54 'GroupPermissionsLookup',
58 'deletedrevisions' => [
59 'class' => ApiQueryDeletedRevisions::class,
62 'ContentHandlerFactory',
76 'class' => ApiQueryDuplicateFiles::class,
82 'class' => ApiQueryExternalLinks::class,
88 'class' => ApiQueryBacklinksprop::class,
95 'class' => ApiQueryImages::class,
101 'class' => ApiQueryImageInfo::class,
110 'class' => ApiQueryInfo::class,
118 'LanguageConverterFactory',
123 'IntroMessageBuilder',
124 'PreloadedContentBuilder',
131 'class' => ApiQueryLinks::class,
139 'class' => ApiQueryBacklinksprop::class,
146 'class' => ApiQueryIWLinks::class,
152 'class' => ApiQueryLangLinks::class,
160 'class' => ApiQueryPageProps::class,
166 'class' => ApiQueryBacklinksprop::class,
173 'class' => ApiQueryRevisions::class,
176 'ContentHandlerFactory',
183 'ContentTransformer',
190 'stashimageinfo' => [
191 'class' => ApiQueryStashImageInfo::class,
200 'class' => ApiQueryLinks::class,
208 'class' => ApiQueryBacklinksprop::class,
219 private const QUERY_LIST_MODULES = [
221 'class' => ApiQueryAllCategories::class,
223 'alldeletedrevisions' => [
224 'class' => ApiQueryAllDeletedRevisions::class,
227 'ContentHandlerFactory',
234 'ContentTransformer',
241 'class' => ApiQueryAllLinks::class,
250 'class' => ApiQueryAllImages::class,
253 'GroupPermissionsLookup',
257 'class' => ApiQueryAllLinks::class,
266 'class' => ApiQueryAllPages::class,
274 'class' => ApiQueryAllLinks::class,
283 'class' => ApiQueryAllRevisions::class,
286 'ContentHandlerFactory',
293 'ContentTransformer',
299 'mystashedfiles' => [
300 'class' => ApiQueryMyStashedFiles::class,
302 'alltransclusions' => [
303 'class' => ApiQueryAllLinks::class,
312 'class' => ApiQueryAllUsers::class,
316 'GroupPermissionsLookup',
319 'RecentChangeLookup',
320 'TempUserDetailsLookup',
324 'class' => ApiQueryBacklinks::class,
330 'class' => ApiQueryBlocks::class,
332 'DatabaseBlockStore',
334 'BlockRestrictionStore',
340 'categorymembers' => [
341 'class' => ApiQueryCategoryMembers::class,
347 'class' => ApiQueryCodexIcons::class,
350 'class' => ApiQueryDeletedrevs::class,
353 'RowCommentFormatter',
361 'class' => ApiQueryBacklinks::class,
367 'class' => ApiQueryExtLinksUsage::class,
373 'class' => ApiQueryFilearchive::class,
380 'class' => ApiQueryBacklinks::class,
386 'class' => ApiQueryIWBacklinks::class,
389 'class' => ApiQueryLangBacklinks::class,
392 'class' => ApiQueryLogEvents::class,
395 'RowCommentFormatter',
399 'LogFormatterFactory',
403 'class' => ApiQueryPagesWithProp::class,
406 'class' => ApiQueryPagePropNames::class,
409 'class' => ApiQueryPrefixSearch::class,
411 'SearchEngineConfig',
412 'SearchEngineFactory',
415 'protectedtitles' => [
416 'class' => ApiQueryProtectedTitles::class,
419 'RowCommentFormatter'
423 'class' => ApiQueryQueryPage::class,
425 'SpecialPageFactory',
429 'class' => ApiQueryRandom::class,
431 'ContentHandlerFactory'
435 'class' => ApiQueryRecentChanges::class,
438 'RowCommentFormatter',
441 'LogFormatterFactory',
442 'ChangesListQueryFactory',
443 'RecentChangeLookup',
447 'class' => ApiQuerySearch::class,
449 'SearchEngineConfig',
450 'SearchEngineFactory',
455 'class' => ApiQueryTags::class,
458 'ChangeTagsFormatter',
461 'trackingcategories' => [
462 'class' => ApiQueryTrackingCategories::class,
464 'TrackingCategories',
468 'class' => ApiQueryUserContribs::class,
471 'UserIdentityLookup',
481 'class' => ApiQueryUsers::class,
489 'TempUserDetailsLookup',
493 'class' => ApiQueryWatchlist::class,
496 'ChangesListQueryFactory',
497 'RowCommentFormatter',
499 'LogFormatterFactory',
500 'RecentChangeLookup',
502 'WatchlistLabelStore',
506 'class' => ApiQueryWatchlistRaw::class,
508 'WatchedItemQueryService',
519 private const QUERY_META_MODULES = [
521 'class' => ApiQueryAllMessages::class,
530 'authmanagerinfo' => [
531 'class' => ApiQueryAuthManagerInfo::class,
537 'class' => ApiQuerySiteinfo::class,
542 'LanguageConverterFactory',
550 'SpecialPageFactory',
556 'GroupPermissionsLookup',
562 'class' => ApiQueryUserInfo::class,
564 'TalkPageNotificationManager',
569 'WatchlistLabelStore',
573 'class' => ApiQueryFileRepoInfo::class,
579 'class' => ApiQueryTokens::class,
582 'class' => ApiQueryLanguageinfo::class,
587 'LanguageConverterFactory',
605 ObjectFactory $objectFactory,
610 parent::__construct( $main, $action );
619 $this->mModuleMgr->addModules( self::QUERY_PROP_MODULES,
'prop' );
621 $this->mModuleMgr->addModules( self::QUERY_LIST_MODULES,
'list' );
623 $this->mModuleMgr->addModules( self::QUERY_META_MODULES,
'meta' );
626 $this->
getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
637 return $this->mModuleMgr;
645 return $this->mPageSet;
657 $this->
getMain()->createPrinterByName(
'xml' ) );
678 $this->instantiateModules( $allModules,
'prop' );
679 $propModules = array_keys( $allModules );
680 $this->instantiateModules( $allModules,
'list' );
681 $this->instantiateModules( $allModules,
'meta' );
687 $modules = $continuationManager->getRunModules();
688 '@phan-var ApiQueryBase[] $modules';
691 $message =
'hookaborted';
692 if ( !$this->
getHookRunner()->onApiQueryCheckCanExecute( $modules, $this->
getUser(), $message ) ) {
698 if ( !$continuationManager->isGeneratorDone() ) {
701 foreach ( $modules as $module ) {
703 $timer = $statsFactory->getTiming(
'api_query_extraDataTiming_seconds' )
704 ->setLabel(
'module', $module->getModuleName() )
706 $module->requestExtraData( $this->mPageSet );
710 $this->mPageSet->execute();
712 $this->outputGeneralPageInfo();
714 $this->mPageSet->executeDryRun();
717 $cacheMode = $this->mPageSet->getCacheMode();
720 foreach ( $modules as $module ) {
722 $timer = $statsFactory->getTiming(
'api_query_executeTiming_seconds' )
723 ->setLabel(
'module', $module->getModuleName() )
726 $params = $module->extractRequestParams();
728 $cacheMode, $module->getCacheMode( $params ) );
729 $scope = LoggerFactory::getContext()->addScoped( [
730 'context.api_query_module_name' => $module->getModuleName(),
736 $module->recordUnifiedMetrics();
737 }
catch ( \Throwable $e ) {
739 $module->recordUnifiedMetrics(
741 'status' =>
'error_' . $e->getCode(),
747 ScopedCallback::consume( $scope );
755 $this->
getMain()->setCacheMode( $cacheMode );
759 if ( $this->mParams[
'rawcontinue'] ) {
760 $data = $continuationManager->getRawNonContinuation();
762 $this->
getResult()->addValue(
null,
'query-noncontinue', $data,
765 $data = $continuationManager->getRawContinuation();
767 $this->
getResult()->addValue(
null,
'query-continue', $data,
771 $continuationManager->setContinuationIntoResult( $this->
getResult() );
785 if ( $modCacheMode ===
'anon-public-user-private' ) {
786 if ( $cacheMode !==
'private' ) {
787 $cacheMode =
'anon-public-user-private';
789 } elseif ( $modCacheMode ===
'public' ) {
792 $cacheMode =
'private';
803 private function instantiateModules( &$modules, $param ) {
804 $wasPosted = $this->
getRequest()->wasPosted();
805 if ( isset( $this->mParams[$param] ) ) {
806 foreach ( $this->mParams[$param] as $moduleName ) {
807 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
808 if ( $instance ===
null ) {
811 if ( !$wasPosted && $instance->mustBePosted() ) {
815 if ( !array_key_exists( $moduleName, $modules ) ) {
816 $modules[$moduleName] = $instance;
827 private function outputGeneralPageInfo() {
835 $values = $pageSet->getNormalizedTitlesAsResult( $result );
838 $fit = $fit && $result->addValue(
'query',
'normalized', $values );
840 $values = $pageSet->getConvertedTitlesAsResult( $result );
842 $fit = $fit && $result->addValue(
'query',
'converted', $values );
844 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams[
'iwurl'] );
846 $fit = $fit && $result->addValue(
'query',
'interwiki', $values );
848 $values = $pageSet->getRedirectTitlesAsResult( $result );
850 $fit = $fit && $result->addValue(
'query',
'redirects', $values );
852 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
854 $fit = $fit && $result->addValue(
'query',
'badrevids', $values );
862 foreach ( $pageSet->getMissingPages() as $fakeId => $page ) {
864 'ns' => $page->getNamespace(),
865 'title' => $this->titleFormatter->getPrefixedText( $page ),
868 $title = $this->titleFactory->newFromPageIdentity( $page );
869 if ( $title->isKnown() ) {
870 $vals[
'known'] =
true;
872 $pages[$fakeId] = $vals;
875 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
876 $pages[$fakeId] = $data + [
'invalid' => true ];
879 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
887 foreach ( $pageSet->getSpecialPages() as $fakeId => $page ) {
889 'ns' => $page->getNamespace(),
890 'title' => $this->titleFormatter->getPrefixedText( $page ),
893 $title = $this->titleFactory->newFromPageReference( $page );
894 if ( !$title->isKnown() ) {
895 $vals[
'missing'] =
true;
897 $pages[$fakeId] = $vals;
901 foreach ( $pageSet->getGoodPages() as $pageid => $page ) {
904 'ns' => $page->getNamespace(),
905 'title' => $this->titleFormatter->getPrefixedText( $page ),
909 if ( count( $pages ) ) {
910 $pageSet->populateGeneratorData( $pages );
913 if ( $this->mParams[
'indexpageids'] ) {
916 $pageIDs = array_map(
'strval', $pageIDs );
918 $fit = $fit && $result->addValue(
'query',
'pageids', $pageIDs );
922 $fit = $fit && $result->addValue(
'query',
'pages', $pages );
926 $this->
dieWithError(
'apierror-badconfig-resulttoosmall',
'badconfig' );
929 if ( $this->mParams[
'export'] ) {
930 $this->doExport( $pageSet, $result );
938 private function doExport( $pageSet, $result ) {
940 $titles = $pageSet->getGoodPages();
941 if ( count( $titles ) ) {
943 foreach ( $titles as $title ) {
944 if ( $this->
getAuthority()->authorizeRead(
'read', $title ) ) {
945 $exportTitles[] = $title;
950 $exporter = $this->wikiExporterFactory->getWikiExporter( $this->
getDB() );
951 $sink =
new DumpStringOutput;
952 $exporter->setOutputSink( $sink );
953 $exporter->setSchemaVersion( $this->mParams[
'exportschema'] );
954 $exporter->openStream();
955 foreach ( $exportTitles as $title ) {
956 $exporter->pageByTitle( $title );
958 $exporter->closeStream();
963 if ( $this->mParams[
'exportnowrap'] ) {
979 ParamValidator::PARAM_ISMULTI =>
true,
980 ParamValidator::PARAM_TYPE =>
'submodule',
983 ParamValidator::PARAM_ISMULTI =>
true,
984 ParamValidator::PARAM_TYPE =>
'submodule',
987 ParamValidator::PARAM_ISMULTI =>
true,
988 ParamValidator::PARAM_TYPE =>
'submodule',
990 'indexpageids' =>
false,
992 'exportnowrap' =>
false,
1001 'rawcontinue' =>
false,
1004 $result += $this->
getPageSet()->getFinalParams( $flags );
1018 $allowedParams = [
'rawcontinue' => 1,
'indexpageids' => 1 ];
1022 $needed = $param ===
'meta';
1023 if ( !isset( $allowedParams[$param] ) &&
$request->getCheck( $param ) !== $needed ) {
1031 $this->instantiateModules( $modules,
'meta' );
1032 foreach ( $modules as $module ) {
1033 if ( $module->isReadMode() ) {
1046 $this->instantiateModules( $modules,
'list' );
1047 $this->instantiateModules( $modules,
'meta' );
1048 $this->instantiateModules( $modules,
'prop' );
1049 foreach ( $modules as $module ) {
1050 if ( $module->isWriteMode() ) {
1060 $title = Title::newMainPage()->getPrefixedText();
1061 $mp = rawurlencode( $title );
1064 'action=query&prop=revisions&meta=siteinfo&' .
1065 "titles={$mp}&rvprop=user|comment&continue="
1066 =>
'apihelp-query-example-revisions',
1067 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
1068 =>
'apihelp-query-example-allpages',
1075 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
1076 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
1077 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
1078 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',