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