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 ]
459 ],
460 'trackingcategories' => [
461 'class' => ApiQueryTrackingCategories::class,
462 'services' => [
463 'TrackingCategories',
464 ]
465 ],
466 'usercontribs' => [
467 'class' => ApiQueryUserContribs::class,
468 'services' => [
469 'CommentStore',
470 'UserIdentityLookup',
471 'UserNameUtils',
472 'RevisionStore',
473 'ChangeTagDefStore',
474 'ChangeTagsStore',
475 'ActorMigration',
476 'CommentFormatter',
477 ],
478 ],
479 'users' => [
480 'class' => ApiQueryUsers::class,
481 'services' => [
482 'UserNameUtils',
483 'UserFactory',
484 'UserGroupManager',
485 'GenderCache',
486 'AuthManager',
487 'TempUserConfig',
488 'TempUserDetailsLookup',
489 ],
490 ],
491 'watchlist' => [
492 'class' => ApiQueryWatchlist::class,
493 'services' => [
494 'CommentStore',
495 'ChangesListQueryFactory',
496 'RowCommentFormatter',
497 'TempUserConfig',
498 'LogFormatterFactory',
499 'RecentChangeLookup',
500 'TitleFormatter',
501 'WatchlistLabelStore',
502 ],
503 ],
504 'watchlistraw' => [
505 'class' => ApiQueryWatchlistRaw::class,
506 'services' => [
507 'WatchedItemQueryService',
508 'ContentLanguage',
509 'NamespaceInfo',
510 'GenderCache',
511 ]
512 ],
513 ];
514
518 private const QUERY_META_MODULES = [
519 'allmessages' => [
520 'class' => ApiQueryAllMessages::class,
521 'services' => [
522 'ContentLanguage',
523 'LanguageFactory',
524 'LanguageNameUtils',
525 'LocalisationCache',
526 'MessageCache',
527 ]
528 ],
529 'authmanagerinfo' => [
530 'class' => ApiQueryAuthManagerInfo::class,
531 'services' => [
532 'AuthManager',
533 ]
534 ],
535 'siteinfo' => [
536 'class' => ApiQuerySiteinfo::class,
537 'services' => [
538 'UserOptionsLookup',
539 'UserGroupManager',
540 'HookContainer',
541 'LanguageConverterFactory',
542 'LanguageFactory',
543 'LanguageNameUtils',
544 'ContentLanguage',
545 'NamespaceInfo',
546 'InterwikiLookup',
547 'ParserFactory',
548 'MagicWordFactory',
549 'SpecialPageFactory',
550 'SkinFactory',
551 'DBLoadBalancer',
552 'ReadOnlyMode',
553 'UrlUtils',
554 'TempUserConfig',
555 'GroupPermissionsLookup',
556 ]
557 ],
558 'userinfo' => [
559 'class' => ApiQueryUserInfo::class,
560 'services' => [
561 'TalkPageNotificationManager',
562 'WatchedItemStore',
563 'UserEditTracker',
564 'UserOptionsLookup',
565 'UserGroupManager',
566 'WatchlistLabelStore',
567 ]
568 ],
569 'filerepoinfo' => [
570 'class' => ApiQueryFileRepoInfo::class,
571 'services' => [
572 'RepoGroup',
573 ]
574 ],
575 'tokens' => [
576 'class' => ApiQueryTokens::class,
577 ],
578 'languageinfo' => [
579 'class' => ApiQueryLanguageinfo::class,
580 'services' => [
581 'LanguageFactory',
582 'LanguageNameUtils',
583 'LanguageFallback',
584 'LanguageConverterFactory',
585 ],
586 ],
587 ];
588
592 private $mPageSet;
593
595 private $mParams;
596
597 private ApiModuleManager $mModuleMgr;
598
599 public function __construct(
600 ApiMain $main,
601 string $action,
602 ObjectFactory $objectFactory,
603 private readonly WikiExporterFactory $wikiExporterFactory,
604 private readonly TitleFormatter $titleFormatter,
605 private readonly TitleFactory $titleFactory,
606 ) {
607 parent::__construct( $main, $action );
608
609 $this->mModuleMgr = new ApiModuleManager(
610 $this,
611 $objectFactory
612 );
613
614 // Allow custom modules to be added in LocalSettings.php
615 $config = $this->getConfig();
616 $this->mModuleMgr->addModules( self::QUERY_PROP_MODULES, 'prop' );
617 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIPropModules ), 'prop' );
618 $this->mModuleMgr->addModules( self::QUERY_LIST_MODULES, 'list' );
619 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIListModules ), 'list' );
620 $this->mModuleMgr->addModules( self::QUERY_META_MODULES, 'meta' );
621 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIMetaModules ), 'meta' );
622
623 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
624
625 // Create PageSet that will process titles/pageids/revids/generator
626 $this->mPageSet = new ApiPageSet( $this );
627 }
628
633 public function getModuleManager() {
634 return $this->mModuleMgr;
635 }
636
641 public function getPageSet() {
642 return $this->mPageSet;
643 }
644
648 public function getCustomPrinter() {
649 // If &exportnowrap is set, use the raw formatter
650 if ( $this->getParameter( 'export' ) &&
651 $this->getParameter( 'exportnowrap' )
652 ) {
653 return new ApiFormatRaw( $this->getMain(),
654 $this->getMain()->createPrinterByName( 'xml' ) );
655 } else {
656 return null;
657 }
658 }
659
670 public function execute() {
671 $this->mParams = $this->extractRequestParams();
672
673 // Instantiate requested modules
674 $allModules = [];
675 $this->instantiateModules( $allModules, 'prop' );
676 $propModules = array_keys( $allModules );
677 $this->instantiateModules( $allModules, 'list' );
678 $this->instantiateModules( $allModules, 'meta' );
679
680 // Filter modules based on continue parameter
681 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
682 $this->setContinuationManager( $continuationManager );
684 $modules = $continuationManager->getRunModules();
685 '@phan-var ApiQueryBase[] $modules';
686
687 // Allow extensions to stop execution for arbitrary reasons.
688 $message = 'hookaborted';
689 if ( !$this->getHookRunner()->onApiQueryCheckCanExecute( $modules, $this->getUser(), $message ) ) {
690 $this->dieWithError( $message );
691 }
692
693 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
694
695 if ( !$continuationManager->isGeneratorDone() ) {
696 // Query modules may optimize data requests through the $this->getPageSet()
697 // object by adding extra fields from the page table.
698 foreach ( $modules as $module ) {
699 // Augment api-query.$module.executeTiming metric with timings for requestExtraData()
700 $timer = $statsFactory->getTiming( 'api_query_extraDataTiming_seconds' )
701 ->setLabel( 'module', $module->getModuleName() )
702 ->start();
703 $module->requestExtraData( $this->mPageSet );
704 $timer->stop();
705 }
706 // Populate page/revision information
707 $this->mPageSet->execute();
708 // Record page information (title, namespace, if exists, etc)
709 $this->outputGeneralPageInfo();
710 } else {
711 $this->mPageSet->executeDryRun();
712 }
713
714 $cacheMode = $this->mPageSet->getCacheMode();
715
716 // Execute all unfinished modules
717 foreach ( $modules as $module ) {
718 // Break down of the api.query.executeTiming metric by query module.
719 $timer = $statsFactory->getTiming( 'api_query_executeTiming_seconds' )
720 ->setLabel( 'module', $module->getModuleName() )
721 ->start();
722 $t = microtime( true );
723
724 $params = $module->extractRequestParams();
725 $cacheMode = $this->mergeCacheMode(
726 $cacheMode, $module->getCacheMode( $params ) );
727 $scope = LoggerFactory::getContext()->addScoped( [
728 'context.api_query_module_name' => $module->getModuleName(),
729 ] );
730
731 // Wrap the execution in a try/catch to record metrics for success and errors
732 try {
733 $module->execute();
734 $module->recordUnifiedMetrics(
735 microtime( true ) - $t // Run time
736 );
737 } catch ( \Throwable $e ) {
738 // Unified metrics for errors
739 $module->recordUnifiedMetrics(
740 microtime( true ) - $t, // Run time
741 [
742 'status' => 'error_' . $e->getCode(), // Failure codes
743 ]
744 );
745 // Re-throw the exception so it's bubbled up
746 throw $e;
747 }
748 ScopedCallback::consume( $scope );
749
750 $timer->stop();
751
752 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
753 }
754
755 // Set the cache mode
756 $this->getMain()->setCacheMode( $cacheMode );
757
758 // Write the continuation data into the result
759 $this->setContinuationManager( null );
760 if ( $this->mParams['rawcontinue'] ) {
761 $data = $continuationManager->getRawNonContinuation();
762 if ( $data ) {
763 $this->getResult()->addValue( null, 'query-noncontinue', $data,
765 }
766 $data = $continuationManager->getRawContinuation();
767 if ( $data ) {
768 $this->getResult()->addValue( null, 'query-continue', $data,
770 }
771 } else {
772 $continuationManager->setContinuationIntoResult( $this->getResult() );
773 }
774 }
775
785 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
786 if ( $modCacheMode === 'anon-public-user-private' ) {
787 if ( $cacheMode !== 'private' ) {
788 $cacheMode = 'anon-public-user-private';
789 }
790 } elseif ( $modCacheMode === 'public' ) {
791 // do nothing, if it's public already it will stay public
792 } else {
793 $cacheMode = 'private';
794 }
795
796 return $cacheMode;
797 }
798
804 private function instantiateModules( &$modules, $param ) {
805 $wasPosted = $this->getRequest()->wasPosted();
806 if ( isset( $this->mParams[$param] ) ) {
807 foreach ( $this->mParams[$param] as $moduleName ) {
808 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
809 if ( $instance === null ) {
810 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
811 }
812 if ( !$wasPosted && $instance->mustBePosted() ) {
813 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
814 }
815 // Ignore duplicates. TODO 2.0: die()?
816 if ( !array_key_exists( $moduleName, $modules ) ) {
817 $modules[$moduleName] = $instance;
818 }
819 }
820 }
821 }
822
828 private function outputGeneralPageInfo() {
829 $pageSet = $this->getPageSet();
830 $result = $this->getResult();
831
832 // We can't really handle max-result-size failure here, but we need to
833 // check anyway in case someone set the limit stupidly low.
834 $fit = true;
835
836 $values = $pageSet->getNormalizedTitlesAsResult( $result );
837 if ( $values ) {
838 // @phan-suppress-next-line PhanRedundantCondition
839 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
840 }
841 $values = $pageSet->getConvertedTitlesAsResult( $result );
842 if ( $values ) {
843 $fit = $fit && $result->addValue( 'query', 'converted', $values );
844 }
845 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
846 if ( $values ) {
847 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
848 }
849 $values = $pageSet->getRedirectTitlesAsResult( $result );
850 if ( $values ) {
851 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
852 }
853 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
854 if ( $values ) {
855 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
856 }
857
858 // Page elements
859 // Cannot use ApiPageSet::getInvalidTitlesAndRevisions, it does not set $fakeId
860 $pages = [];
861
862 // Report any missing titles
863 foreach ( $pageSet->getMissingPages() as $fakeId => $page ) {
864 $vals = [
865 'ns' => $page->getNamespace(),
866 'title' => $this->titleFormatter->getPrefixedText( $page ),
867 'missing' => true,
868 ];
869 $title = $this->titleFactory->newFromPageIdentity( $page );
870 if ( $title->isKnown() ) {
871 $vals['known'] = true;
872 }
873 $pages[$fakeId] = $vals;
874 }
875 // Report any invalid titles
876 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
877 $pages[$fakeId] = $data + [ 'invalid' => true ];
878 }
879 // Report any missing page ids
880 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
881 $pages[$pageid] = [
882 'pageid' => $pageid,
883 'missing' => true,
884 ];
885 }
886 // Report special pages
888 foreach ( $pageSet->getSpecialPages() as $fakeId => $page ) {
889 $vals = [
890 'ns' => $page->getNamespace(),
891 'title' => $this->titleFormatter->getPrefixedText( $page ),
892 'special' => true,
893 ];
894 $title = $this->titleFactory->newFromPageReference( $page );
895 if ( !$title->isKnown() ) {
896 $vals['missing'] = true;
897 }
898 $pages[$fakeId] = $vals;
899 }
900
901 // Output general page information for found titles
902 foreach ( $pageSet->getGoodPages() as $pageid => $page ) {
903 $pages[$pageid] = [
904 'pageid' => $pageid,
905 'ns' => $page->getNamespace(),
906 'title' => $this->titleFormatter->getPrefixedText( $page ),
907 ];
908 }
909
910 if ( count( $pages ) ) {
911 $pageSet->populateGeneratorData( $pages );
912 ApiResult::setArrayType( $pages, 'BCarray' );
913
914 if ( $this->mParams['indexpageids'] ) {
915 $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
916 // json treats all map keys as strings - converting to match
917 $pageIDs = array_map( 'strval', $pageIDs );
918 ApiResult::setIndexedTagName( $pageIDs, 'id' );
919 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
920 }
921
922 ApiResult::setIndexedTagName( $pages, 'page' );
923 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
924 }
925
926 if ( !$fit ) {
927 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
928 }
929
930 if ( $this->mParams['export'] ) {
931 $this->doExport( $pageSet, $result );
932 }
933 }
934
939 private function doExport( $pageSet, $result ) {
940 $exportTitles = [];
941 $titles = $pageSet->getGoodPages();
942 if ( count( $titles ) ) {
944 foreach ( $titles as $title ) {
945 if ( $this->getAuthority()->authorizeRead( 'read', $title ) ) {
946 $exportTitles[] = $title;
947 }
948 }
949 }
950
951 $exporter = $this->wikiExporterFactory->getWikiExporter( $this->getDB() );
952 $sink = new DumpStringOutput;
953 $exporter->setOutputSink( $sink );
954 $exporter->setSchemaVersion( $this->mParams['exportschema'] );
955 $exporter->openStream();
956 foreach ( $exportTitles as $title ) {
957 $exporter->pageByTitle( $title );
958 }
959 $exporter->closeStream();
960
961 // Don't check the size of exported stuff
962 // It's not continuable, so it would cause more
963 // problems than it'd solve
964 if ( $this->mParams['exportnowrap'] ) {
965 $result->reset();
966 // Raw formatter will handle this
967 $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
968 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
969 $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
970 } else {
971 $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
972 $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
973 }
974 }
975
977 public function getAllowedParams( $flags = 0 ) {
978 $result = [
979 'prop' => [
980 ParamValidator::PARAM_ISMULTI => true,
981 ParamValidator::PARAM_TYPE => 'submodule',
982 ],
983 'list' => [
984 ParamValidator::PARAM_ISMULTI => true,
985 ParamValidator::PARAM_TYPE => 'submodule',
986 ],
987 'meta' => [
988 ParamValidator::PARAM_ISMULTI => true,
989 ParamValidator::PARAM_TYPE => 'submodule',
990 ],
991 'indexpageids' => false,
992 'export' => false,
993 'exportnowrap' => false,
994 'exportschema' => [
995 ParamValidator::PARAM_DEFAULT => WikiExporter::schemaVersion(),
996 ParamValidator::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
997 ],
998 'iwurl' => false,
999 'continue' => [
1000 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
1001 ],
1002 'rawcontinue' => false,
1003 ];
1004 if ( $flags ) {
1005 $result += $this->getPageSet()->getFinalParams( $flags );
1006 }
1007
1008 return $result;
1009 }
1010
1012 public function isReadMode() {
1013 // We need to make an exception for certain meta modules that should be
1014 // accessible even without the 'read' right. Restrict the exception as
1015 // much as possible: no other modules allowed, and no pageset
1016 // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
1017 // parameters since frameworks might add these unconditionally and they
1018 // can't expose anything here.
1019 $allowedParams = [ 'rawcontinue' => 1, 'indexpageids' => 1 ];
1020 $this->mParams = $this->extractRequestParams();
1021 $request = $this->getRequest();
1022 foreach ( $this->mParams + $this->getPageSet()->extractRequestParams() as $param => $value ) {
1023 $needed = $param === 'meta';
1024 if ( !isset( $allowedParams[$param] ) && $request->getCheck( $param ) !== $needed ) {
1025 return true;
1026 }
1027 }
1028
1029 // Ask each module if it requires read mode. Any true => this returns
1030 // true.
1031 $modules = [];
1032 $this->instantiateModules( $modules, 'meta' );
1033 foreach ( $modules as $module ) {
1034 if ( $module->isReadMode() ) {
1035 return true;
1036 }
1037 }
1038
1039 return false;
1040 }
1041
1043 public function isWriteMode() {
1044 // Ask each module if it requires write mode. If any require write mode this returns true.
1045 $modules = [];
1046 $this->mParams = $this->extractRequestParams();
1047 $this->instantiateModules( $modules, 'list' );
1048 $this->instantiateModules( $modules, 'meta' );
1049 $this->instantiateModules( $modules, 'prop' );
1050 foreach ( $modules as $module ) {
1051 if ( $module->isWriteMode() ) {
1052 return true;
1053 }
1054 }
1055
1056 return false;
1057 }
1058
1060 protected function getExamplesMessages() {
1061 $title = Title::newMainPage()->getPrefixedText();
1062 $mp = rawurlencode( $title );
1063
1064 return [
1065 'action=query&prop=revisions&meta=siteinfo&' .
1066 "titles={$mp}&rvprop=user|comment&continue="
1067 => 'apihelp-query-example-revisions',
1068 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
1069 => 'apihelp-query-example-allpages',
1070 ];
1071 }
1072
1074 public function getHelpUrls() {
1075 return [
1076 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
1077 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
1078 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
1079 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
1080 ];
1081 }
1082}
1083
1085class_alias( ApiQuery::class, 'ApiQuery' );
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:60
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1506
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:766
getMain()
Get the main module.
Definition ApiBase.php:560
getResult()
Get the result object.
Definition ApiBase.php:681
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:705
setContinuationManager(?ApiContinuationManager $manager=null)
Definition ApiBase.php:728
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1743
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:166
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:822
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:1673
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:943
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:67
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:785
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:670
getAllowedParams( $flags=0)
Definition ApiQuery.php:977
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:599
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:633
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:641
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.
Helper trait for implementations \DAO.