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 'TitleFormatter',
189 ]
190 ],
191 'stashimageinfo' => [
192 'class' => ApiQueryStashImageInfo::class,
193 'services' => [
194 // Same as for imageinfo
195 'RepoGroup',
196 'ContentLanguage',
197 'BadFileLookup',
198 ]
199 ],
200 'templates' => [
201 'class' => ApiQueryLinks::class,
202 'services' => [
203 // Same as for links
204 'LinkBatchFactory',
205 'LinksMigration',
206 ]
207 ],
208 'transcludedin' => [
209 'class' => ApiQueryBacklinksprop::class,
210 'services' => [
211 // Same as for fileusage, linkshere, redirects
212 'LinksMigration',
213 ]
214 ],
215 ];
216
220 private const QUERY_LIST_MODULES = [
221 'allcategories' => [
222 'class' => ApiQueryAllCategories::class,
223 ],
224 'alldeletedrevisions' => [
225 'class' => ApiQueryAllDeletedRevisions::class,
226 'services' => [
227 'RevisionStore',
228 'ContentHandlerFactory',
229 'ParserFactory',
230 'SlotRoleRegistry',
231 'ChangeTagDefStore',
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 'ContentRenderer',
292 'ContentTransformer',
293 'CommentFormatter',
294 'TempUserCreator',
295 'UserFactory',
296 ]
297 ],
298 'mystashedfiles' => [
299 'class' => ApiQueryMyStashedFiles::class,
300 ],
301 'alltransclusions' => [
302 'class' => ApiQueryAllLinks::class,
303 'services' => [
304 // Same as for allfileusages, alllinks, allredirects
305 'NamespaceInfo',
306 'GenderCache',
307 'LinksMigration',
308 ]
309 ],
310 'allusers' => [
311 'class' => ApiQueryAllUsers::class,
312 'services' => [
313 'UserFactory',
314 'UserGroupManager',
315 'GroupPermissionsLookup',
316 'ContentLanguage',
317 ]
318 ],
319 'backlinks' => [
320 'class' => ApiQueryBacklinks::class,
321 'services' => [
322 'LinksMigration',
323 ]
324 ],
325 'blocks' => [
326 'class' => ApiQueryBlocks::class,
327 'services' => [
328 'DatabaseBlockStore',
329 'BlockActionInfo',
330 'BlockRestrictionStore',
331 'CommentStore',
332 'HideUserUtils',
333 ],
334 ],
335 'categorymembers' => [
336 'class' => ApiQueryCategoryMembers::class,
337 'services' => [
338 'CollationFactory',
339 ]
340 ],
341 'deletedrevs' => [
342 'class' => ApiQueryDeletedrevs::class,
343 'services' => [
344 'CommentStore',
345 'RowCommentFormatter',
346 'RevisionStore',
347 'ChangeTagDefStore',
348 'LinkBatchFactory',
349 ],
350 ],
351 'embeddedin' => [
352 'class' => ApiQueryBacklinks::class,
353 'services' => [
354 'LinksMigration',
355 ]
356 ],
357 'exturlusage' => [
358 'class' => ApiQueryExtLinksUsage::class,
359 'services' => [
360 'UrlUtils',
361 ],
362 ],
363 'filearchive' => [
364 'class' => ApiQueryFilearchive::class,
365 'services' => [
366 'CommentStore',
367 'CommentFormatter',
368 ],
369 ],
370 'imageusage' => [
371 'class' => ApiQueryBacklinks::class,
372 'services' => [
373 'LinksMigration',
374 ]
375 ],
376 'iwbacklinks' => [
377 'class' => ApiQueryIWBacklinks::class,
378 ],
379 'langbacklinks' => [
380 'class' => ApiQueryLangBacklinks::class,
381 ],
382 'logevents' => [
383 'class' => ApiQueryLogEvents::class,
384 'services' => [
385 'CommentStore',
386 'RowCommentFormatter',
387 'ChangeTagDefStore',
388 'UserNameUtils',
389 ],
390 ],
391 'pageswithprop' => [
392 'class' => ApiQueryPagesWithProp::class,
393 ],
394 'pagepropnames' => [
395 'class' => ApiQueryPagePropNames::class,
396 ],
397 'prefixsearch' => [
398 'class' => ApiQueryPrefixSearch::class,
399 'services' => [
400 'SearchEngineConfig',
401 'SearchEngineFactory',
402 ],
403 ],
404 'protectedtitles' => [
405 'class' => ApiQueryProtectedTitles::class,
406 'services' => [
407 'CommentStore',
408 'RowCommentFormatter'
409 ],
410 ],
411 'querypage' => [
412 'class' => ApiQueryQueryPage::class,
413 'services' => [
414 'SpecialPageFactory',
415 ]
416 ],
417 'random' => [
418 'class' => ApiQueryRandom::class,
419 ],
420 'recentchanges' => [
421 'class' => ApiQueryRecentChanges::class,
422 'services' => [
423 'CommentStore',
424 'RowCommentFormatter',
425 'ChangeTagDefStore',
426 'SlotRoleStore',
427 'SlotRoleRegistry',
428 'UserNameUtils',
429 ],
430 ],
431 'search' => [
432 'class' => ApiQuerySearch::class,
433 'services' => [
434 'SearchEngineConfig',
435 'SearchEngineFactory',
436 'TitleMatcher',
437 ],
438 ],
439 'tags' => [
440 'class' => ApiQueryTags::class,
441 'services' => [
442 'ChangeTagsStore',
443 ]
444 ],
445 'usercontribs' => [
446 'class' => ApiQueryUserContribs::class,
447 'services' => [
448 'CommentStore',
449 'UserIdentityLookup',
450 'UserNameUtils',
451 'RevisionStore',
452 'ChangeTagDefStore',
453 'ActorMigration',
454 'CommentFormatter',
455 ],
456 ],
457 'users' => [
458 'class' => ApiQueryUsers::class,
459 'services' => [
460 'UserNameUtils',
461 'UserFactory',
462 'UserGroupManager',
463 'GenderCache',
464 'AuthManager',
465 ],
466 ],
467 'watchlist' => [
468 'class' => ApiQueryWatchlist::class,
469 'services' => [
470 'CommentStore',
471 'WatchedItemQueryService',
472 'ContentLanguage',
473 'NamespaceInfo',
474 'GenderCache',
475 'CommentFormatter',
476 'TempUserConfig'
477 ],
478 ],
479 'watchlistraw' => [
480 'class' => ApiQueryWatchlistRaw::class,
481 'services' => [
482 'WatchedItemQueryService',
483 'ContentLanguage',
484 'NamespaceInfo',
485 'GenderCache',
486 ]
487 ],
488 ];
489
493 private const QUERY_META_MODULES = [
494 'allmessages' => [
495 'class' => ApiQueryAllMessages::class,
496 'services' => [
497 'ContentLanguage',
498 'LanguageFactory',
499 'LanguageNameUtils',
500 'LocalisationCache',
501 'MessageCache',
502 ]
503 ],
504 'authmanagerinfo' => [
505 'class' => ApiQueryAuthManagerInfo::class,
506 'services' => [
507 'AuthManager',
508 ]
509 ],
510 'siteinfo' => [
511 'class' => ApiQuerySiteinfo::class,
512 'services' => [
513 'UserOptionsLookup',
514 'UserGroupManager',
515 'LanguageConverterFactory',
516 'LanguageFactory',
517 'LanguageNameUtils',
518 'ContentLanguage',
519 'NamespaceInfo',
520 'InterwikiLookup',
521 'ParserFactory',
522 'MagicWordFactory',
523 'SpecialPageFactory',
524 'SkinFactory',
525 'DBLoadBalancer',
526 'ReadOnlyMode',
527 'UrlUtils',
528 'TempUserConfig'
529 ]
530 ],
531 'userinfo' => [
532 'class' => ApiQueryUserInfo::class,
533 'services' => [
534 'TalkPageNotificationManager',
535 'WatchedItemStore',
536 'UserEditTracker',
537 'UserOptionsLookup',
538 'UserGroupManager',
539 ]
540 ],
541 'filerepoinfo' => [
542 'class' => ApiQueryFileRepoInfo::class,
543 'services' => [
544 'RepoGroup',
545 ]
546 ],
547 'tokens' => [
548 'class' => ApiQueryTokens::class,
549 ],
550 'languageinfo' => [
551 'class' => ApiQueryLanguageinfo::class,
552 'services' => [
553 'LanguageFactory',
554 'LanguageNameUtils',
555 'LanguageFallback',
556 'LanguageConverterFactory',
557 ],
558 ],
559 ];
560
564 private $mPageSet;
565
566 private $mParams;
567 private $mModuleMgr;
568
569 private WikiExporterFactory $wikiExporterFactory;
570 private TitleFormatter $titleFormatter;
571 private TitleFactory $titleFactory;
572
581 public function __construct(
582 ApiMain $main,
583 $action,
584 ObjectFactory $objectFactory,
585 WikiExporterFactory $wikiExporterFactory,
586 TitleFormatter $titleFormatter,
587 TitleFactory $titleFactory
588 ) {
589 parent::__construct( $main, $action );
590
591 $this->mModuleMgr = new ApiModuleManager(
592 $this,
593 $objectFactory
594 );
595
596 // Allow custom modules to be added in LocalSettings.php
597 $config = $this->getConfig();
598 $this->mModuleMgr->addModules( self::QUERY_PROP_MODULES, 'prop' );
599 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIPropModules ), 'prop' );
600 $this->mModuleMgr->addModules( self::QUERY_LIST_MODULES, 'list' );
601 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIListModules ), 'list' );
602 $this->mModuleMgr->addModules( self::QUERY_META_MODULES, 'meta' );
603 $this->mModuleMgr->addModules( $config->get( MainConfigNames::APIMetaModules ), 'meta' );
604
605 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
606
607 // Create PageSet that will process titles/pageids/revids/generator
608 $this->mPageSet = new ApiPageSet( $this );
609 $this->wikiExporterFactory = $wikiExporterFactory;
610 $this->titleFormatter = $titleFormatter;
611 $this->titleFactory = $titleFactory;
612 }
613
618 public function getModuleManager() {
619 return $this->mModuleMgr;
620 }
621
626 public function getPageSet() {
627 return $this->mPageSet;
628 }
629
633 public function getCustomPrinter() {
634 // If &exportnowrap is set, use the raw formatter
635 if ( $this->getParameter( 'export' ) &&
636 $this->getParameter( 'exportnowrap' )
637 ) {
638 return new ApiFormatRaw( $this->getMain(),
639 $this->getMain()->createPrinterByName( 'xml' ) );
640 } else {
641 return null;
642 }
643 }
644
655 public function execute() {
656 $this->mParams = $this->extractRequestParams();
657
658 // Instantiate requested modules
659 $allModules = [];
660 $this->instantiateModules( $allModules, 'prop' );
661 $propModules = array_keys( $allModules );
662 $this->instantiateModules( $allModules, 'list' );
663 $this->instantiateModules( $allModules, 'meta' );
664
665 // Filter modules based on continue parameter
666 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
667 $this->setContinuationManager( $continuationManager );
669 $modules = $continuationManager->getRunModules();
670 '@phan-var ApiQueryBase[] $modules';
671
672 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
673
674 if ( !$continuationManager->isGeneratorDone() ) {
675 // Query modules may optimize data requests through the $this->getPageSet()
676 // object by adding extra fields from the page table.
677 foreach ( $modules as $module ) {
678 // Augment api-query.$module.executeTiming metric with timings for requestExtraData()
679 $timer = $statsFactory->getTiming( 'api_query_extraDataTiming_seconds' )
680 ->setLabel( 'module', $module->getModuleName() )
681 ->copyToStatsdAt( 'api-query.' . $module->getModuleName() . '.extraDataTiming' );
682 $timer->start();
683 $module->requestExtraData( $this->mPageSet );
684 $timer->stop();
685 }
686 // Populate page/revision information
687 $this->mPageSet->execute();
688 // Record page information (title, namespace, if exists, etc)
689 $this->outputGeneralPageInfo();
690 } else {
691 $this->mPageSet->executeDryRun();
692 }
693
694 $cacheMode = $this->mPageSet->getCacheMode();
695
696 // Execute all unfinished modules
697 foreach ( $modules as $module ) {
698 // Break down of the api.query.executeTiming metric by query module.
699 $timer = $statsFactory->getTiming( 'api_query_executeTiming_seconds' )
700 ->setLabel( 'module', $module->getModuleName() )
701 ->copyToStatsdAt( 'api-query.' . $module->getModuleName() . '.executeTiming' );
702 $timer->start();
703
704 $params = $module->extractRequestParams();
705 $cacheMode = $this->mergeCacheMode(
706 $cacheMode, $module->getCacheMode( $params ) );
707 $module->execute();
708
709 $timer->stop();
710
711 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
712 }
713
714 // Set the cache mode
715 $this->getMain()->setCacheMode( $cacheMode );
716
717 // Write the continuation data into the result
718 $this->setContinuationManager( null );
719 if ( $this->mParams['rawcontinue'] ) {
720 $data = $continuationManager->getRawNonContinuation();
721 if ( $data ) {
722 $this->getResult()->addValue( null, 'query-noncontinue', $data,
723 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
724 }
725 $data = $continuationManager->getRawContinuation();
726 if ( $data ) {
727 $this->getResult()->addValue( null, 'query-continue', $data,
728 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
729 }
730 } else {
731 $continuationManager->setContinuationIntoResult( $this->getResult() );
732 }
733 }
734
744 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
745 if ( $modCacheMode === 'anon-public-user-private' ) {
746 if ( $cacheMode !== 'private' ) {
747 $cacheMode = 'anon-public-user-private';
748 }
749 } elseif ( $modCacheMode === 'public' ) {
750 // do nothing, if it's public already it will stay public
751 } else {
752 $cacheMode = 'private';
753 }
754
755 return $cacheMode;
756 }
757
763 private function instantiateModules( &$modules, $param ) {
764 $wasPosted = $this->getRequest()->wasPosted();
765 if ( isset( $this->mParams[$param] ) ) {
766 foreach ( $this->mParams[$param] as $moduleName ) {
767 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
768 if ( $instance === null ) {
769 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
770 }
771 if ( !$wasPosted && $instance->mustBePosted() ) {
772 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
773 }
774 // Ignore duplicates. TODO 2.0: die()?
775 if ( !array_key_exists( $moduleName, $modules ) ) {
776 $modules[$moduleName] = $instance;
777 }
778 }
779 }
780 }
781
787 private function outputGeneralPageInfo() {
788 $pageSet = $this->getPageSet();
789 $result = $this->getResult();
790
791 // We can't really handle max-result-size failure here, but we need to
792 // check anyway in case someone set the limit stupidly low.
793 $fit = true;
794
795 $values = $pageSet->getNormalizedTitlesAsResult( $result );
796 if ( $values ) {
797 // @phan-suppress-next-line PhanRedundantCondition
798 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
799 }
800 $values = $pageSet->getConvertedTitlesAsResult( $result );
801 if ( $values ) {
802 $fit = $fit && $result->addValue( 'query', 'converted', $values );
803 }
804 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
805 if ( $values ) {
806 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
807 }
808 $values = $pageSet->getRedirectTitlesAsResult( $result );
809 if ( $values ) {
810 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
811 }
812 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
813 if ( $values ) {
814 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
815 }
816
817 // Page elements
818 // Cannot use ApiPageSet::getInvalidTitlesAndRevisions, it does not set $fakeId
819 $pages = [];
820
821 // Report any missing titles
822 foreach ( $pageSet->getMissingPages() as $fakeId => $page ) {
823 $vals = [];
824 $vals['ns'] = $page->getNamespace();
825 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
826 $vals['missing'] = true;
827 $title = $this->titleFactory->newFromPageIdentity( $page );
828 if ( $title->isKnown() ) {
829 $vals['known'] = true;
830 }
831 $pages[$fakeId] = $vals;
832 }
833 // Report any invalid titles
834 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
835 $pages[$fakeId] = $data + [ 'invalid' => true ];
836 }
837 // Report any missing page ids
838 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
839 $pages[$pageid] = [
840 'pageid' => $pageid,
841 'missing' => true,
842 ];
843 }
844 // Report special pages
846 foreach ( $pageSet->getSpecialPages() as $fakeId => $page ) {
847 $vals = [];
848 $vals['ns'] = $page->getNamespace();
849 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
850 $vals['special'] = true;
851 $title = $this->titleFactory->newFromPageReference( $page );
852 if ( !$title->isKnown() ) {
853 $vals['missing'] = true;
854 }
855 $pages[$fakeId] = $vals;
856 }
857
858 // Output general page information for found titles
859 foreach ( $pageSet->getGoodPages() as $pageid => $page ) {
860 $vals = [];
861 $vals['pageid'] = $pageid;
862 $vals['ns'] = $page->getNamespace();
863 $vals['title'] = $this->titleFormatter->getPrefixedText( $page );
864 $pages[$pageid] = $vals;
865 }
866
867 if ( count( $pages ) ) {
868 $pageSet->populateGeneratorData( $pages );
869 ApiResult::setArrayType( $pages, 'BCarray' );
870
871 if ( $this->mParams['indexpageids'] ) {
872 $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
873 // json treats all map keys as strings - converting to match
874 $pageIDs = array_map( 'strval', $pageIDs );
875 ApiResult::setIndexedTagName( $pageIDs, 'id' );
876 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
877 }
878
879 ApiResult::setIndexedTagName( $pages, 'page' );
880 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
881 }
882
883 if ( !$fit ) {
884 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
885 }
886
887 if ( $this->mParams['export'] ) {
888 $this->doExport( $pageSet, $result );
889 }
890 }
891
896 private function doExport( $pageSet, $result ) {
897 $exportTitles = [];
898 $titles = $pageSet->getGoodPages();
899 if ( count( $titles ) ) {
901 foreach ( $titles as $title ) {
902 if ( $this->getAuthority()->authorizeRead( 'read', $title ) ) {
903 $exportTitles[] = $title;
904 }
905 }
906 }
907
908 $exporter = $this->wikiExporterFactory->getWikiExporter( $this->getDB() );
909 $sink = new DumpStringOutput;
910 $exporter->setOutputSink( $sink );
911 $exporter->setSchemaVersion( $this->mParams['exportschema'] );
912 $exporter->openStream();
913 foreach ( $exportTitles as $title ) {
914 $exporter->pageByTitle( $title );
915 }
916 $exporter->closeStream();
917
918 // Don't check the size of exported stuff
919 // It's not continuable, so it would cause more
920 // problems than it'd solve
921 if ( $this->mParams['exportnowrap'] ) {
922 $result->reset();
923 // Raw formatter will handle this
924 $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
925 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
926 $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
927 } else {
928 $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
929 $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
930 }
931 }
932
933 public function getAllowedParams( $flags = 0 ) {
934 $result = [
935 'prop' => [
936 ParamValidator::PARAM_ISMULTI => true,
937 ParamValidator::PARAM_TYPE => 'submodule',
938 ],
939 'list' => [
940 ParamValidator::PARAM_ISMULTI => true,
941 ParamValidator::PARAM_TYPE => 'submodule',
942 ],
943 'meta' => [
944 ParamValidator::PARAM_ISMULTI => true,
945 ParamValidator::PARAM_TYPE => 'submodule',
946 ],
947 'indexpageids' => false,
948 'export' => false,
949 'exportnowrap' => false,
950 'exportschema' => [
951 ParamValidator::PARAM_DEFAULT => WikiExporter::schemaVersion(),
952 ParamValidator::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
953 ],
954 'iwurl' => false,
955 'continue' => [
956 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
957 ],
958 'rawcontinue' => false,
959 ];
960 if ( $flags ) {
961 $result += $this->getPageSet()->getFinalParams( $flags );
962 }
963
964 return $result;
965 }
966
967 public function isReadMode() {
968 // We need to make an exception for certain meta modules that should be
969 // accessible even without the 'read' right. Restrict the exception as
970 // much as possible: no other modules allowed, and no pageset
971 // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
972 // parameters since frameworks might add these unconditionally and they
973 // can't expose anything here.
974 $allowedParams = [ 'rawcontinue' => 1, 'indexpageids' => 1 ];
975 $this->mParams = $this->extractRequestParams();
976 $request = $this->getRequest();
977 foreach ( $this->mParams + $this->getPageSet()->extractRequestParams() as $param => $value ) {
978 $needed = $param === 'meta';
979 if ( !isset( $allowedParams[$param] ) && $request->getCheck( $param ) !== $needed ) {
980 return true;
981 }
982 }
983
984 // Ask each module if it requires read mode. Any true => this returns
985 // true.
986 $modules = [];
987 $this->instantiateModules( $modules, 'meta' );
988 foreach ( $modules as $module ) {
989 if ( $module->isReadMode() ) {
990 return true;
991 }
992 }
993
994 return false;
995 }
996
997 public function isWriteMode() {
998 // Ask each module if it requires write mode. If any require write mode this returns true.
999 $modules = [];
1000 $this->mParams = $this->extractRequestParams();
1001 $this->instantiateModules( $modules, 'list' );
1002 $this->instantiateModules( $modules, 'meta' );
1003 $this->instantiateModules( $modules, 'prop' );
1004 foreach ( $modules as $module ) {
1005 if ( $module->isWriteMode() ) {
1006 return true;
1007 }
1008 }
1009
1010 return false;
1011 }
1012
1013 protected function getExamplesMessages() {
1014 $title = Title::newMainPage()->getPrefixedText();
1015 $mp = rawurlencode( $title );
1016
1017 return [
1018 'action=query&prop=revisions&meta=siteinfo&' .
1019 "titles={$mp}&rvprop=user|comment&continue="
1020 => 'apihelp-query-example-revisions',
1021 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
1022 => 'apihelp-query-example-allpages',
1023 ];
1024 }
1025
1026 public function getHelpUrls() {
1027 return [
1028 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
1029 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
1030 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
1031 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
1032 ];
1033 }
1034}
array $params
The job parameters.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:65
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1540
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:943
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:705
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:1713
getMain()
Get the main module.
Definition ApiBase.php:560
setContinuationManager(ApiContinuationManager $manager=null)
Definition ApiBase.php:728
getResult()
Get the result object.
Definition ApiBase.php:681
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:821
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:172
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:766
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: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:43
isReadMode()
Indicates whether this module requires read rights.
Definition ApiQuery.php:967
isWriteMode()
Indicates whether this module requires write mode.
Definition ApiQuery.php:997
mergeCacheMode( $cacheMode, $modCacheMode)
Update a cache mode string, applying the cache mode of a new module to it.
Definition ApiQuery.php:744
getAllowedParams( $flags=0)
Definition ApiQuery.php:933
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:618
__construct(ApiMain $main, $action, ObjectFactory $objectFactory, WikiExporterFactory $wikiExporterFactory, TitleFormatter $titleFormatter, TitleFactory $titleFactory)
Definition ApiQuery.php:581
getExamplesMessages()
Returns usage examples for this module.
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:626
execute()
Query execution happens in the following steps: #1 Create a PageSet object with any pages requested b...
Definition ApiQuery.php:655
getCustomPrinter()
Definition ApiQuery.php:633
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:79
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.