MediaWiki master
ApiQuery.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
32use WikiExporter;
33use Wikimedia\ObjectFactory\ObjectFactory;
36
48class ApiQuery extends ApiBase {
49
53 private const QUERY_PROP_MODULES = [
54 'categories' => [
55 'class' => ApiQueryCategories::class,
56 ],
57 'categoryinfo' => [
58 'class' => ApiQueryCategoryInfo::class,
59 ],
60 'contributors' => [
61 'class' => ApiQueryContributors::class,
62 'services' => [
63 'RevisionStore',
64 'ActorMigration',
65 'UserGroupManager',
66 'GroupPermissionsLookup',
67 'TempUserConfig'
68 ]
69 ],
70 'deletedrevisions' => [
71 'class' => ApiQueryDeletedRevisions::class,
72 'services' => [
73 'RevisionStore',
74 'ContentHandlerFactory',
75 'ParserFactory',
76 'SlotRoleRegistry',
77 'ChangeTagDefStore',
78 'ChangeTagsStore',
79 'LinkBatchFactory',
80 'ContentRenderer',
81 'ContentTransformer',
82 'CommentFormatter',
83 'TempUserCreator',
84 'UserFactory',
85 ]
86 ],
87 'duplicatefiles' => [
88 'class' => ApiQueryDuplicateFiles::class,
89 'services' => [
90 'RepoGroup',
91 ]
92 ],
93 'extlinks' => [
94 'class' => ApiQueryExternalLinks::class,
95 'services' => [
96 'UrlUtils',
97 ],
98 ],
99 'fileusage' => [
100 'class' => ApiQueryBacklinksprop::class,
101 'services' => [
102 // Same as for linkshere, redirects, transcludedin
103 'LinksMigration',
104 ]
105 ],
106 'images' => [
107 'class' => ApiQueryImages::class,
108 ],
109 'imageinfo' => [
110 'class' => ApiQueryImageInfo::class,
111 'services' => [
112 // Same as for stashimageinfo
113 'RepoGroup',
114 'ContentLanguage',
115 'BadFileLookup',
116 ]
117 ],
118 'info' => [
119 'class' => ApiQueryInfo::class,
120 'services' => [
121 'ContentLanguage',
122 'LinkBatchFactory',
123 'NamespaceInfo',
124 'TitleFactory',
125 'TitleFormatter',
126 'WatchedItemStore',
127 'LanguageConverterFactory',
128 'RestrictionStore',
129 'LinksMigration',
130 'TempUserCreator',
131 'UserFactory',
132 'IntroMessageBuilder',
133 'PreloadedContentBuilder',
134 'RevisionLookup',
135 'UrlUtils',
136 ],
137 ],
138 'links' => [
139 'class' => ApiQueryLinks::class,
140 'services' => [
141 // Same as for templates
142 'LinkBatchFactory',
143 'LinksMigration',
144 ]
145 ],
146 'linkshere' => [
147 'class' => ApiQueryBacklinksprop::class,
148 'services' => [
149 // Same as for fileusage, redirects, transcludedin
150 'LinksMigration',
151 ]
152 ],
153 'iwlinks' => [
154 'class' => ApiQueryIWLinks::class,
155 'services' => [
156 'UrlUtils',
157 ]
158 ],
159 'langlinks' => [
160 'class' => ApiQueryLangLinks::class,
161 'services' => [
162 'LanguageNameUtils',
163 'ContentLanguage',
164 'UrlUtils',
165 ]
166 ],
167 'pageprops' => [
168 'class' => ApiQueryPageProps::class,
169 'services' => [
170 'PageProps',
171 ]
172 ],
173 'redirects' => [
174 'class' => ApiQueryBacklinksprop::class,
175 'services' => [
176 // Same as for fileusage, linkshere, transcludedin
177 'LinksMigration',
178 ]
179 ],
180 'revisions' => [
181 'class' => ApiQueryRevisions::class,
182 'services' => [
183 'RevisionStore',
184 'ContentHandlerFactory',
185 'ParserFactory',
186 'SlotRoleRegistry',
187 'ChangeTagDefStore',
188 'ChangeTagsStore',
189 'ActorMigration',
190 'ContentRenderer',
191 'ContentTransformer',
192 'CommentFormatter',
193 'TempUserCreator',
194 'UserFactory',
195 'TitleFormatter',
196 ]
197 ],
198 'stashimageinfo' => [
199 'class' => ApiQueryStashImageInfo::class,
200 'services' => [
201 // Same as for imageinfo
202 'RepoGroup',
203 'ContentLanguage',
204 'BadFileLookup',
205 ]
206 ],
207 'templates' => [
208 'class' => ApiQueryLinks::class,
209 'services' => [
210 // Same as for links
211 'LinkBatchFactory',
212 'LinksMigration',
213 ]
214 ],
215 'transcludedin' => [
216 'class' => ApiQueryBacklinksprop::class,
217 'services' => [
218 // Same as for fileusage, linkshere, redirects
219 'LinksMigration',
220 ]
221 ],
222 ];
223
227 private const QUERY_LIST_MODULES = [
228 'allcategories' => [
229 'class' => ApiQueryAllCategories::class,
230 ],
231 'alldeletedrevisions' => [
232 'class' => ApiQueryAllDeletedRevisions::class,
233 'services' => [
234 'RevisionStore',
235 'ContentHandlerFactory',
236 'ParserFactory',
237 'SlotRoleRegistry',
238 'ChangeTagDefStore',
239 'ChangeTagsStore',
240 'NamespaceInfo',
241 'ContentRenderer',
242 'ContentTransformer',
243 'CommentFormatter',
244 'TempUserCreator',
245 'UserFactory',
246 ]
247 ],
248 'allfileusages' => [
249 'class' => ApiQueryAllLinks::class,
250 'services' => [
251 // Same as for alllinks, allredirects, alltransclusions
252 'NamespaceInfo',
253 'GenderCache',
254 'LinksMigration',
255 ]
256 ],
257 'allimages' => [
258 'class' => ApiQueryAllImages::class,
259 'services' => [
260 'RepoGroup',
261 'GroupPermissionsLookup',
262 ]
263 ],
264 'alllinks' => [
265 'class' => ApiQueryAllLinks::class,
266 'services' => [
267 // Same as for allfileusages, allredirects, alltransclusions
268 'NamespaceInfo',
269 'GenderCache',
270 'LinksMigration',
271 ]
272 ],
273 'allpages' => [
274 'class' => ApiQueryAllPages::class,
275 'services' => [
276 'NamespaceInfo',
277 'GenderCache',
278 'RestrictionStore',
279 ]
280 ],
281 'allredirects' => [
282 'class' => ApiQueryAllLinks::class,
283 'services' => [
284 // Same as for allfileusages, alllinks, alltransclusions
285 'NamespaceInfo',
286 'GenderCache',
287 'LinksMigration',
288 ]
289 ],
290 'allrevisions' => [
291 'class' => ApiQueryAllRevisions::class,
292 'services' => [
293 'RevisionStore',
294 'ContentHandlerFactory',
295 'ParserFactory',
296 'SlotRoleRegistry',
297 'ActorMigration',
298 'NamespaceInfo',
299 'ChangeTagsStore',
300 'ContentRenderer',
301 'ContentTransformer',
302 'CommentFormatter',
303 'TempUserCreator',
304 'UserFactory',
305 ]
306 ],
307 'mystashedfiles' => [
308 'class' => ApiQueryMyStashedFiles::class,
309 ],
310 'alltransclusions' => [
311 'class' => ApiQueryAllLinks::class,
312 'services' => [
313 // Same as for allfileusages, alllinks, allredirects
314 'NamespaceInfo',
315 'GenderCache',
316 'LinksMigration',
317 ]
318 ],
319 'allusers' => [
320 'class' => ApiQueryAllUsers::class,
321 'services' => [
322 'UserFactory',
323 'UserGroupManager',
324 'GroupPermissionsLookup',
325 'ContentLanguage',
326 'TempUserConfig',
327 ]
328 ],
329 'backlinks' => [
330 'class' => ApiQueryBacklinks::class,
331 'services' => [
332 'LinksMigration',
333 ]
334 ],
335 'blocks' => [
336 'class' => ApiQueryBlocks::class,
337 'services' => [
338 'DatabaseBlockStore',
339 'BlockActionInfo',
340 'BlockRestrictionStore',
341 'CommentStore',
342 'HideUserUtils',
343 ],
344 ],
345 'categorymembers' => [
346 'class' => ApiQueryCategoryMembers::class,
347 'services' => [
348 'CollationFactory',
349 ]
350 ],
351 'deletedrevs' => [
352 'class' => ApiQueryDeletedrevs::class,
353 'services' => [
354 'CommentStore',
355 'RowCommentFormatter',
356 'RevisionStore',
357 'ChangeTagDefStore',
358 'ChangeTagsStore',
359 'LinkBatchFactory',
360 ],
361 ],
362 'embeddedin' => [
363 'class' => ApiQueryBacklinks::class,
364 'services' => [
365 'LinksMigration',
366 ]
367 ],
368 'exturlusage' => [
369 'class' => ApiQueryExtLinksUsage::class,
370 'services' => [
371 'UrlUtils',
372 ],
373 ],
374 'filearchive' => [
375 'class' => ApiQueryFilearchive::class,
376 'services' => [
377 'CommentStore',
378 'CommentFormatter',
379 ],
380 ],
381 'imageusage' => [
382 'class' => ApiQueryBacklinks::class,
383 'services' => [
384 'LinksMigration',
385 ]
386 ],
387 'iwbacklinks' => [
388 'class' => ApiQueryIWBacklinks::class,
389 ],
390 'langbacklinks' => [
391 'class' => ApiQueryLangBacklinks::class,
392 ],
393 'logevents' => [
394 'class' => ApiQueryLogEvents::class,
395 'services' => [
396 'CommentStore',
397 'RowCommentFormatter',
398 'ChangeTagDefStore',
399 'ChangeTagsStore',
400 'UserNameUtils',
401 'LogFormatterFactory',
402 ],
403 ],
404 'pageswithprop' => [
405 'class' => ApiQueryPagesWithProp::class,
406 ],
407 'pagepropnames' => [
408 'class' => ApiQueryPagePropNames::class,
409 ],
410 'prefixsearch' => [
411 'class' => ApiQueryPrefixSearch::class,
412 'services' => [
413 'SearchEngineConfig',
414 'SearchEngineFactory',
415 ],
416 ],
417 'protectedtitles' => [
418 'class' => ApiQueryProtectedTitles::class,
419 'services' => [
420 'CommentStore',
421 'RowCommentFormatter'
422 ],
423 ],
424 'querypage' => [
425 'class' => ApiQueryQueryPage::class,
426 'services' => [
427 'SpecialPageFactory',
428 ]
429 ],
430 'random' => [
431 'class' => ApiQueryRandom::class,
432 ],
433 'recentchanges' => [
434 'class' => ApiQueryRecentChanges::class,
435 'services' => [
436 'CommentStore',
437 'RowCommentFormatter',
438 'ChangeTagDefStore',
439 'ChangeTagsStore',
440 'SlotRoleStore',
441 'SlotRoleRegistry',
442 'UserNameUtils',
443 'TempUserConfig',
444 'LogFormatterFactory',
445 ],
446 ],
447 'search' => [
448 'class' => ApiQuerySearch::class,
449 'services' => [
450 'SearchEngineConfig',
451 'SearchEngineFactory',
452 'TitleMatcher',
453 ],
454 ],
455 'tags' => [
456 'class' => ApiQueryTags::class,
457 'services' => [
458 'ChangeTagsStore',
459 ]
460 ],
461 'usercontribs' => [
462 'class' => ApiQueryUserContribs::class,
463 'services' => [
464 'CommentStore',
465 'UserIdentityLookup',
466 'UserNameUtils',
467 'RevisionStore',
468 'ChangeTagDefStore',
469 'ChangeTagsStore',
470 'ActorMigration',
471 'CommentFormatter',
472 ],
473 ],
474 'users' => [
475 'class' => ApiQueryUsers::class,
476 'services' => [
477 'UserNameUtils',
478 'UserFactory',
479 'UserGroupManager',
480 'GenderCache',
481 'AuthManager',
482 ],
483 ],
484 'watchlist' => [
485 'class' => ApiQueryWatchlist::class,
486 'services' => [
487 'CommentStore',
488 'WatchedItemQueryService',
489 'ContentLanguage',
490 'NamespaceInfo',
491 'GenderCache',
492 'CommentFormatter',
493 'TempUserConfig',
494 'LogFormatterFactory',
495 ],
496 ],
497 'watchlistraw' => [
498 'class' => ApiQueryWatchlistRaw::class,
499 'services' => [
500 'WatchedItemQueryService',
501 'ContentLanguage',
502 'NamespaceInfo',
503 'GenderCache',
504 ]
505 ],
506 ];
507
511 private const QUERY_META_MODULES = [
512 'allmessages' => [
513 'class' => ApiQueryAllMessages::class,
514 'services' => [
515 'ContentLanguage',
516 'LanguageFactory',
517 'LanguageNameUtils',
518 'LocalisationCache',
519 'MessageCache',
520 ]
521 ],
522 'authmanagerinfo' => [
523 'class' => ApiQueryAuthManagerInfo::class,
524 'services' => [
525 'AuthManager',
526 ]
527 ],
528 'siteinfo' => [
529 'class' => ApiQuerySiteinfo::class,
530 'services' => [
531 'UserOptionsLookup',
532 'UserGroupManager',
533 'HookContainer',
534 'LanguageConverterFactory',
535 'LanguageFactory',
536 'LanguageNameUtils',
537 'ContentLanguage',
538 'NamespaceInfo',
539 'InterwikiLookup',
540 'ParserFactory',
541 'MagicWordFactory',
542 'SpecialPageFactory',
543 'SkinFactory',
544 'DBLoadBalancer',
545 'ReadOnlyMode',
546 'UrlUtils',
547 'TempUserConfig'
548 ]
549 ],
550 'userinfo' => [
551 'class' => ApiQueryUserInfo::class,
552 'services' => [
553 'TalkPageNotificationManager',
554 'WatchedItemStore',
555 'UserEditTracker',
556 'UserOptionsLookup',
557 'UserGroupManager',
558 ]
559 ],
560 'filerepoinfo' => [
561 'class' => ApiQueryFileRepoInfo::class,
562 'services' => [
563 'RepoGroup',
564 ]
565 ],
566 'tokens' => [
567 'class' => ApiQueryTokens::class,
568 ],
569 'languageinfo' => [
570 'class' => ApiQueryLanguageinfo::class,
571 'services' => [
572 'LanguageFactory',
573 'LanguageNameUtils',
574 'LanguageFallback',
575 'LanguageConverterFactory',
576 ],
577 ],
578 ];
579
583 private $mPageSet;
584
586 private $mParams;
588 private $mModuleMgr;
589
590 private WikiExporterFactory $wikiExporterFactory;
591 private TitleFormatter $titleFormatter;
592 private TitleFactory $titleFactory;
593
594 public function __construct(
595 ApiMain $main,
596 string $action,
597 ObjectFactory $objectFactory,
598 WikiExporterFactory $wikiExporterFactory,
599 TitleFormatter $titleFormatter,
600 TitleFactory $titleFactory
601 ) {
602 parent::__construct( $main, $action );
603
604 $this->mModuleMgr = new ApiModuleManager(
605 $this,
606 $objectFactory
607 );
608
609 // Allow custom modules to be added in LocalSettings.php
610 $config = $this->getConfig();
611 $this->mModuleMgr->addModules( self::QUERY_PROP_MODULES, 'prop' );
612 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIPropModules ), 'prop' );
613 $this->mModuleMgr->addModules( self::QUERY_LIST_MODULES, 'list' );
614 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIListModules ), 'list' );
615 $this->mModuleMgr->addModules( self::QUERY_META_MODULES, 'meta' );
616 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIMetaModules ), 'meta' );
617
618 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
619
620 // Create PageSet that will process titles/pageids/revids/generator
621 $this->mPageSet = new ApiPageSet( $this );
622 $this->wikiExporterFactory = $wikiExporterFactory;
623 $this->titleFormatter = $titleFormatter;
624 $this->titleFactory = $titleFactory;
625 }
626
631 public function getModuleManager() {
632 return $this->mModuleMgr;
633 }
634
639 public function getPageSet() {
640 return $this->mPageSet;
641 }
642
646 public function getCustomPrinter() {
647 // If &exportnowrap is set, use the raw formatter
648 if ( $this->getParameter( 'export' ) &&
649 $this->getParameter( 'exportnowrap' )
650 ) {
651 return new ApiFormatRaw( $this->getMain(),
652 $this->getMain()->createPrinterByName( 'xml' ) );
653 } else {
654 return null;
655 }
656 }
657
668 public function execute() {
669 $this->mParams = $this->extractRequestParams();
670
671 // Instantiate requested modules
672 $allModules = [];
673 $this->instantiateModules( $allModules, 'prop' );
674 $propModules = array_keys( $allModules );
675 $this->instantiateModules( $allModules, 'list' );
676 $this->instantiateModules( $allModules, 'meta' );
677
678 // Filter modules based on continue parameter
679 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
680 $this->setContinuationManager( $continuationManager );
682 $modules = $continuationManager->getRunModules();
683 '@phan-var ApiQueryBase[] $modules';
684
685 // Allow extensions to stop execution for arbitrary reasons.
686 $message = 'hookaborted';
687 if ( !$this->getHookRunner()->onApiQueryCheckCanExecute( $modules, $this->getUser(), $message ) ) {
688 $this->dieWithError( $message );
689 }
690
691 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
692
693 if ( !$continuationManager->isGeneratorDone() ) {
694 // Query modules may optimize data requests through the $this->getPageSet()
695 // object by adding extra fields from the page table.
696 foreach ( $modules as $module ) {
697 // Augment api-query.$module.executeTiming metric with timings for requestExtraData()
698 $timer = $statsFactory->getTiming( 'api_query_extraDataTiming_seconds' )
699 ->setLabel( 'module', $module->getModuleName() )
700 ->copyToStatsdAt( 'api-query.' . $module->getModuleName() . '.extraDataTiming' );
701 $timer->start();
702 $module->requestExtraData( $this->mPageSet );
703 $timer->stop();
704 }
705 // Populate page/revision information
706 $this->mPageSet->execute();
707 // Record page information (title, namespace, if exists, etc)
708 $this->outputGeneralPageInfo();
709 } else {
710 $this->mPageSet->executeDryRun();
711 }
712
713 $cacheMode = $this->mPageSet->getCacheMode();
714
715 // Execute all unfinished modules
716 foreach ( $modules as $module ) {
717 // Break down of the api.query.executeTiming metric by query module.
718 $timer = $statsFactory->getTiming( 'api_query_executeTiming_seconds' )
719 ->setLabel( 'module', $module->getModuleName() )
720 ->copyToStatsdAt( 'api-query.' . $module->getModuleName() . '.executeTiming' );
721 $timer->start();
722
723 $params = $module->extractRequestParams();
724 $cacheMode = $this->mergeCacheMode(
725 $cacheMode, $module->getCacheMode( $params ) );
726 $module->execute();
727
728 $timer->stop();
729
730 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
731 }
732
733 // Set the cache mode
734 $this->getMain()->setCacheMode( $cacheMode );
735
736 // Write the continuation data into the result
737 $this->setContinuationManager( null );
738 if ( $this->mParams['rawcontinue'] ) {
739 $data = $continuationManager->getRawNonContinuation();
740 if ( $data ) {
741 $this->getResult()->addValue( null, 'query-noncontinue', $data,
743 }
744 $data = $continuationManager->getRawContinuation();
745 if ( $data ) {
746 $this->getResult()->addValue( null, 'query-continue', $data,
748 }
749 } else {
750 $continuationManager->setContinuationIntoResult( $this->getResult() );
751 }
752 }
753
763 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
764 if ( $modCacheMode === 'anon-public-user-private' ) {
765 if ( $cacheMode !== 'private' ) {
766 $cacheMode = 'anon-public-user-private';
767 }
768 } elseif ( $modCacheMode === 'public' ) {
769 // do nothing, if it's public already it will stay public
770 } else {
771 $cacheMode = 'private';
772 }
773
774 return $cacheMode;
775 }
776
782 private function instantiateModules( &$modules, $param ) {
783 $wasPosted = $this->getRequest()->wasPosted();
784 if ( isset( $this->mParams[$param] ) ) {
785 foreach ( $this->mParams[$param] as $moduleName ) {
786 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
787 if ( $instance === null ) {
788 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
789 }
790 if ( !$wasPosted && $instance->mustBePosted() ) {
791 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
792 }
793 // Ignore duplicates. TODO 2.0: die()?
794 if ( !array_key_exists( $moduleName, $modules ) ) {
795 $modules[$moduleName] = $instance;
796 }
797 }
798 }
799 }
800
806 private function outputGeneralPageInfo() {
807 $pageSet = $this->getPageSet();
808 $result = $this->getResult();
809
810 // We can't really handle max-result-size failure here, but we need to
811 // check anyway in case someone set the limit stupidly low.
812 $fit = true;
813
814 $values = $pageSet->getNormalizedTitlesAsResult( $result );
815 if ( $values ) {
816 // @phan-suppress-next-line PhanRedundantCondition
817 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
818 }
819 $values = $pageSet->getConvertedTitlesAsResult( $result );
820 if ( $values ) {
821 $fit = $fit && $result->addValue( 'query', 'converted', $values );
822 }
823 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
824 if ( $values ) {
825 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
826 }
827 $values = $pageSet->getRedirectTitlesAsResult( $result );
828 if ( $values ) {
829 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
830 }
831 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
832 if ( $values ) {
833 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
834 }
835
836 // Page elements
837 // Cannot use ApiPageSet::getInvalidTitlesAndRevisions, it does not set $fakeId
838 $pages = [];
839
840 // Report any missing titles
841 foreach ( $pageSet->getMissingPages() as $fakeId => $page ) {
842 $vals = [];
843 $vals['ns'] = $page->getNamespace();
844 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
845 $vals['missing'] = true;
846 $title = $this->titleFactory->newFromPageIdentity( $page );
847 if ( $title->isKnown() ) {
848 $vals['known'] = true;
849 }
850 $pages[$fakeId] = $vals;
851 }
852 // Report any invalid titles
853 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
854 $pages[$fakeId] = $data + [ 'invalid' => true ];
855 }
856 // Report any missing page ids
857 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
858 $pages[$pageid] = [
859 'pageid' => $pageid,
860 'missing' => true,
861 ];
862 }
863 // Report special pages
865 foreach ( $pageSet->getSpecialPages() as $fakeId => $page ) {
866 $vals = [];
867 $vals['ns'] = $page->getNamespace();
868 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
869 $vals['special'] = true;
870 $title = $this->titleFactory->newFromPageReference( $page );
871 if ( !$title->isKnown() ) {
872 $vals['missing'] = true;
873 }
874 $pages[$fakeId] = $vals;
875 }
876
877 // Output general page information for found titles
878 foreach ( $pageSet->getGoodPages() as $pageid => $page ) {
879 $vals = [];
880 $vals['pageid'] = $pageid;
881 $vals['ns'] = $page->getNamespace();
882 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
883 $pages[$pageid] = $vals;
884 }
885
886 if ( count( $pages ) ) {
887 $pageSet->populateGeneratorData( $pages );
888 ApiResult::setArrayType( $pages, 'BCarray' );
889
890 if ( $this->mParams['indexpageids'] ) {
891 $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
892 // json treats all map keys as strings - converting to match
893 $pageIDs = array_map( 'strval', $pageIDs );
894 ApiResult::setIndexedTagName( $pageIDs, 'id' );
895 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
896 }
897
898 ApiResult::setIndexedTagName( $pages, 'page' );
899 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
900 }
901
902 if ( !$fit ) {
903 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
904 }
905
906 if ( $this->mParams['export'] ) {
907 $this->doExport( $pageSet, $result );
908 }
909 }
910
915 private function doExport( $pageSet, $result ) {
916 $exportTitles = [];
917 $titles = $pageSet->getGoodPages();
918 if ( count( $titles ) ) {
920 foreach ( $titles as $title ) {
921 if ( $this->getAuthority()->authorizeRead( 'read', $title ) ) {
922 $exportTitles[] = $title;
923 }
924 }
925 }
926
927 $exporter = $this->wikiExporterFactory->getWikiExporter( $this->getDB() );
928 $sink = new DumpStringOutput;
929 $exporter->setOutputSink( $sink );
930 $exporter->setSchemaVersion( $this->mParams['exportschema'] );
931 $exporter->openStream();
932 foreach ( $exportTitles as $title ) {
933 $exporter->pageByTitle( $title );
934 }
935 $exporter->closeStream();
936
937 // Don't check the size of exported stuff
938 // It's not continuable, so it would cause more
939 // problems than it'd solve
940 if ( $this->mParams['exportnowrap'] ) {
941 $result->reset();
942 // Raw formatter will handle this
943 $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
944 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
945 $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
946 } else {
947 $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
948 $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
949 }
950 }
951
952 public function getAllowedParams( $flags = 0 ) {
953 $result = [
954 'prop' => [
955 ParamValidator::PARAM_ISMULTI => true,
956 ParamValidator::PARAM_TYPE => 'submodule',
957 ],
958 'list' => [
959 ParamValidator::PARAM_ISMULTI => true,
960 ParamValidator::PARAM_TYPE => 'submodule',
961 ],
962 'meta' => [
963 ParamValidator::PARAM_ISMULTI => true,
964 ParamValidator::PARAM_TYPE => 'submodule',
965 ],
966 'indexpageids' => false,
967 'export' => false,
968 'exportnowrap' => false,
969 'exportschema' => [
970 ParamValidator::PARAM_DEFAULT => WikiExporter::schemaVersion(),
971 ParamValidator::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
972 ],
973 'iwurl' => false,
974 'continue' => [
975 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
976 ],
977 'rawcontinue' => false,
978 ];
979 if ( $flags ) {
980 $result += $this->getPageSet()->getFinalParams( $flags );
981 }
982
983 return $result;
984 }
985
986 public function isReadMode() {
987 // We need to make an exception for certain meta modules that should be
988 // accessible even without the 'read' right. Restrict the exception as
989 // much as possible: no other modules allowed, and no pageset
990 // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
991 // parameters since frameworks might add these unconditionally and they
992 // can't expose anything here.
993 $allowedParams = [ 'rawcontinue' => 1, 'indexpageids' => 1 ];
994 $this->mParams = $this->extractRequestParams();
995 $request = $this->getRequest();
996 foreach ( $this->mParams + $this->getPageSet()->extractRequestParams() as $param => $value ) {
997 $needed = $param === 'meta';
998 if ( !isset( $allowedParams[$param] ) && $request->getCheck( $param ) !== $needed ) {
999 return true;
1000 }
1001 }
1002
1003 // Ask each module if it requires read mode. Any true => this returns
1004 // true.
1005 $modules = [];
1006 $this->instantiateModules( $modules, 'meta' );
1007 foreach ( $modules as $module ) {
1008 if ( $module->isReadMode() ) {
1009 return true;
1010 }
1011 }
1012
1013 return false;
1014 }
1015
1016 public function isWriteMode() {
1017 // Ask each module if it requires write mode. If any require write mode this returns true.
1018 $modules = [];
1019 $this->mParams = $this->extractRequestParams();
1020 $this->instantiateModules( $modules, 'list' );
1021 $this->instantiateModules( $modules, 'meta' );
1022 $this->instantiateModules( $modules, 'prop' );
1023 foreach ( $modules as $module ) {
1024 if ( $module->isWriteMode() ) {
1025 return true;
1026 }
1027 }
1028
1029 return false;
1030 }
1031
1032 protected function getExamplesMessages() {
1033 $title = Title::newMainPage()->getPrefixedText();
1034 $mp = rawurlencode( $title );
1035
1036 return [
1037 'action=query&prop=revisions&meta=siteinfo&' .
1038 "titles={$mp}&rvprop=user|comment&continue="
1039 => 'apihelp-query-example-revisions',
1040 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
1041 => 'apihelp-query-example-allpages',
1042 ];
1043 }
1044
1045 public function getHelpUrls() {
1046 return [
1047 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
1048 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
1049 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
1050 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
1051 ];
1052 }
1053}
1054
1056class_alias( ApiQuery::class, 'ApiQuery' );
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1565
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:795
getMain()
Get the main module.
Definition ApiBase.php:589
getResult()
Get the result object.
Definition ApiBase.php:710
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:734
setContinuationManager(?ApiContinuationManager $manager=null)
Definition ApiBase.php:757
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1808
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:851
dieWithErrorOrDebug( $msg, $code=null, $data=null, $httpCode=null)
Will only set a warning instead of failing if the global $wgDebugAPI is set to true.
Definition ApiBase.php:1738
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:973
Formatter that spits out anything you like with any desired MIME type.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:78
This class holds a list of modules and handles instantiation.
This class contains a list of pages that the client has requested.
This is the main query class.
Definition ApiQuery.php:48
mergeCacheMode( $cacheMode, $modCacheMode)
Update a cache mode string, applying the cache mode of a new module to it.
Definition ApiQuery.php:763
isReadMode()
Indicates whether this module requires read rights.
Definition ApiQuery.php:986
execute()
Query execution happens in the following steps: #1 Create a PageSet object with any pages requested b...
Definition ApiQuery.php:668
getAllowedParams( $flags=0)
Definition ApiQuery.php:952
__construct(ApiMain $main, string $action, ObjectFactory $objectFactory, WikiExporterFactory $wikiExporterFactory, TitleFormatter $titleFormatter, TitleFactory $titleFactory)
Definition ApiQuery.php:594
isWriteMode()
Indicates whether this module requires write access to the wiki.
getExamplesMessages()
Returns usage examples for this module.
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:631
getHelpUrls()
Return links to more detailed help pages about the module.
getPageSet()
Gets the set of pages the user has requested (or generated)
Definition ApiQuery.php:639
const ADD_ON_TOP
For addValue(), setValue() and similar functions, if the value does not exist, add it as the first el...
Definition ApiResult.php:57
const META_BC_SUBELEMENTS
Key for the 'BC subelements' metadata item.
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
Definition ApiResult.php:66
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Factory service for WikiExporter instances.
A class containing constants representing the names of configuration variables.
const APIListModules
Name constant for the APIListModules setting, for use with Config::get()
const APIPropModules
Name constant for the APIPropModules setting, for use with Config::get()
const APIMetaModules
Name constant for the APIMetaModules setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Creates Title objects.
Represents a title within MediaWiki.
Definition Title.php:78
static schemaVersion()
Returns the default export schema version, as defined by the XmlDumpSchemaVersion setting.
Service for formatting and validating API parameters.
static string[] $supportedSchemas
the schema versions supported for output @final
A title formatter service for MediaWiki.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:30
A helper class for throttling authentication attempts.