Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / api / ApiQuery.php
blob830911b85d7ff8bf1c1fb55a465d473d35d9462d
1 <?php
2 /**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
23 namespace MediaWiki\Api;
25 use DumpStringOutput;
26 use MediaWiki\Export\WikiExporterFactory;
27 use MediaWiki\MainConfigNames;
28 use MediaWiki\MediaWikiServices;
29 use MediaWiki\Title\Title;
30 use MediaWiki\Title\TitleFactory;
31 use MediaWiki\Title\TitleFormatter;
32 use WikiExporter;
33 use Wikimedia\ObjectFactory\ObjectFactory;
34 use Wikimedia\ParamValidator\ParamValidator;
35 use XmlDumpWriter;
37 /**
38 * This is the main query class. It behaves similar to ApiMain: based on the
39 * parameters given, it will create a list of titles to work on (an ApiPageSet
40 * object), instantiate and execute various property/list/meta modules, and
41 * assemble all resulting data into a single ApiResult object.
43 * In generator mode, a generator will be executed first to populate a second
44 * ApiPageSet object, and that object will be used for all subsequent modules.
46 * @ingroup API
48 class ApiQuery extends ApiBase {
50 /**
51 * List of Api Query prop modules
53 private const QUERY_PROP_MODULES = [
54 'categories' => [
55 'class' => ApiQueryCategories::class,
57 'categoryinfo' => [
58 'class' => ApiQueryCategoryInfo::class,
60 'contributors' => [
61 'class' => ApiQueryContributors::class,
62 'services' => [
63 'RevisionStore',
64 'ActorMigration',
65 'UserGroupManager',
66 'GroupPermissionsLookup',
67 'TempUserConfig'
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',
87 'duplicatefiles' => [
88 'class' => ApiQueryDuplicateFiles::class,
89 'services' => [
90 'RepoGroup',
93 'extlinks' => [
94 'class' => ApiQueryExternalLinks::class,
95 'services' => [
96 'UrlUtils',
99 'fileusage' => [
100 'class' => ApiQueryBacklinksprop::class,
101 'services' => [
102 // Same as for linkshere, redirects, transcludedin
103 'LinksMigration',
106 'images' => [
107 'class' => ApiQueryImages::class,
109 'imageinfo' => [
110 'class' => ApiQueryImageInfo::class,
111 'services' => [
112 // Same as for stashimageinfo
113 'RepoGroup',
114 'ContentLanguage',
115 'BadFileLookup',
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',
138 'links' => [
139 'class' => ApiQueryLinks::class,
140 'services' => [
141 // Same as for templates
142 'LinkBatchFactory',
143 'LinksMigration',
146 'linkshere' => [
147 'class' => ApiQueryBacklinksprop::class,
148 'services' => [
149 // Same as for fileusage, redirects, transcludedin
150 'LinksMigration',
153 'iwlinks' => [
154 'class' => ApiQueryIWLinks::class,
155 'services' => [
156 'UrlUtils',
159 'langlinks' => [
160 'class' => ApiQueryLangLinks::class,
161 'services' => [
162 'LanguageNameUtils',
163 'ContentLanguage',
164 'UrlUtils',
167 'pageprops' => [
168 'class' => ApiQueryPageProps::class,
169 'services' => [
170 'PageProps',
173 'redirects' => [
174 'class' => ApiQueryBacklinksprop::class,
175 'services' => [
176 // Same as for fileusage, linkshere, transcludedin
177 'LinksMigration',
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',
198 'stashimageinfo' => [
199 'class' => ApiQueryStashImageInfo::class,
200 'services' => [
201 // Same as for imageinfo
202 'RepoGroup',
203 'ContentLanguage',
204 'BadFileLookup',
207 'templates' => [
208 'class' => ApiQueryLinks::class,
209 'services' => [
210 // Same as for links
211 'LinkBatchFactory',
212 'LinksMigration',
215 'transcludedin' => [
216 'class' => ApiQueryBacklinksprop::class,
217 'services' => [
218 // Same as for fileusage, linkshere, redirects
219 'LinksMigration',
225 * List of Api Query list modules
227 private const QUERY_LIST_MODULES = [
228 'allcategories' => [
229 'class' => ApiQueryAllCategories::class,
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',
248 'allfileusages' => [
249 'class' => ApiQueryAllLinks::class,
250 'services' => [
251 // Same as for alllinks, allredirects, alltransclusions
252 'NamespaceInfo',
253 'GenderCache',
254 'LinksMigration',
257 'allimages' => [
258 'class' => ApiQueryAllImages::class,
259 'services' => [
260 'RepoGroup',
261 'GroupPermissionsLookup',
264 'alllinks' => [
265 'class' => ApiQueryAllLinks::class,
266 'services' => [
267 // Same as for allfileusages, allredirects, alltransclusions
268 'NamespaceInfo',
269 'GenderCache',
270 'LinksMigration',
273 'allpages' => [
274 'class' => ApiQueryAllPages::class,
275 'services' => [
276 'NamespaceInfo',
277 'GenderCache',
278 'RestrictionStore',
281 'allredirects' => [
282 'class' => ApiQueryAllLinks::class,
283 'services' => [
284 // Same as for allfileusages, alllinks, alltransclusions
285 'NamespaceInfo',
286 'GenderCache',
287 'LinksMigration',
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',
307 'mystashedfiles' => [
308 'class' => ApiQueryMyStashedFiles::class,
310 'alltransclusions' => [
311 'class' => ApiQueryAllLinks::class,
312 'services' => [
313 // Same as for allfileusages, alllinks, allredirects
314 'NamespaceInfo',
315 'GenderCache',
316 'LinksMigration',
319 'allusers' => [
320 'class' => ApiQueryAllUsers::class,
321 'services' => [
322 'UserFactory',
323 'UserGroupManager',
324 'GroupPermissionsLookup',
325 'ContentLanguage',
326 'TempUserConfig',
329 'backlinks' => [
330 'class' => ApiQueryBacklinks::class,
331 'services' => [
332 'LinksMigration',
335 'blocks' => [
336 'class' => ApiQueryBlocks::class,
337 'services' => [
338 'DatabaseBlockStore',
339 'BlockActionInfo',
340 'BlockRestrictionStore',
341 'CommentStore',
342 'HideUserUtils',
343 'CommentFormatter',
346 'categorymembers' => [
347 'class' => ApiQueryCategoryMembers::class,
348 'services' => [
349 'CollationFactory',
352 'deletedrevs' => [
353 'class' => ApiQueryDeletedrevs::class,
354 'services' => [
355 'CommentStore',
356 'RowCommentFormatter',
357 'RevisionStore',
358 'ChangeTagDefStore',
359 'ChangeTagsStore',
360 'LinkBatchFactory',
363 'embeddedin' => [
364 'class' => ApiQueryBacklinks::class,
365 'services' => [
366 'LinksMigration',
369 'exturlusage' => [
370 'class' => ApiQueryExtLinksUsage::class,
371 'services' => [
372 'UrlUtils',
375 'filearchive' => [
376 'class' => ApiQueryFilearchive::class,
377 'services' => [
378 'CommentStore',
379 'CommentFormatter',
382 'imageusage' => [
383 'class' => ApiQueryBacklinks::class,
384 'services' => [
385 'LinksMigration',
388 'iwbacklinks' => [
389 'class' => ApiQueryIWBacklinks::class,
391 'langbacklinks' => [
392 'class' => ApiQueryLangBacklinks::class,
394 'logevents' => [
395 'class' => ApiQueryLogEvents::class,
396 'services' => [
397 'CommentStore',
398 'RowCommentFormatter',
399 'ChangeTagDefStore',
400 'ChangeTagsStore',
401 'UserNameUtils',
402 'LogFormatterFactory',
405 'pageswithprop' => [
406 'class' => ApiQueryPagesWithProp::class,
408 'pagepropnames' => [
409 'class' => ApiQueryPagePropNames::class,
411 'prefixsearch' => [
412 'class' => ApiQueryPrefixSearch::class,
413 'services' => [
414 'SearchEngineConfig',
415 'SearchEngineFactory',
418 'protectedtitles' => [
419 'class' => ApiQueryProtectedTitles::class,
420 'services' => [
421 'CommentStore',
422 'RowCommentFormatter'
425 'querypage' => [
426 'class' => ApiQueryQueryPage::class,
427 'services' => [
428 'SpecialPageFactory',
431 'random' => [
432 'class' => ApiQueryRandom::class,
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',
448 'search' => [
449 'class' => ApiQuerySearch::class,
450 'services' => [
451 'SearchEngineConfig',
452 'SearchEngineFactory',
453 'TitleMatcher',
456 'tags' => [
457 'class' => ApiQueryTags::class,
458 'services' => [
459 'ChangeTagsStore',
462 'usercontribs' => [
463 'class' => ApiQueryUserContribs::class,
464 'services' => [
465 'CommentStore',
466 'UserIdentityLookup',
467 'UserNameUtils',
468 'RevisionStore',
469 'ChangeTagDefStore',
470 'ChangeTagsStore',
471 'ActorMigration',
472 'CommentFormatter',
475 'users' => [
476 'class' => ApiQueryUsers::class,
477 'services' => [
478 'UserNameUtils',
479 'UserFactory',
480 'UserGroupManager',
481 'GenderCache',
482 'AuthManager',
485 'watchlist' => [
486 'class' => ApiQueryWatchlist::class,
487 'services' => [
488 'CommentStore',
489 'WatchedItemQueryService',
490 'ContentLanguage',
491 'NamespaceInfo',
492 'GenderCache',
493 'CommentFormatter',
494 'TempUserConfig',
495 'LogFormatterFactory',
498 'watchlistraw' => [
499 'class' => ApiQueryWatchlistRaw::class,
500 'services' => [
501 'WatchedItemQueryService',
502 'ContentLanguage',
503 'NamespaceInfo',
504 'GenderCache',
510 * List of Api Query meta modules
512 private const QUERY_META_MODULES = [
513 'allmessages' => [
514 'class' => ApiQueryAllMessages::class,
515 'services' => [
516 'ContentLanguage',
517 'LanguageFactory',
518 'LanguageNameUtils',
519 'LocalisationCache',
520 'MessageCache',
523 'authmanagerinfo' => [
524 'class' => ApiQueryAuthManagerInfo::class,
525 'services' => [
526 'AuthManager',
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'
551 'userinfo' => [
552 'class' => ApiQueryUserInfo::class,
553 'services' => [
554 'TalkPageNotificationManager',
555 'WatchedItemStore',
556 'UserEditTracker',
557 'UserOptionsLookup',
558 'UserGroupManager',
561 'filerepoinfo' => [
562 'class' => ApiQueryFileRepoInfo::class,
563 'services' => [
564 'RepoGroup',
567 'tokens' => [
568 'class' => ApiQueryTokens::class,
570 'languageinfo' => [
571 'class' => ApiQueryLanguageinfo::class,
572 'services' => [
573 'LanguageFactory',
574 'LanguageNameUtils',
575 'LanguageFallback',
576 'LanguageConverterFactory',
582 * @var ApiPageSet
584 private $mPageSet;
586 /** @var array */
587 private $mParams;
588 /** @var ApiModuleManager */
589 private $mModuleMgr;
591 private WikiExporterFactory $wikiExporterFactory;
592 private TitleFormatter $titleFormatter;
593 private TitleFactory $titleFactory;
595 public function __construct(
596 ApiMain $main,
597 string $action,
598 ObjectFactory $objectFactory,
599 WikiExporterFactory $wikiExporterFactory,
600 TitleFormatter $titleFormatter,
601 TitleFactory $titleFactory
603 parent::__construct( $main, $action );
605 $this->mModuleMgr = new ApiModuleManager(
606 $this,
607 $objectFactory
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' );
619 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
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;
629 * Overrides to return this instance's module manager.
630 * @return ApiModuleManager
632 public function getModuleManager() {
633 return $this->mModuleMgr;
637 * Gets the set of pages the user has requested (or generated)
638 * @return ApiPageSet
640 public function getPageSet() {
641 return $this->mPageSet;
645 * @return ApiFormatRaw|null
647 public function getCustomPrinter() {
648 // If &exportnowrap is set, use the raw formatter
649 if ( $this->getParameter( 'export' ) &&
650 $this->getParameter( 'exportnowrap' )
652 return new ApiFormatRaw( $this->getMain(),
653 $this->getMain()->createPrinterByName( 'xml' ) );
654 } else {
655 return null;
660 * Query execution happens in the following steps:
661 * #1 Create a PageSet object with any pages requested by the user
662 * #2 If using a generator, execute it to get a new ApiPageSet object
663 * #3 Instantiate all requested modules.
664 * This way the PageSet object will know what shared data is required,
665 * and minimize DB calls.
666 * #4 Output all normalization and redirect resolution information
667 * #5 Execute all requested modules
669 public function execute() {
670 $this->mParams = $this->extractRequestParams();
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' );
679 // Filter modules based on continue parameter
680 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
681 $this->setContinuationManager( $continuationManager );
682 /** @var ApiQueryBase[] $modules */
683 $modules = $continuationManager->getRunModules();
684 '@phan-var ApiQueryBase[] $modules';
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 );
692 $statsFactory = MediaWikiServices::getInstance()->getStatsFactory();
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();
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();
714 $cacheMode = $this->mPageSet->getCacheMode();
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();
724 $params = $module->extractRequestParams();
725 $cacheMode = $this->mergeCacheMode(
726 $cacheMode, $module->getCacheMode( $params ) );
727 $module->execute();
729 $timer->stop();
731 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
734 // Set the cache mode
735 $this->getMain()->setCacheMode( $cacheMode );
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,
743 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
745 $data = $continuationManager->getRawContinuation();
746 if ( $data ) {
747 $this->getResult()->addValue( null, 'query-continue', $data,
748 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
750 } else {
751 $continuationManager->setContinuationIntoResult( $this->getResult() );
756 * Update a cache mode string, applying the cache mode of a new module to it.
757 * The cache mode may increase in the level of privacy, but public modules
758 * added to private data do not decrease the level of privacy.
760 * @param string $cacheMode
761 * @param string $modCacheMode
762 * @return string
764 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
765 if ( $modCacheMode === 'anon-public-user-private' ) {
766 if ( $cacheMode !== 'private' ) {
767 $cacheMode = 'anon-public-user-private';
769 } elseif ( $modCacheMode === 'public' ) {
770 // do nothing, if it's public already it will stay public
771 } else {
772 $cacheMode = 'private';
775 return $cacheMode;
779 * Create instances of all modules requested by the client
780 * @param array &$modules To append instantiated modules to
781 * @param string $param Parameter name to read modules from
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' );
791 if ( !$wasPosted && $instance->mustBePosted() ) {
792 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
794 // Ignore duplicates. TODO 2.0: die()?
795 if ( !array_key_exists( $moduleName, $modules ) ) {
796 $modules[$moduleName] = $instance;
803 * Appends an element for each page in the current pageSet with the
804 * most general information (id, title), plus any title normalizations
805 * and missing or invalid title/pageids/revids.
807 private function outputGeneralPageInfo() {
808 $pageSet = $this->getPageSet();
809 $result = $this->getResult();
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;
815 $values = $pageSet->getNormalizedTitlesAsResult( $result );
816 if ( $values ) {
817 // @phan-suppress-next-line PhanRedundantCondition
818 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
820 $values = $pageSet->getConvertedTitlesAsResult( $result );
821 if ( $values ) {
822 $fit = $fit && $result->addValue( 'query', 'converted', $values );
824 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
825 if ( $values ) {
826 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
828 $values = $pageSet->getRedirectTitlesAsResult( $result );
829 if ( $values ) {
830 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
832 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
833 if ( $values ) {
834 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
837 // Page elements
838 // Cannot use ApiPageSet::getInvalidTitlesAndRevisions, it does not set $fakeId
839 $pages = [];
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;
851 $pages[$fakeId] = $vals;
853 // Report any invalid titles
854 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
855 $pages[$fakeId] = $data + [ 'invalid' => true ];
857 // Report any missing page ids
858 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
859 $pages[$pageid] = [
860 'pageid' => $pageid,
861 'missing' => true,
864 // Report special pages
865 /** @var \MediaWiki\Page\PageReference $page */
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;
875 $pages[$fakeId] = $vals;
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;
887 if ( count( $pages ) ) {
888 $pageSet->populateGeneratorData( $pages );
889 ApiResult::setArrayType( $pages, 'BCarray' );
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 );
899 ApiResult::setIndexedTagName( $pages, 'page' );
900 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
903 if ( !$fit ) {
904 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
907 if ( $this->mParams['export'] ) {
908 $this->doExport( $pageSet, $result );
913 * @param ApiPageSet $pageSet Pages to be exported
914 * @param ApiResult $result Result to output to
916 private function doExport( $pageSet, $result ) {
917 $exportTitles = [];
918 $titles = $pageSet->getGoodPages();
919 if ( count( $titles ) ) {
920 /** @var Title $title */
921 foreach ( $titles as $title ) {
922 if ( $this->getAuthority()->authorizeRead( 'read', $title ) ) {
923 $exportTitles[] = $title;
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 );
936 $exporter->closeStream();
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' ] );
953 public function getAllowedParams( $flags = 0 ) {
954 $result = [
955 'prop' => [
956 ParamValidator::PARAM_ISMULTI => true,
957 ParamValidator::PARAM_TYPE => 'submodule',
959 'list' => [
960 ParamValidator::PARAM_ISMULTI => true,
961 ParamValidator::PARAM_TYPE => 'submodule',
963 'meta' => [
964 ParamValidator::PARAM_ISMULTI => true,
965 ParamValidator::PARAM_TYPE => 'submodule',
967 'indexpageids' => false,
968 'export' => false,
969 'exportnowrap' => false,
970 'exportschema' => [
971 ParamValidator::PARAM_DEFAULT => WikiExporter::schemaVersion(),
972 ParamValidator::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
974 'iwurl' => false,
975 'continue' => [
976 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
978 'rawcontinue' => false,
980 if ( $flags ) {
981 $result += $this->getPageSet()->getFinalParams( $flags );
984 return $result;
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;
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;
1014 return false;
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;
1030 return false;
1033 protected function getExamplesMessages() {
1034 $title = Title::newMainPage()->getPrefixedText();
1035 $mp = rawurlencode( $title );
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',
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',
1056 /** @deprecated class alias since 1.43 */
1057 class_alias( ApiQuery::class, 'ApiQuery' );