MediaWiki REL1_35
ApiQuery.php
Go to the documentation of this file.
1<?php
25
37class ApiQuery extends ApiBase {
38
43 private static $QueryPropModules = [
44 'categories' => ApiQueryCategories::class,
45 'categoryinfo' => ApiQueryCategoryInfo::class,
46 'contributors' => ApiQueryContributors::class,
47 'deletedrevisions' => ApiQueryDeletedRevisions::class,
48 'duplicatefiles' => ApiQueryDuplicateFiles::class,
49 'extlinks' => ApiQueryExternalLinks::class,
50 'fileusage' => ApiQueryBacklinksprop::class,
51 'images' => ApiQueryImages::class,
52 'imageinfo' => ApiQueryImageInfo::class,
53 'info' => ApiQueryInfo::class,
54 'links' => ApiQueryLinks::class,
55 'linkshere' => ApiQueryBacklinksprop::class,
56 'iwlinks' => ApiQueryIWLinks::class,
57 'langlinks' => ApiQueryLangLinks::class,
58 'pageprops' => ApiQueryPageProps::class,
59 'redirects' => ApiQueryBacklinksprop::class,
60 'revisions' => ApiQueryRevisions::class,
61 'stashimageinfo' => ApiQueryStashImageInfo::class,
62 'templates' => ApiQueryLinks::class,
63 'transcludedin' => ApiQueryBacklinksprop::class,
64 ];
65
70 private static $QueryListModules = [
71 'allcategories' => ApiQueryAllCategories::class,
72 'alldeletedrevisions' => ApiQueryAllDeletedRevisions::class,
73 'allfileusages' => ApiQueryAllLinks::class,
74 'allimages' => ApiQueryAllImages::class,
75 'alllinks' => ApiQueryAllLinks::class,
76 'allpages' => ApiQueryAllPages::class,
77 'allredirects' => ApiQueryAllLinks::class,
78 'allrevisions' => ApiQueryAllRevisions::class,
79 'mystashedfiles' => ApiQueryMyStashedFiles::class,
80 'alltransclusions' => ApiQueryAllLinks::class,
81 'allusers' => ApiQueryAllUsers::class,
82 'backlinks' => ApiQueryBacklinks::class,
83 'blocks' => ApiQueryBlocks::class,
84 'categorymembers' => ApiQueryCategoryMembers::class,
85 'deletedrevs' => ApiQueryDeletedrevs::class,
86 'embeddedin' => ApiQueryBacklinks::class,
87 'exturlusage' => ApiQueryExtLinksUsage::class,
88 'filearchive' => ApiQueryFilearchive::class,
89 'imageusage' => ApiQueryBacklinks::class,
90 'iwbacklinks' => ApiQueryIWBacklinks::class,
91 'langbacklinks' => ApiQueryLangBacklinks::class,
92 'logevents' => ApiQueryLogEvents::class,
93 'pageswithprop' => ApiQueryPagesWithProp::class,
94 'pagepropnames' => ApiQueryPagePropNames::class,
95 'prefixsearch' => ApiQueryPrefixSearch::class,
96 'protectedtitles' => ApiQueryProtectedTitles::class,
97 'querypage' => ApiQueryQueryPage::class,
98 'random' => ApiQueryRandom::class,
99 'recentchanges' => ApiQueryRecentChanges::class,
100 'search' => ApiQuerySearch::class,
101 'tags' => ApiQueryTags::class,
102 'usercontribs' => ApiQueryUserContribs::class,
103 'users' => ApiQueryUsers::class,
104 'watchlist' => ApiQueryWatchlist::class,
105 'watchlistraw' => ApiQueryWatchlistRaw::class,
106 ];
107
112 private static $QueryMetaModules = [
113 'allmessages' => ApiQueryAllMessages::class,
114 'authmanagerinfo' => ApiQueryAuthManagerInfo::class,
115 'siteinfo' => ApiQuerySiteinfo::class,
116 'userinfo' => ApiQueryUserInfo::class,
117 'filerepoinfo' => ApiQueryFileRepoInfo::class,
118 'tokens' => ApiQueryTokens::class,
119 'languageinfo' => [
120 'class' => ApiQueryLanguageinfo::class,
121 'services' => [
122 'LanguageFactory',
123 'LanguageNameUtils',
124 'LanguageFallback',
125 'LanguageConverterFactory',
126 ],
127 ],
128 ];
129
133 private $mPageSet;
134
135 private $mParams;
136 private $mNamedDB = [];
137 private $mModuleMgr;
138
143 public function __construct( ApiMain $main, $action ) {
144 parent::__construct( $main, $action );
145
146 $this->mModuleMgr = new ApiModuleManager(
147 $this,
148 MediaWikiServices::getInstance()->getObjectFactory()
149 );
150
151 // Allow custom modules to be added in LocalSettings.php
152 $config = $this->getConfig();
153 $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
154 $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
155 $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
156 $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
157 $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
158 $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
159
160 $this->getHookRunner()->onApiQuery__moduleManager( $this->mModuleMgr );
161
162 // Create PageSet that will process titles/pageids/revids/generator
163 $this->mPageSet = new ApiPageSet( $this );
164 }
165
170 public function getModuleManager() {
171 return $this->mModuleMgr;
172 }
173
184 public function getNamedDB( $name, $db, $groups ) {
185 if ( !array_key_exists( $name, $this->mNamedDB ) ) {
186 $this->mNamedDB[$name] = wfGetDB( $db, $groups );
187 }
188
189 return $this->mNamedDB[$name];
190 }
191
196 public function getPageSet() {
197 return $this->mPageSet;
198 }
199
203 public function getCustomPrinter() {
204 // If &exportnowrap is set, use the raw formatter
205 if ( $this->getParameter( 'export' ) &&
206 $this->getParameter( 'exportnowrap' )
207 ) {
208 return new ApiFormatRaw( $this->getMain(),
209 $this->getMain()->createPrinterByName( 'xml' ) );
210 } else {
211 return null;
212 }
213 }
214
225 public function execute() {
226 $this->mParams = $this->extractRequestParams();
227
228 // Instantiate requested modules
229 $allModules = [];
230 $this->instantiateModules( $allModules, 'prop' );
231 $propModules = array_keys( $allModules );
232 $this->instantiateModules( $allModules, 'list' );
233 $this->instantiateModules( $allModules, 'meta' );
234
235 // Filter modules based on continue parameter
236 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
237 $this->setContinuationManager( $continuationManager );
239 $modules = $continuationManager->getRunModules();
240 '@phan-var ApiQueryBase[] $modules';
241
242 if ( !$continuationManager->isGeneratorDone() ) {
243 // Query modules may optimize data requests through the $this->getPageSet()
244 // object by adding extra fields from the page table.
245 foreach ( $modules as $module ) {
246 $module->requestExtraData( $this->mPageSet );
247 }
248 // Populate page/revision information
249 $this->mPageSet->execute();
250 // Record page information (title, namespace, if exists, etc)
251 $this->outputGeneralPageInfo();
252 } else {
253 $this->mPageSet->executeDryRun();
254 }
255
256 $cacheMode = $this->mPageSet->getCacheMode();
257
258 // Execute all unfinished modules
259 foreach ( $modules as $module ) {
260 $params = $module->extractRequestParams();
261 $cacheMode = $this->mergeCacheMode(
262 $cacheMode, $module->getCacheMode( $params ) );
263 $module->execute();
264 $this->getHookRunner()->onAPIQueryAfterExecute( $module );
265 }
266
267 // Set the cache mode
268 $this->getMain()->setCacheMode( $cacheMode );
269
270 // Write the continuation data into the result
271 $this->setContinuationManager( null );
272 if ( $this->mParams['rawcontinue'] ) {
273 $data = $continuationManager->getRawNonContinuation();
274 if ( $data ) {
275 $this->getResult()->addValue( null, 'query-noncontinue', $data,
276 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
277 }
278 $data = $continuationManager->getRawContinuation();
279 if ( $data ) {
280 $this->getResult()->addValue( null, 'query-continue', $data,
281 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
282 }
283 } else {
284 $continuationManager->setContinuationIntoResult( $this->getResult() );
285 }
286 }
287
297 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
298 if ( $modCacheMode === 'anon-public-user-private' ) {
299 if ( $cacheMode !== 'private' ) {
300 $cacheMode = 'anon-public-user-private';
301 }
302 } elseif ( $modCacheMode === 'public' ) {
303 // do nothing, if it's public already it will stay public
304 } else {
305 $cacheMode = 'private';
306 }
307
308 return $cacheMode;
309 }
310
316 private function instantiateModules( &$modules, $param ) {
317 $wasPosted = $this->getRequest()->wasPosted();
318 if ( isset( $this->mParams[$param] ) ) {
319 foreach ( $this->mParams[$param] as $moduleName ) {
320 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
321 if ( $instance === null ) {
322 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
323 }
324 if ( !$wasPosted && $instance->mustBePosted() ) {
325 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
326 }
327 // Ignore duplicates. TODO 2.0: die()?
328 if ( !array_key_exists( $moduleName, $modules ) ) {
329 $modules[$moduleName] = $instance;
330 }
331 }
332 }
333 }
334
340 private function outputGeneralPageInfo() {
341 $pageSet = $this->getPageSet();
342 $result = $this->getResult();
343
344 // We can't really handle max-result-size failure here, but we need to
345 // check anyway in case someone set the limit stupidly low.
346 $fit = true;
347
348 $values = $pageSet->getNormalizedTitlesAsResult( $result );
349 if ( $values ) {
350 // @phan-suppress-next-line PhanRedundantCondition
351 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
352 }
353 $values = $pageSet->getConvertedTitlesAsResult( $result );
354 if ( $values ) {
355 $fit = $fit && $result->addValue( 'query', 'converted', $values );
356 }
357 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
358 if ( $values ) {
359 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
360 }
361 $values = $pageSet->getRedirectTitlesAsResult( $result );
362 if ( $values ) {
363 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
364 }
365 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
366 if ( $values ) {
367 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
368 }
369
370 // Page elements
371 $pages = [];
372
373 // Report any missing titles
374 foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
375 $vals = [];
376 ApiQueryBase::addTitleInfo( $vals, $title );
377 $vals['missing'] = true;
378 if ( $title->isKnown() ) {
379 $vals['known'] = true;
380 }
381 $pages[$fakeId] = $vals;
382 }
383 // Report any invalid titles
384 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
385 $pages[$fakeId] = $data + [ 'invalid' => true ];
386 }
387 // Report any missing page ids
388 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
389 $pages[$pageid] = [
390 'pageid' => $pageid,
391 'missing' => true,
392 ];
393 }
394 // Report special pages
396 foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
397 $vals = [];
398 ApiQueryBase::addTitleInfo( $vals, $title );
399 $vals['special'] = true;
400 if ( !$title->isKnown() ) {
401 $vals['missing'] = true;
402 }
403 $pages[$fakeId] = $vals;
404 }
405
406 // Output general page information for found titles
407 foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
408 $vals = [];
409 $vals['pageid'] = $pageid;
410 ApiQueryBase::addTitleInfo( $vals, $title );
411 $pages[$pageid] = $vals;
412 }
413
414 if ( count( $pages ) ) {
415 $pageSet->populateGeneratorData( $pages );
416 ApiResult::setArrayType( $pages, 'BCarray' );
417
418 if ( $this->mParams['indexpageids'] ) {
419 $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
420 // json treats all map keys as strings - converting to match
421 $pageIDs = array_map( 'strval', $pageIDs );
422 ApiResult::setIndexedTagName( $pageIDs, 'id' );
423 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
424 }
425
426 ApiResult::setIndexedTagName( $pages, 'page' );
427 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
428 }
429
430 if ( !$fit ) {
431 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
432 }
433
434 if ( $this->mParams['export'] ) {
435 $this->doExport( $pageSet, $result );
436 }
437 }
438
443 private function doExport( $pageSet, $result ) {
444 $exportTitles = [];
445 $titles = $pageSet->getGoodTitles();
446 if ( count( $titles ) ) {
448 foreach ( $titles as $title ) {
449 if ( $this->getPermissionManager()->userCan( 'read', $this->getUser(), $title ) ) {
450 $exportTitles[] = $title;
451 }
452 }
453 }
454
455 $exporter = new WikiExporter( $this->getDB() );
456 $sink = new DumpStringOutput;
457 $exporter->setOutputSink( $sink );
458 $exporter->setSchemaVersion( $this->mParams['exportschema'] );
459 $exporter->openStream();
460 foreach ( $exportTitles as $title ) {
461 $exporter->pageByTitle( $title );
462 }
463 $exporter->closeStream();
464
465 // Don't check the size of exported stuff
466 // It's not continuable, so it would cause more
467 // problems than it'd solve
468 if ( $this->mParams['exportnowrap'] ) {
469 $result->reset();
470 // Raw formatter will handle this
471 $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
472 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
473 $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
474 } else {
475 $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
476 $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
477 }
478 }
479
480 public function getAllowedParams( $flags = 0 ) {
481 $result = [
482 'prop' => [
484 ApiBase::PARAM_TYPE => 'submodule',
485 ],
486 'list' => [
488 ApiBase::PARAM_TYPE => 'submodule',
489 ],
490 'meta' => [
492 ApiBase::PARAM_TYPE => 'submodule',
493 ],
494 'indexpageids' => false,
495 'export' => false,
496 'exportnowrap' => false,
497 'exportschema' => [
499 ApiBase::PARAM_TYPE => XmlDumpWriter::$supportedSchemas,
500 ],
501 'iwurl' => false,
502 'continue' => [
503 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
504 ],
505 'rawcontinue' => false,
506 ];
507 if ( $flags ) {
508 $result += $this->getPageSet()->getFinalParams( $flags );
509 }
510
511 return $result;
512 }
513
514 public function isReadMode() {
515 // We need to make an exception for certain meta modules that should be
516 // accessible even without the 'read' right. Restrict the exception as
517 // much as possible: no other modules allowed, and no pageset
518 // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
519 // parameters since frameworks might add these unconditionally and they
520 // can't expose anything here.
521 $allowedParams = [ 'rawcontinue' => 1, 'indexpageids' => 1 ];
522 $this->mParams = $this->extractRequestParams();
523 $request = $this->getRequest();
524 foreach ( $this->mParams + $this->getPageSet()->extractRequestParams() as $param => $value ) {
525 $needed = $param === 'meta';
526 if ( !isset( $allowedParams[$param] ) && $request->getCheck( $param ) !== $needed ) {
527 return true;
528 }
529 }
530
531 // Ask each module if it requires read mode. Any true => this returns
532 // true.
533 $modules = [];
534 $this->instantiateModules( $modules, 'meta' );
535 foreach ( $modules as $module ) {
536 if ( $module->isReadMode() ) {
537 return true;
538 }
539 }
540
541 return false;
542 }
543
544 protected function getExamplesMessages() {
545 return [
546 'action=query&prop=revisions&meta=siteinfo&' .
547 'titles=Main%20Page&rvprop=user|comment&continue='
548 => 'apihelp-query-example-revisions',
549 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
550 => 'apihelp-query-example-allpages',
551 ];
552 }
553
554 public function getHelpUrls() {
555 return [
556 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
557 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
558 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
559 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
560 ];
561 }
562}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:52
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1437
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:892
getDB()
Gets a default replica DB connection object Stable to override.
Definition ApiBase.php:650
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:1599
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1629
getMain()
Get the main module.
Definition ApiBase.php:515
const PARAM_TYPE
Definition ApiBase.php:78
const PARAM_DFLT
Definition ApiBase.php:70
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:692
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition ApiBase.php:676
getResult()
Get the result object.
Definition ApiBase.php:620
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:772
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:717
const PARAM_ISMULTI
Definition ApiBase.php:74
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:47
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:37
static array $QueryListModules
List of Api Query list modules.
Definition ApiQuery.php:70
isReadMode()
Indicates whether this module requires read rights Stable to override.
Definition ApiQuery.php:514
instantiateModules(&$modules, $param)
Create instances of all modules requested by the client.
Definition ApiQuery.php:316
static array $QueryPropModules
List of Api Query prop modules.
Definition ApiQuery.php:43
mergeCacheMode( $cacheMode, $modCacheMode)
Update a cache mode string, applying the cache mode of a new module to it.
Definition ApiQuery.php:297
getAllowedParams( $flags=0)
Definition ApiQuery.php:480
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:170
ApiPageSet $mPageSet
Definition ApiQuery.php:133
__construct(ApiMain $main, $action)
Definition ApiQuery.php:143
outputGeneralPageInfo()
Appends an element for each page in the current pageSet with the most general information (id,...
Definition ApiQuery.php:340
getExamplesMessages()
Returns usage examples for this module.
Definition ApiQuery.php:544
doExport( $pageSet, $result)
Definition ApiQuery.php:443
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiQuery.php:554
getPageSet()
Gets the set of pages the user has requested (or generated)
Definition ApiQuery.php:196
execute()
Query execution happens in the following steps: #1 Create a PageSet object with any pages requested b...
Definition ApiQuery.php:225
static array $QueryMetaModules
List of Api Query meta modules.
Definition ApiQuery.php:112
getNamedDB( $name, $db, $groups)
Get the query database connection with the given name.
Definition ApiQuery.php:184
getCustomPrinter()
Definition ApiQuery.php:203
getUser()
Stable to override.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:42
static schemaVersion()
Returns the default export schema version, as defined by $wgXmlDumpSchemaVersion.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
return true
Definition router.php:92