MediaWiki  master
ApiQuery.php
Go to the documentation of this file.
1 <?php
25 
37 class 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,
269  }
270  $data = $continuationManager->getRawContinuation();
271  if ( $data ) {
272  $this->getResult()->addValue( null, 'query-continue', $data,
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 = [];
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 = [];
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;
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' => [
474  ApiBase::PARAM_ISMULTI => true,
475  ApiBase::PARAM_TYPE => 'submodule',
476  ],
477  'list' => [
478  ApiBase::PARAM_ISMULTI => true,
479  ApiBase::PARAM_TYPE => 'submodule',
480  ],
481  'meta' => [
482  ApiBase::PARAM_ISMULTI => true,
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  $this->mParams = $this->extractRequestParams();
513  $params = array_filter(
514  array_diff_key(
515  $this->mParams + $this->getPageSet()->extractRequestParams(),
516  [ 'rawcontinue' => 1, 'indexpageids' => 1 ]
517  )
518  );
519  if ( array_keys( $params ) !== [ 'meta' ] ) {
520  return true;
521  }
522 
523  // Ask each module if it requires read mode. Any true => this returns
524  // true.
525  $modules = [];
526  $this->instantiateModules( $modules, 'meta' );
527  foreach ( $modules as $module ) {
528  if ( $module->isReadMode() ) {
529  return true;
530  }
531  }
532 
533  return false;
534  }
535 
536  protected function getExamplesMessages() {
537  return [
538  'action=query&prop=revisions&meta=siteinfo&' .
539  'titles=Main%20Page&rvprop=user|comment&continue='
540  => 'apihelp-query-example-revisions',
541  'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
542  => 'apihelp-query-example-allpages',
543  ];
544  }
545 
546  public function getHelpUrls() {
547  return [
548  'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
549  'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
550  'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
551  'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
552  ];
553  }
554 }
getHelpUrls()
Definition: ApiQuery.php:546
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
getResult()
Get the result object.
Definition: ApiBase.php:640
execute()
Query execution happens in the following steps: #1 Create a PageSet object with any pages requested b...
Definition: ApiQuery.php:217
This class contains a list of pages that the client has requested.
Definition: ApiPageSet.php:40
This class holds a list of modules and handles instantiation.
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
getMain()
Get the main module.
Definition: ApiBase.php:536
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:668
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Formatter that spits out anything you like with any desired MIME type.
This manages continuation state.
mergeCacheMode( $cacheMode, $modCacheMode)
Update a cache mode string, applying the cache mode of a new module to it.
Definition: ApiQuery.php:289
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2005
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
getPageSet()
Gets the set of pages the user has requested (or generated)
Definition: ApiQuery.php:188
const ADD_ON_TOP
For addValue(), setValue() and similar functions, if the value does not exist, add it as the first el...
Definition: ApiResult.php:49
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
instantiateModules(&$modules, $param)
Create instances of all modules requested by the client.
Definition: ApiQuery.php:308
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:876
static array $QueryPropModules
List of Api Query prop modules.
Definition: ApiQuery.php:43
outputGeneralPageInfo()
Appends an element for each page in the current pageSet with the most general information (id...
Definition: ApiQuery.php:332
$modules
getNamedDB( $name, $db, $groups)
Get the query database connection with the given name.
Definition: ApiQuery.php:176
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:694
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don&#39;t use this unless yo...
Definition: ApiResult.php:58
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
Definition: ApiResult.php:1056
getExamplesMessages()
Definition: ApiQuery.php:536
This is the main query class.
Definition: ApiQuery.php:37
getModuleManager()
Overrides to return this instance&#39;s module manager.
Definition: ApiQuery.php:162
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:131
static array static array $QueryListModules
List of Api Query list modules.
Definition: ApiQuery.php:70
isReadMode()
Definition: ApiQuery.php:505
getAllowedParams( $flags=0)
Definition: ApiQuery.php:471
doExport( $pageSet, $result)
Definition: ApiQuery.php:434
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:2182
__construct(ApiMain $main, $action)
Definition: ApiQuery.php:135
static schemaVersion()
Returns the default export schema version, as defined by $wgXmlDumpSchemaVersion. ...
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:58
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks...
Definition: ApiBase.php:710
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:2211
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:42
static string [] $supportedSchemas
the schema versions supported for output
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:728
static array static array static array ApiPageSet $mPageSet
Definition: ApiQuery.php:114
const META_BC_SUBELEMENTS
Key for the &#39;BC subelements&#39; metadata item.
Definition: ApiResult.php:143
getCustomPrinter()
Definition: ApiQuery.php:195
static array static array static array $QueryMetaModules
List of Api Query meta modules.
Definition: ApiQuery.php:112
return true
Definition: router.php:92
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200