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