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