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