MediaWiki master
ApiQuery.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
19use WikiExporter;
20use Wikimedia\ObjectFactory\ObjectFactory;
22use 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 ],
97 'imageinfo' => [
98 'class' => ApiQueryImageInfo::class,
99 'services' => [
100 // Same as for stashimageinfo
101 'RepoGroup',
102 'ContentLanguage',
103 'BadFileLookup',
104 ]
105 ],
106 'info' => [
107 'class' => ApiQueryInfo::class,
108 'services' => [
109 'ContentLanguage',
110 'LinkBatchFactory',
111 'NamespaceInfo',
112 'TitleFactory',
113 'TitleFormatter',
114 'WatchedItemStore',
115 'LanguageConverterFactory',
116 'RestrictionStore',
117 'LinksMigration',
118 'TempUserCreator',
119 'UserFactory',
120 'IntroMessageBuilder',
121 'PreloadedContentBuilder',
122 'RevisionLookup',
123 'UrlUtils',
124 'LinkRenderer',
125 ],
126 ],
127 'links' => [
128 'class' => ApiQueryLinks::class,
129 'services' => [
130 // Same as for templates
131 'LinkBatchFactory',
132 'LinksMigration',
133 ]
134 ],
135 'linkshere' => [
136 'class' => ApiQueryBacklinksprop::class,
137 'services' => [
138 // Same as for fileusage, redirects, transcludedin
139 'LinksMigration',
140 ]
141 ],
142 'iwlinks' => [
143 'class' => ApiQueryIWLinks::class,
144 'services' => [
145 'UrlUtils',
146 ]
147 ],
148 'langlinks' => [
149 'class' => ApiQueryLangLinks::class,
150 'services' => [
151 'LanguageNameUtils',
152 'ContentLanguage',
153 'UrlUtils',
154 ]
155 ],
156 'pageprops' => [
157 'class' => ApiQueryPageProps::class,
158 'services' => [
159 'PageProps',
160 ]
161 ],
162 'redirects' => [
163 'class' => ApiQueryBacklinksprop::class,
164 'services' => [
165 // Same as for fileusage, linkshere, transcludedin
166 'LinksMigration',
167 ]
168 ],
169 'revisions' => [
170 'class' => ApiQueryRevisions::class,
171 'services' => [
172 'RevisionStore',
173 'ContentHandlerFactory',
174 'ParserFactory',
175 'SlotRoleRegistry',
176 'ChangeTagDefStore',
177 'ChangeTagsStore',
178 'ActorMigration',
179 'ContentRenderer',
180 'ContentTransformer',
181 'CommentFormatter',
182 'TempUserCreator',
183 'UserFactory',
184 'TitleFormatter',
185 ]
186 ],
187 'stashimageinfo' => [
188 'class' => ApiQueryStashImageInfo::class,
189 'services' => [
190 // Same as for imageinfo
191 'RepoGroup',
192 'ContentLanguage',
193 'BadFileLookup',
194 ]
195 ],
196 'templates' => [
197 'class' => ApiQueryLinks::class,
198 'services' => [
199 // Same as for links
200 'LinkBatchFactory',
201 'LinksMigration',
202 ]
203 ],
204 'transcludedin' => [
205 'class' => ApiQueryBacklinksprop::class,
206 'services' => [
207 // Same as for fileusage, linkshere, redirects
208 'LinksMigration',
209 ]
210 ],
211 ];
212
216 private const QUERY_LIST_MODULES = [
217 'allcategories' => [
218 'class' => ApiQueryAllCategories::class,
219 ],
220 'alldeletedrevisions' => [
221 'class' => ApiQueryAllDeletedRevisions::class,
222 'services' => [
223 'RevisionStore',
224 'ContentHandlerFactory',
225 'ParserFactory',
226 'SlotRoleRegistry',
227 'ChangeTagDefStore',
228 'ChangeTagsStore',
229 'NamespaceInfo',
230 'ContentRenderer',
231 'ContentTransformer',
232 'CommentFormatter',
233 'TempUserCreator',
234 'UserFactory',
235 ]
236 ],
237 'allfileusages' => [
238 'class' => ApiQueryAllLinks::class,
239 'services' => [
240 // Same as for alllinks, allredirects, alltransclusions
241 'NamespaceInfo',
242 'GenderCache',
243 'LinksMigration',
244 ]
245 ],
246 'allimages' => [
247 'class' => ApiQueryAllImages::class,
248 'services' => [
249 'RepoGroup',
250 'GroupPermissionsLookup',
251 ]
252 ],
253 'alllinks' => [
254 'class' => ApiQueryAllLinks::class,
255 'services' => [
256 // Same as for allfileusages, allredirects, alltransclusions
257 'NamespaceInfo',
258 'GenderCache',
259 'LinksMigration',
260 ]
261 ],
262 'allpages' => [
263 'class' => ApiQueryAllPages::class,
264 'services' => [
265 'NamespaceInfo',
266 'GenderCache',
267 'RestrictionStore',
268 ]
269 ],
270 'allredirects' => [
271 'class' => ApiQueryAllLinks::class,
272 'services' => [
273 // Same as for allfileusages, alllinks, alltransclusions
274 'NamespaceInfo',
275 'GenderCache',
276 'LinksMigration',
277 ]
278 ],
279 'allrevisions' => [
280 'class' => ApiQueryAllRevisions::class,
281 'services' => [
282 'RevisionStore',
283 'ContentHandlerFactory',
284 'ParserFactory',
285 'SlotRoleRegistry',
286 'ActorMigration',
287 'NamespaceInfo',
288 'ChangeTagsStore',
289 'ContentRenderer',
290 'ContentTransformer',
291 'CommentFormatter',
292 'TempUserCreator',
293 'UserFactory',
294 ]
295 ],
296 'mystashedfiles' => [
297 'class' => ApiQueryMyStashedFiles::class,
298 ],
299 'alltransclusions' => [
300 'class' => ApiQueryAllLinks::class,
301 'services' => [
302 // Same as for allfileusages, alllinks, allredirects
303 'NamespaceInfo',
304 'GenderCache',
305 'LinksMigration',
306 ]
307 ],
308 'allusers' => [
309 'class' => ApiQueryAllUsers::class,
310 'services' => [
311 'UserFactory',
312 'UserGroupManager',
313 'GroupPermissionsLookup',
314 'ContentLanguage',
315 'TempUserConfig',
316 'RecentChangeLookup',
317 'TempUserDetailsLookup',
318 ]
319 ],
320 'backlinks' => [
321 'class' => ApiQueryBacklinks::class,
322 'services' => [
323 'LinksMigration',
324 ]
325 ],
326 'blocks' => [
327 'class' => ApiQueryBlocks::class,
328 'services' => [
329 'DatabaseBlockStore',
330 'BlockActionInfo',
331 'BlockRestrictionStore',
332 'CommentStore',
333 'HideUserUtils',
334 'CommentFormatter',
335 ],
336 ],
337 'categorymembers' => [
338 'class' => ApiQueryCategoryMembers::class,
339 'services' => [
340 'CollationFactory',
341 ]
342 ],
343 'codexicons' => [
344 'class' => ApiQueryCodexIcons::class,
345 ],
346 'deletedrevs' => [
347 'class' => ApiQueryDeletedrevs::class,
348 'services' => [
349 'CommentStore',
350 'RowCommentFormatter',
351 'RevisionStore',
352 'ChangeTagDefStore',
353 'ChangeTagsStore',
354 'LinkBatchFactory',
355 ],
356 ],
357 'embeddedin' => [
358 'class' => ApiQueryBacklinks::class,
359 'services' => [
360 'LinksMigration',
361 ]
362 ],
363 'exturlusage' => [
364 'class' => ApiQueryExtLinksUsage::class,
365 'services' => [
366 'UrlUtils',
367 ],
368 ],
369 'filearchive' => [
370 'class' => ApiQueryFilearchive::class,
371 'services' => [
372 'CommentStore',
373 'CommentFormatter',
374 ],
375 ],
376 'imageusage' => [
377 'class' => ApiQueryBacklinks::class,
378 'services' => [
379 'LinksMigration',
380 ]
381 ],
382 'iwbacklinks' => [
383 'class' => ApiQueryIWBacklinks::class,
384 ],
385 'langbacklinks' => [
386 'class' => ApiQueryLangBacklinks::class,
387 ],
388 'logevents' => [
389 'class' => ApiQueryLogEvents::class,
390 'services' => [
391 'CommentStore',
392 'RowCommentFormatter',
393 'ChangeTagDefStore',
394 'ChangeTagsStore',
395 'UserNameUtils',
396 'LogFormatterFactory',
397 ],
398 ],
399 'pageswithprop' => [
400 'class' => ApiQueryPagesWithProp::class,
401 ],
402 'pagepropnames' => [
403 'class' => ApiQueryPagePropNames::class,
404 ],
405 'prefixsearch' => [
406 'class' => ApiQueryPrefixSearch::class,
407 'services' => [
408 'SearchEngineConfig',
409 'SearchEngineFactory',
410 ],
411 ],
412 'protectedtitles' => [
413 'class' => ApiQueryProtectedTitles::class,
414 'services' => [
415 'CommentStore',
416 'RowCommentFormatter'
417 ],
418 ],
419 'querypage' => [
420 'class' => ApiQueryQueryPage::class,
421 'services' => [
422 'SpecialPageFactory',
423 ]
424 ],
425 'random' => [
426 'class' => ApiQueryRandom::class,
427 'services' => [
428 'ContentHandlerFactory'
429 ]
430 ],
431 'recentchanges' => [
432 'class' => ApiQueryRecentChanges::class,
433 'services' => [
434 'CommentStore',
435 'RowCommentFormatter',
436 'SlotRoleRegistry',
437 'UserNameUtils',
438 'LogFormatterFactory',
439 'ChangesListQueryFactory',
440 'RecentChangeLookup',
441 ],
442 ],
443 'search' => [
444 'class' => ApiQuerySearch::class,
445 'services' => [
446 'SearchEngineConfig',
447 'SearchEngineFactory',
448 'TitleMatcher',
449 ],
450 ],
451 'tags' => [
452 'class' => ApiQueryTags::class,
453 'services' => [
454 'ChangeTagsStore',
455 ]
456 ],
457 'trackingcategories' => [
458 'class' => ApiQueryTrackingCategories::class,
459 'services' => [
460 'TrackingCategories',
461 ]
462 ],
463 'usercontribs' => [
464 'class' => ApiQueryUserContribs::class,
465 'services' => [
466 'CommentStore',
467 'UserIdentityLookup',
468 'UserNameUtils',
469 'RevisionStore',
470 'ChangeTagDefStore',
471 'ChangeTagsStore',
472 'ActorMigration',
473 'CommentFormatter',
474 ],
475 ],
476 'users' => [
477 'class' => ApiQueryUsers::class,
478 'services' => [
479 'UserNameUtils',
480 'UserFactory',
481 'UserGroupManager',
482 'GenderCache',
483 'AuthManager',
484 'TempUserConfig',
485 'TempUserDetailsLookup',
486 ],
487 ],
488 'watchlist' => [
489 'class' => ApiQueryWatchlist::class,
490 'services' => [
491 'CommentStore',
492 'ChangesListQueryFactory',
493 'RowCommentFormatter',
494 'TempUserConfig',
495 'LogFormatterFactory',
496 'RecentChangeLookup',
497 'TitleFormatter',
498 ],
499 ],
500 'watchlistraw' => [
501 'class' => ApiQueryWatchlistRaw::class,
502 'services' => [
503 'WatchedItemQueryService',
504 'ContentLanguage',
505 'NamespaceInfo',
506 'GenderCache',
507 ]
508 ],
509 ];
510
514 private const QUERY_META_MODULES = [
515 'allmessages' => [
516 'class' => ApiQueryAllMessages::class,
517 'services' => [
518 'ContentLanguage',
519 'LanguageFactory',
520 'LanguageNameUtils',
521 'LocalisationCache',
522 'MessageCache',
523 ]
524 ],
525 'authmanagerinfo' => [
526 'class' => ApiQueryAuthManagerInfo::class,
527 'services' => [
528 'AuthManager',
529 ]
530 ],
531 'siteinfo' => [
532 'class' => ApiQuerySiteinfo::class,
533 'services' => [
534 'UserOptionsLookup',
535 'UserGroupManager',
536 'HookContainer',
537 'LanguageConverterFactory',
538 'LanguageFactory',
539 'LanguageNameUtils',
540 'ContentLanguage',
541 'NamespaceInfo',
542 'InterwikiLookup',
543 'ParserFactory',
544 'MagicWordFactory',
545 'SpecialPageFactory',
546 'SkinFactory',
547 'DBLoadBalancer',
548 'ReadOnlyMode',
549 'UrlUtils',
550 'TempUserConfig',
551 'GroupPermissionsLookup',
552 ]
553 ],
554 'userinfo' => [
555 'class' => ApiQueryUserInfo::class,
556 'services' => [
557 'TalkPageNotificationManager',
558 'WatchedItemStore',
559 'UserEditTracker',
560 'UserOptionsLookup',
561 'UserGroupManager',
562 ]
563 ],
564 'filerepoinfo' => [
565 'class' => ApiQueryFileRepoInfo::class,
566 'services' => [
567 'RepoGroup',
568 ]
569 ],
570 'tokens' => [
571 'class' => ApiQueryTokens::class,
572 ],
573 'languageinfo' => [
574 'class' => ApiQueryLanguageinfo::class,
575 'services' => [
576 'LanguageFactory',
577 'LanguageNameUtils',
578 'LanguageFallback',
579 'LanguageConverterFactory',
580 ],
581 ],
582 ];
583
587 private $mPageSet;
588
590 private $mParams;
592 private $mModuleMgr;
593
594 private WikiExporterFactory $wikiExporterFactory;
595 private TitleFormatter $titleFormatter;
596 private TitleFactory $titleFactory;
597
598 public function __construct(
599 ApiMain $main,
600 string $action,
601 ObjectFactory $objectFactory,
602 WikiExporterFactory $wikiExporterFactory,
603 TitleFormatter $titleFormatter,
604 TitleFactory $titleFactory
605 ) {
606 parent::__construct( $main, $action );
607
608 $this->mModuleMgr = new ApiModuleManager(
609 $this,
610 $objectFactory
611 );
612
613 // Allow custom modules to be added in LocalSettings.php
614 $config = $this->getConfig();
615 $this->mModuleMgr->addModules( self::QUERY_PROP_MODULES, 'prop' );
616 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIPropModules ), 'prop' );
617 $this->mModuleMgr->addModules( self::QUERY_LIST_MODULES, 'list' );
618 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIListModules ), 'list' );
619 $this->mModuleMgr->addModules( self::QUERY_META_MODULES, 'meta' );
620 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIMetaModules ), 'meta' );
621
622 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
623
624 // Create PageSet that will process titles/pageids/revids/generator
625 $this->mPageSet = new ApiPageSet( $this );
626 $this->wikiExporterFactory = $wikiExporterFactory;
627 $this->titleFormatter = $titleFormatter;
628 $this->titleFactory = $titleFactory;
629 }
630
635 public function getModuleManager() {
636 return $this->mModuleMgr;
637 }
638
643 public function getPageSet() {
644 return $this->mPageSet;
645 }
646
650 public function getCustomPrinter() {
651 // If &exportnowrap is set, use the raw formatter
652 if ( $this->getParameter( 'export' ) &&
653 $this->getParameter( 'exportnowrap' )
654 ) {
655 return new ApiFormatRaw( $this->getMain(),
656 $this->getMain()->createPrinterByName( 'xml' ) );
657 } else {
658 return null;
659 }
660 }
661
672 public function execute() {
673 $this->mParams = $this->extractRequestParams();
674
675 // Instantiate requested modules
676 $allModules = [];
677 $this->instantiateModules( $allModules, 'prop' );
678 $propModules = array_keys( $allModules );
679 $this->instantiateModules( $allModules, 'list' );
680 $this->instantiateModules( $allModules, 'meta' );
681
682 // Filter modules based on continue parameter
683 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
684 $this->setContinuationManager( $continuationManager );
686 $modules = $continuationManager->getRunModules();
687 '@phan-var ApiQueryBase[] $modules';
688
689 // Allow extensions to stop execution for arbitrary reasons.
690 $message = 'hookaborted';
691 if ( !$this->getHookRunner()->onApiQueryCheckCanExecute( $modules, $this->getUser(), $message ) ) {
692 $this->dieWithError( $message );
693 }
694
695 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
696
697 if ( !$continuationManager->isGeneratorDone() ) {
698 // Query modules may optimize data requests through the $this->getPageSet()
699 // object by adding extra fields from the page table.
700 foreach ( $modules as $module ) {
701 // Augment api-query.$module.executeTiming metric with timings for requestExtraData()
702 $timer = $statsFactory->getTiming( 'api_query_extraDataTiming_seconds' )
703 ->setLabel( 'module', $module->getModuleName() )
704 ->start();
705 $module->requestExtraData( $this->mPageSet );
706 $timer->stop();
707 }
708 // Populate page/revision information
709 $this->mPageSet->execute();
710 // Record page information (title, namespace, if exists, etc)
711 $this->outputGeneralPageInfo();
712 } else {
713 $this->mPageSet->executeDryRun();
714 }
715
716 $cacheMode = $this->mPageSet->getCacheMode();
717
718 // Execute all unfinished modules
719 foreach ( $modules as $module ) {
720 // Break down of the api.query.executeTiming metric by query module.
721 $timer = $statsFactory->getTiming( 'api_query_executeTiming_seconds' )
722 ->setLabel( 'module', $module->getModuleName() )
723 ->start();
724 $t = microtime( true );
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 microtime( true ) - $t // Run time
738 );
739 } catch ( \Throwable $e ) {
740 // Unified metrics for errors
741 $module->recordUnifiedMetrics(
742 microtime( true ) - $t, // Run time
743 [
744 'status' => 'error_' . $e->getCode(), // Failure codes
745 ]
746 );
747 // Re-throw the exception so it's bubbled up
748 throw $e;
749 }
750 ScopedCallback::consume( $scope );
751
752 $timer->stop();
753
754 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
755 }
756
757 // Set the cache mode
758 $this->getMain()->setCacheMode( $cacheMode );
759
760 // Write the continuation data into the result
761 $this->setContinuationManager( null );
762 if ( $this->mParams['rawcontinue'] ) {
763 $data = $continuationManager->getRawNonContinuation();
764 if ( $data ) {
765 $this->getResult()->addValue( null, 'query-noncontinue', $data,
767 }
768 $data = $continuationManager->getRawContinuation();
769 if ( $data ) {
770 $this->getResult()->addValue( null, 'query-continue', $data,
772 }
773 } else {
774 $continuationManager->setContinuationIntoResult( $this->getResult() );
775 }
776 }
777
787 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
788 if ( $modCacheMode === 'anon-public-user-private' ) {
789 if ( $cacheMode !== 'private' ) {
790 $cacheMode = 'anon-public-user-private';
791 }
792 } elseif ( $modCacheMode === 'public' ) {
793 // do nothing, if it's public already it will stay public
794 } else {
795 $cacheMode = 'private';
796 }
797
798 return $cacheMode;
799 }
800
806 private function instantiateModules( &$modules, $param ) {
807 $wasPosted = $this->getRequest()->wasPosted();
808 if ( isset( $this->mParams[$param] ) ) {
809 foreach ( $this->mParams[$param] as $moduleName ) {
810 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
811 if ( $instance === null ) {
812 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
813 }
814 if ( !$wasPosted && $instance->mustBePosted() ) {
815 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
816 }
817 // Ignore duplicates. TODO 2.0: die()?
818 if ( !array_key_exists( $moduleName, $modules ) ) {
819 $modules[$moduleName] = $instance;
820 }
821 }
822 }
823 }
824
830 private function outputGeneralPageInfo() {
831 $pageSet = $this->getPageSet();
832 $result = $this->getResult();
833
834 // We can't really handle max-result-size failure here, but we need to
835 // check anyway in case someone set the limit stupidly low.
836 $fit = true;
837
838 $values = $pageSet->getNormalizedTitlesAsResult( $result );
839 if ( $values ) {
840 // @phan-suppress-next-line PhanRedundantCondition
841 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
842 }
843 $values = $pageSet->getConvertedTitlesAsResult( $result );
844 if ( $values ) {
845 $fit = $fit && $result->addValue( 'query', 'converted', $values );
846 }
847 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
848 if ( $values ) {
849 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
850 }
851 $values = $pageSet->getRedirectTitlesAsResult( $result );
852 if ( $values ) {
853 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
854 }
855 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
856 if ( $values ) {
857 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
858 }
859
860 // Page elements
861 // Cannot use ApiPageSet::getInvalidTitlesAndRevisions, it does not set $fakeId
862 $pages = [];
863
864 // Report any missing titles
865 foreach ( $pageSet->getMissingPages() as $fakeId => $page ) {
866 $vals = [];
867 $vals['ns'] = $page->getNamespace();
868 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
869 $vals['missing'] = true;
870 $title = $this->titleFactory->newFromPageIdentity( $page );
871 if ( $title->isKnown() ) {
872 $vals['known'] = true;
873 }
874 $pages[$fakeId] = $vals;
875 }
876 // Report any invalid titles
877 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
878 $pages[$fakeId] = $data + [ 'invalid' => true ];
879 }
880 // Report any missing page ids
881 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
882 $pages[$pageid] = [
883 'pageid' => $pageid,
884 'missing' => true,
885 ];
886 }
887 // Report special pages
889 foreach ( $pageSet->getSpecialPages() as $fakeId => $page ) {
890 $vals = [];
891 $vals['ns'] = $page->getNamespace();
892 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
893 $vals['special'] = true;
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 $vals = [];
904 $vals['pageid'] = $pageid;
905 $vals['ns'] = $page->getNamespace();
906 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
907 $pages[$pageid] = $vals;
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:61
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1511
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:767
getMain()
Get the main module.
Definition ApiBase.php:561
getResult()
Get the result object.
Definition ApiBase.php:682
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:706
setContinuationManager(?ApiContinuationManager $manager=null)
Definition ApiBase.php:729
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1748
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
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:1678
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:944
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:787
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:672
getAllowedParams( $flags=0)
Definition ApiQuery.php:977
__construct(ApiMain $main, string $action, ObjectFactory $objectFactory, WikiExporterFactory $wikiExporterFactory, TitleFormatter $titleFormatter, TitleFactory $titleFactory)
Definition ApiQuery.php:598
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...
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:635
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:643
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:47
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:56
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:70
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
Helper trait for implementations \DAO.