MediaWiki REL1_34
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' => ApiQueryLanguageinfo::class,
120 ];
121
125 private $mPageSet;
126
127 private $mParams;
128 private $mNamedDB = [];
129 private $mModuleMgr;
130
135 public function __construct( ApiMain $main, $action ) {
136 parent::__construct( $main, $action );
137
138 $this->mModuleMgr = new ApiModuleManager(
139 $this,
140 MediaWikiServices::getInstance()->getObjectFactory()
141 );
142
143 // Allow custom modules to be added in LocalSettings.php
144 $config = $this->getConfig();
145 $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
146 $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
147 $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
148 $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
149 $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
150 $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
151
152 Hooks::run( 'ApiQuery::moduleManager', [ $this->mModuleMgr ] );
153
154 // Create PageSet that will process titles/pageids/revids/generator
155 $this->mPageSet = new ApiPageSet( $this );
156 }
157
162 public function getModuleManager() {
163 return $this->mModuleMgr;
164 }
165
176 public function getNamedDB( $name, $db, $groups ) {
177 if ( !array_key_exists( $name, $this->mNamedDB ) ) {
178 $this->mNamedDB[$name] = wfGetDB( $db, $groups );
179 }
180
181 return $this->mNamedDB[$name];
182 }
183
188 public function getPageSet() {
189 return $this->mPageSet;
190 }
191
195 public function getCustomPrinter() {
196 // If &exportnowrap is set, use the raw formatter
197 if ( $this->getParameter( 'export' ) &&
198 $this->getParameter( 'exportnowrap' )
199 ) {
200 return new ApiFormatRaw( $this->getMain(),
201 $this->getMain()->createPrinterByName( 'xml' ) );
202 } else {
203 return null;
204 }
205 }
206
217 public function execute() {
218 $this->mParams = $this->extractRequestParams();
219
220 // Instantiate requested modules
221 $allModules = [];
222 $this->instantiateModules( $allModules, 'prop' );
223 $propModules = array_keys( $allModules );
224 $this->instantiateModules( $allModules, 'list' );
225 $this->instantiateModules( $allModules, 'meta' );
226
227 // Filter modules based on continue parameter
228 $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
229 $this->setContinuationManager( $continuationManager );
231 $modules = $continuationManager->getRunModules();
232 '@phan-var ApiQueryBase[] $modules';
233
234 if ( !$continuationManager->isGeneratorDone() ) {
235 // Query modules may optimize data requests through the $this->getPageSet()
236 // object by adding extra fields from the page table.
237 foreach ( $modules as $module ) {
238 $module->requestExtraData( $this->mPageSet );
239 }
240 // Populate page/revision information
241 $this->mPageSet->execute();
242 // Record page information (title, namespace, if exists, etc)
243 $this->outputGeneralPageInfo();
244 } else {
245 $this->mPageSet->executeDryRun();
246 }
247
248 $cacheMode = $this->mPageSet->getCacheMode();
249
250 // Execute all unfinished modules
251 foreach ( $modules as $module ) {
252 $params = $module->extractRequestParams();
253 $cacheMode = $this->mergeCacheMode(
254 $cacheMode, $module->getCacheMode( $params ) );
255 $module->execute();
256 Hooks::run( 'APIQueryAfterExecute', [ &$module ] );
257 }
258
259 // Set the cache mode
260 $this->getMain()->setCacheMode( $cacheMode );
261
262 // Write the continuation data into the result
263 $this->setContinuationManager( null );
264 if ( $this->mParams['rawcontinue'] ) {
265 $data = $continuationManager->getRawNonContinuation();
266 if ( $data ) {
267 $this->getResult()->addValue( null, 'query-noncontinue', $data,
268 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
269 }
270 $data = $continuationManager->getRawContinuation();
271 if ( $data ) {
272 $this->getResult()->addValue( null, 'query-continue', $data,
273 ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
274 }
275 } else {
276 $continuationManager->setContinuationIntoResult( $this->getResult() );
277 }
278 }
279
289 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
290 if ( $modCacheMode === 'anon-public-user-private' ) {
291 if ( $cacheMode !== 'private' ) {
292 $cacheMode = 'anon-public-user-private';
293 }
294 } elseif ( $modCacheMode === 'public' ) {
295 // do nothing, if it's public already it will stay public
296 } else {
297 $cacheMode = 'private';
298 }
299
300 return $cacheMode;
301 }
302
308 private function instantiateModules( &$modules, $param ) {
309 $wasPosted = $this->getRequest()->wasPosted();
310 if ( isset( $this->mParams[$param] ) ) {
311 foreach ( $this->mParams[$param] as $moduleName ) {
312 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
313 if ( $instance === null ) {
314 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
315 }
316 if ( !$wasPosted && $instance->mustBePosted() ) {
317 $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
318 }
319 // Ignore duplicates. TODO 2.0: die()?
320 if ( !array_key_exists( $moduleName, $modules ) ) {
321 $modules[$moduleName] = $instance;
322 }
323 }
324 }
325 }
326
332 private function outputGeneralPageInfo() {
333 $pageSet = $this->getPageSet();
334 $result = $this->getResult();
335
336 // We can't really handle max-result-size failure here, but we need to
337 // check anyway in case someone set the limit stupidly low.
338 $fit = true;
339
340 $values = $pageSet->getNormalizedTitlesAsResult( $result );
341 if ( $values ) {
342 $fit = $fit && $result->addValue( 'query', 'normalized', $values );
343 }
344 $values = $pageSet->getConvertedTitlesAsResult( $result );
345 if ( $values ) {
346 $fit = $fit && $result->addValue( 'query', 'converted', $values );
347 }
348 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
349 if ( $values ) {
350 $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
351 }
352 $values = $pageSet->getRedirectTitlesAsResult( $result );
353 if ( $values ) {
354 $fit = $fit && $result->addValue( 'query', 'redirects', $values );
355 }
356 $values = $pageSet->getMissingRevisionIDsAsResult( $result );
357 if ( $values ) {
358 $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
359 }
360
361 // Page elements
362 $pages = [];
363
364 // Report any missing titles
365 foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
366 $vals = [];
367 ApiQueryBase::addTitleInfo( $vals, $title );
368 $vals['missing'] = true;
369 if ( $title->isKnown() ) {
370 $vals['known'] = true;
371 }
372 $pages[$fakeId] = $vals;
373 }
374 // Report any invalid titles
375 foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
376 $pages[$fakeId] = $data + [ 'invalid' => true ];
377 }
378 // Report any missing page ids
379 foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
380 $pages[$pageid] = [
381 'pageid' => $pageid,
382 'missing' => true,
383 ];
384 }
385 // Report special pages
387 foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
388 $vals = [];
389 ApiQueryBase::addTitleInfo( $vals, $title );
390 $vals['special'] = true;
391 if ( !$title->isKnown() ) {
392 $vals['missing'] = true;
393 }
394 $pages[$fakeId] = $vals;
395 }
396
397 // Output general page information for found titles
398 foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
399 $vals = [];
400 $vals['pageid'] = $pageid;
401 ApiQueryBase::addTitleInfo( $vals, $title );
402 $pages[$pageid] = $vals;
403 }
404
405 if ( count( $pages ) ) {
406 $pageSet->populateGeneratorData( $pages );
407 ApiResult::setArrayType( $pages, 'BCarray' );
408
409 if ( $this->mParams['indexpageids'] ) {
410 $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
411 // json treats all map keys as strings - converting to match
412 $pageIDs = array_map( 'strval', $pageIDs );
413 ApiResult::setIndexedTagName( $pageIDs, 'id' );
414 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
415 }
416
417 ApiResult::setIndexedTagName( $pages, 'page' );
418 $fit = $fit && $result->addValue( 'query', 'pages', $pages );
419 }
420
421 if ( !$fit ) {
422 $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
423 }
424
425 if ( $this->mParams['export'] ) {
426 $this->doExport( $pageSet, $result );
427 }
428 }
429
434 private function doExport( $pageSet, $result ) {
435 $exportTitles = [];
436 $titles = $pageSet->getGoodTitles();
437 if ( count( $titles ) ) {
439 foreach ( $titles as $title ) {
440 if ( $this->getPermissionManager()->userCan( 'read', $this->getUser(), $title ) ) {
441 $exportTitles[] = $title;
442 }
443 }
444 }
445
446 $exporter = new WikiExporter( $this->getDB() );
447 $sink = new DumpStringOutput;
448 $exporter->setOutputSink( $sink );
449 $exporter->setSchemaVersion( $this->mParams['exportschema'] );
450 $exporter->openStream();
451 foreach ( $exportTitles as $title ) {
452 $exporter->pageByTitle( $title );
453 }
454 $exporter->closeStream();
455
456 // Don't check the size of exported stuff
457 // It's not continuable, so it would cause more
458 // problems than it'd solve
459 if ( $this->mParams['exportnowrap'] ) {
460 $result->reset();
461 // Raw formatter will handle this
462 $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
463 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
464 $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
465 } else {
466 $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
467 $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
468 }
469 }
470
471 public function getAllowedParams( $flags = 0 ) {
472 $result = [
473 'prop' => [
475 ApiBase::PARAM_TYPE => 'submodule',
476 ],
477 'list' => [
479 ApiBase::PARAM_TYPE => 'submodule',
480 ],
481 'meta' => [
483 ApiBase::PARAM_TYPE => 'submodule',
484 ],
485 'indexpageids' => false,
486 'export' => false,
487 'exportnowrap' => false,
488 'exportschema' => [
491 ],
492 'iwurl' => false,
493 'continue' => [
494 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
495 ],
496 'rawcontinue' => false,
497 ];
498 if ( $flags ) {
499 $result += $this->getPageSet()->getFinalParams( $flags );
500 }
501
502 return $result;
503 }
504
505 public function isReadMode() {
506 // We need to make an exception for certain meta modules that should be
507 // accessible even without the 'read' right. Restrict the exception as
508 // much as possible: no other modules allowed, and no pageset
509 // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
510 // parameters since frameworks might add these unconditionally and they
511 // can't expose anything here.
512 $allowedParams = [ 'rawcontinue' => 1, 'indexpageids' => 1 ];
513 $this->mParams = $this->extractRequestParams();
514 $request = $this->getRequest();
515 foreach ( $this->mParams + $this->getPageSet()->extractRequestParams() as $param => $value ) {
516 $needed = $param === 'meta';
517 if ( !isset( $allowedParams[$param] ) && $request->getCheck( $param ) !== $needed ) {
518 return true;
519 }
520 }
521
522 // Ask each module if it requires read mode. Any true => this returns
523 // true.
524 $modules = [];
525 $this->instantiateModules( $modules, 'meta' );
526 foreach ( $modules as $module ) {
527 if ( $module->isReadMode() ) {
528 return true;
529 }
530 }
531
532 return false;
533 }
534
535 protected function getExamplesMessages() {
536 return [
537 'action=query&prop=revisions&meta=siteinfo&' .
538 'titles=Main%20Page&rvprop=user|comment&continue='
539 => 'apihelp-query-example-revisions',
540 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
541 => 'apihelp-query-example-allpages',
542 ];
543 }
544
545 public function getHelpUrls() {
546 return [
547 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
548 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
549 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
550 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
551 ];
552 }
553}
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:42
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:876
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:668
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition ApiBase.php:2014
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:2191
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:2220
getMain()
Get the main module.
Definition ApiBase.php:536
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:94
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:55
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:710
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition ApiBase.php:694
getResult()
Get the result object.
Definition ApiBase.php:640
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:761
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:131
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:58
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:41
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.
Definition ApiQuery.php:505
instantiateModules(&$modules, $param)
Create instances of all modules requested by the client.
Definition ApiQuery.php:308
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:289
getAllowedParams( $flags=0)
Definition ApiQuery.php:471
getModuleManager()
Overrides to return this instance's module manager.
Definition ApiQuery.php:162
ApiPageSet $mPageSet
Definition ApiQuery.php:125
__construct(ApiMain $main, $action)
Definition ApiQuery.php:135
outputGeneralPageInfo()
Appends an element for each page in the current pageSet with the most general information (id,...
Definition ApiQuery.php:332
getExamplesMessages()
Returns usage examples for this module.
Definition ApiQuery.php:535
doExport( $pageSet, $result)
Definition ApiQuery.php:434
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiQuery.php:545
getPageSet()
Gets the set of pages the user has requested (or generated)
Definition ApiQuery.php:188
execute()
Query execution happens in the following steps: #1 Create a PageSet object with any pages requested b...
Definition ApiQuery.php:217
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:176
getCustomPrinter()
Definition ApiQuery.php:195
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.
static string[] $supportedSchemas
the schema versions supported for output @final
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
return true
Definition router.php:94