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