00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 if (!defined('MEDIAWIKI')) {
00027
00028 require_once ('ApiBase.php');
00029 }
00030
00042 class ApiQuery extends ApiBase {
00043
00044 private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
00045 private $mPageSet;
00046 private $params, $redirect;
00047
00048 private $mQueryPropModules = array (
00049 'info' => 'ApiQueryInfo',
00050 'revisions' => 'ApiQueryRevisions',
00051 'links' => 'ApiQueryLinks',
00052 'langlinks' => 'ApiQueryLangLinks',
00053 'images' => 'ApiQueryImages',
00054 'imageinfo' => 'ApiQueryImageInfo',
00055 'templates' => 'ApiQueryLinks',
00056 'categories' => 'ApiQueryCategories',
00057 'extlinks' => 'ApiQueryExternalLinks',
00058 'categoryinfo' => 'ApiQueryCategoryInfo',
00059 'duplicatefiles' => 'ApiQueryDuplicateFiles',
00060 );
00061
00062 private $mQueryListModules = array (
00063 'allimages' => 'ApiQueryAllimages',
00064 'allpages' => 'ApiQueryAllpages',
00065 'alllinks' => 'ApiQueryAllLinks',
00066 'allcategories' => 'ApiQueryAllCategories',
00067 'allusers' => 'ApiQueryAllUsers',
00068 'backlinks' => 'ApiQueryBacklinks',
00069 'blocks' => 'ApiQueryBlocks',
00070 'categorymembers' => 'ApiQueryCategoryMembers',
00071 'deletedrevs' => 'ApiQueryDeletedrevs',
00072 'embeddedin' => 'ApiQueryBacklinks',
00073 'imageusage' => 'ApiQueryBacklinks',
00074 'logevents' => 'ApiQueryLogEvents',
00075 'recentchanges' => 'ApiQueryRecentChanges',
00076 'search' => 'ApiQuerySearch',
00077 'usercontribs' => 'ApiQueryContributions',
00078 'watchlist' => 'ApiQueryWatchlist',
00079 'watchlistraw' => 'ApiQueryWatchlistRaw',
00080 'exturlusage' => 'ApiQueryExtLinksUsage',
00081 'users' => 'ApiQueryUsers',
00082 'random' => 'ApiQueryRandom',
00083 'protectedtitles' => 'ApiQueryProtectedTitles',
00084 );
00085
00086 private $mQueryMetaModules = array (
00087 'siteinfo' => 'ApiQuerySiteinfo',
00088 'userinfo' => 'ApiQueryUserInfo',
00089 'allmessages' => 'ApiQueryAllmessages',
00090 );
00091
00092 private $mSlaveDB = null;
00093 private $mNamedDB = array();
00094
00095 public function __construct($main, $action) {
00096 parent :: __construct($main, $action);
00097
00098
00099 global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
00100 self :: appendUserModules($this->mQueryPropModules, $wgAPIPropModules);
00101 self :: appendUserModules($this->mQueryListModules, $wgAPIListModules);
00102 self :: appendUserModules($this->mQueryMetaModules, $wgAPIMetaModules);
00103
00104 $this->mPropModuleNames = array_keys($this->mQueryPropModules);
00105 $this->mListModuleNames = array_keys($this->mQueryListModules);
00106 $this->mMetaModuleNames = array_keys($this->mQueryMetaModules);
00107
00108
00109
00110 $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);
00111 }
00112
00118 private static function appendUserModules(&$modules, $newModules) {
00119 if (is_array( $newModules )) {
00120 foreach ( $newModules as $moduleName => $moduleClass) {
00121 $modules[$moduleName] = $moduleClass;
00122 }
00123 }
00124 }
00125
00130 public function getDB() {
00131 if (!isset ($this->mSlaveDB)) {
00132 $this->profileDBIn();
00133 $this->mSlaveDB = wfGetDB(DB_SLAVE,'api');
00134 $this->profileDBOut();
00135 }
00136 return $this->mSlaveDB;
00137 }
00138
00149 public function getNamedDB($name, $db, $groups) {
00150 if (!array_key_exists($name, $this->mNamedDB)) {
00151 $this->profileDBIn();
00152 $this->mNamedDB[$name] = wfGetDB($db, $groups);
00153 $this->profileDBOut();
00154 }
00155 return $this->mNamedDB[$name];
00156 }
00157
00162 public function getPageSet() {
00163 return $this->mPageSet;
00164 }
00165
00170 function getModules() {
00171 return array_merge($this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules);
00172 }
00173
00174 public function getCustomPrinter() {
00175
00176 if ($this->getParameter('export') &&
00177 $this->getParameter('exportnowrap'))
00178 return new ApiFormatRaw($this->getMain(),
00179 $this->getMain()->createPrinterByName('xml'));
00180 else
00181 return null;
00182 }
00183
00194 public function execute() {
00195
00196 $this->params = $this->extractRequestParams();
00197 $this->redirects = $this->params['redirects'];
00198
00199
00200
00201
00202 $this->mPageSet = new ApiPageSet($this, $this->redirects);
00203
00204
00205
00206
00207 $modules = array ();
00208 $this->InstantiateModules($modules, 'prop', $this->mQueryPropModules);
00209 $this->InstantiateModules($modules, 'list', $this->mQueryListModules);
00210 $this->InstantiateModules($modules, 'meta', $this->mQueryMetaModules);
00211
00212 $cacheMode = 'public';
00213
00214
00215
00216
00217 if ( isset ( $this->params['generator'] ) ) {
00218 $generator = $this->newGenerator( $this->params['generator'] );
00219 $params = $generator->extractRequestParams();
00220 $cacheMode = $this->mergeCacheMode( $cacheMode,
00221 $generator->getCacheMode( $params ) );
00222 $this->executeGeneratorModule( $generator, $modules );
00223 } else {
00224
00225 $this->addCustomFldsToPageSet($modules, $this->mPageSet);
00226 $this->mPageSet->execute();
00227 }
00228
00229
00230
00231
00232 $this->outputGeneralPageInfo();
00233
00234
00235
00236
00237 foreach ($modules as $module) {
00238 $params = $module->extractRequestParams();
00239 $cacheMode = $this->mergeCacheMode(
00240 $cacheMode, $module->getCacheMode( $params ) );
00241 $module->profileIn();
00242 $module->execute();
00243 wfRunHooks('APIQueryAfterExecute', array(&$module));
00244 $module->profileOut();
00245 }
00246
00247
00248 $this->getMain()->setCacheMode( $cacheMode );
00249 }
00250
00256 protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
00257 if ( $modCacheMode === 'anon-public-user-private' ) {
00258 if ( $cacheMode !== 'private' ) {
00259 $cacheMode = 'anon-public-user-private';
00260 }
00261 } elseif ( $modCacheMode === 'public' ) {
00262
00263 } else {
00264 $cacheMode = 'private';
00265 }
00266 return $cacheMode;
00267 }
00268
00276 private function addCustomFldsToPageSet($modules, $pageSet) {
00277
00278 foreach ($modules as $module) {
00279 $module->requestExtraData($pageSet);
00280 }
00281 }
00282
00289 private function InstantiateModules(&$modules, $param, $moduleList) {
00290 $list = @$this->params[$param];
00291 if (!is_null ($list))
00292 foreach ($list as $moduleName)
00293 $modules[] = new $moduleList[$moduleName] ($this, $moduleName);
00294 }
00295
00301 private function outputGeneralPageInfo() {
00302
00303 $pageSet = $this->getPageSet();
00304 $result = $this->getResult();
00305
00306 # We don't check for a full result set here because we can't be adding
00307 # more than 380K. The maximum revision size is in the megabyte range,
00308 # and the maximum result size must be even higher than that.
00309
00310
00311 $normValues = array ();
00312 foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) {
00313 $normValues[] = array (
00314 'from' => $rawTitleStr,
00315 'to' => $titleStr
00316 );
00317 }
00318
00319 if (count($normValues)) {
00320 $result->setIndexedTagName($normValues, 'n');
00321 $result->addValue('query', 'normalized', $normValues);
00322 }
00323
00324
00325 $intrwValues = array ();
00326 foreach ($pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr) {
00327 $intrwValues[] = array (
00328 'title' => $rawTitleStr,
00329 'iw' => $interwikiStr
00330 );
00331 }
00332
00333 if (count($intrwValues)) {
00334 $result->setIndexedTagName($intrwValues, 'i');
00335 $result->addValue('query', 'interwiki', $intrwValues);
00336 }
00337
00338
00339 $redirValues = array ();
00340 foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) {
00341 $redirValues[] = array (
00342 'from' => strval($titleStrFrom),
00343 'to' => $titleStrTo
00344 );
00345 }
00346
00347 if (count($redirValues)) {
00348 $result->setIndexedTagName($redirValues, 'r');
00349 $result->addValue('query', 'redirects', $redirValues);
00350 }
00351
00352
00353
00354
00355 $missingRevIDs = $pageSet->getMissingRevisionIDs();
00356 if (count($missingRevIDs)) {
00357 $revids = array ();
00358 foreach ($missingRevIDs as $revid) {
00359 $revids[$revid] = array (
00360 'revid' => $revid
00361 );
00362 }
00363 $result->setIndexedTagName($revids, 'rev');
00364 $result->addValue('query', 'badrevids', $revids);
00365 }
00366
00367
00368
00369
00370 $pages = array ();
00371
00372
00373 foreach ($pageSet->getMissingTitles() as $fakeId => $title) {
00374 $vals = array();
00375 ApiQueryBase :: addTitleInfo($vals, $title);
00376 $vals['missing'] = '';
00377 $pages[$fakeId] = $vals;
00378 }
00379
00380 foreach ($pageSet->getInvalidTitles() as $fakeId => $title)
00381 $pages[$fakeId] = array('title' => $title, 'invalid' => '');
00382
00383 foreach ($pageSet->getMissingPageIDs() as $pageid) {
00384 $pages[$pageid] = array (
00385 'pageid' => $pageid,
00386 'missing' => ''
00387 );
00388 }
00389
00390
00391 foreach ($pageSet->getGoodTitles() as $pageid => $title) {
00392 $vals = array();
00393 $vals['pageid'] = $pageid;
00394 ApiQueryBase :: addTitleInfo($vals, $title);
00395 $pages[$pageid] = $vals;
00396 }
00397
00398 if (count($pages)) {
00399
00400 if ($this->params['indexpageids']) {
00401 $pageIDs = array_keys($pages);
00402
00403 $pageIDs = array_map('strval', $pageIDs);
00404 $result->setIndexedTagName($pageIDs, 'id');
00405 $result->addValue('query', 'pageids', $pageIDs);
00406 }
00407
00408 $result->setIndexedTagName($pages, 'page');
00409 $result->addValue('query', 'pages', $pages);
00410 }
00411 if ($this->params['export']) {
00412 $exporter = new WikiExporter($this->getDB());
00413
00414
00415 ob_start();
00416 $exporter->openStream();
00417 foreach (@$pageSet->getGoodTitles() as $title)
00418 if ($title->userCanRead())
00419 $exporter->pageByTitle($title);
00420 $exporter->closeStream();
00421 $exportxml = ob_get_contents();
00422 ob_end_clean();
00423
00424
00425
00426 $result->disableSizeCheck();
00427 if ($this->params['exportnowrap']) {
00428 $result->reset();
00429
00430 $result->addValue(null, 'text', $exportxml);
00431 $result->addValue(null, 'mime', 'text/xml');
00432 } else {
00433 $r = array();
00434 ApiResult::setContent($r, $exportxml);
00435 $result->addValue('query', 'export', $r);
00436 }
00437 $result->enableSizeCheck();
00438 }
00439 }
00440
00444 public function newGenerator( $generatorName ) {
00445
00446 if (isset ($this->mQueryListModules[$generatorName])) {
00447 $className = $this->mQueryListModules[$generatorName];
00448 } elseif (isset ($this->mQueryPropModules[$generatorName])) {
00449 $className = $this->mQueryPropModules[$generatorName];
00450 } else {
00451 ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName");
00452 }
00453
00454
00455 $resultPageSet = new ApiPageSet($this, $this->redirects);
00456
00457
00458 $generator = new $className ($this, $generatorName);
00459 if (!$generator instanceof ApiQueryGeneratorBase)
00460 $this->dieUsage("Module $generatorName cannot be used as a generator", "badgenerator");
00461 $generator->setGeneratorMode();
00462 return $generator;
00463 }
00464
00471 protected function executeGeneratorModule( $generator, $modules ) {
00472
00473 $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
00474
00475
00476 $generator->requestExtraData($this->mPageSet);
00477 $this->addCustomFldsToPageSet($modules, $resultPageSet);
00478
00479
00480 $this->mPageSet->execute();
00481
00482
00483 $generator->profileIn();
00484 $generator->executeGenerator($resultPageSet);
00485 wfRunHooks('APIQueryGeneratorAfterExecute', array(&$generator, &$resultPageSet));
00486 $resultPageSet->finishPageSetGeneration();
00487 $generator->profileOut();
00488
00489
00490 $this->mPageSet = $resultPageSet;
00491 }
00492
00493 public function getAllowedParams() {
00494 return array (
00495 'prop' => array (
00496 ApiBase :: PARAM_ISMULTI => true,
00497 ApiBase :: PARAM_TYPE => $this->mPropModuleNames
00498 ),
00499 'list' => array (
00500 ApiBase :: PARAM_ISMULTI => true,
00501 ApiBase :: PARAM_TYPE => $this->mListModuleNames
00502 ),
00503 'meta' => array (
00504 ApiBase :: PARAM_ISMULTI => true,
00505 ApiBase :: PARAM_TYPE => $this->mMetaModuleNames
00506 ),
00507 'generator' => array (
00508 ApiBase :: PARAM_TYPE => $this->mAllowedGenerators
00509 ),
00510 'redirects' => false,
00511 'indexpageids' => false,
00512 'export' => false,
00513 'exportnowrap' => false,
00514 );
00515 }
00516
00521 public function makeHelpMsg() {
00522
00523 $msg = '';
00524
00525
00526
00527 $this->mPageSet = null;
00528 $this->mAllowedGenerators = array();
00529
00530 $astriks = str_repeat('--- ', 8);
00531 $astriks2 = str_repeat('*** ', 10);
00532 $msg .= "\n$astriks Query: Prop $astriks\n\n";
00533 $msg .= $this->makeHelpMsgHelper($this->mQueryPropModules, 'prop');
00534 $msg .= "\n$astriks Query: List $astriks\n\n";
00535 $msg .= $this->makeHelpMsgHelper($this->mQueryListModules, 'list');
00536 $msg .= "\n$astriks Query: Meta $astriks\n\n";
00537 $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta');
00538 $msg .= "\n\n$astriks2 Modules: continuation $astriks2\n\n";
00539
00540
00541
00542
00543 $msg = parent :: makeHelpMsg() . $msg;
00544
00545 return $msg;
00546 }
00547
00554 private function makeHelpMsgHelper($moduleList, $paramName) {
00555
00556 $moduleDescriptions = array ();
00557
00558 foreach ($moduleList as $moduleName => $moduleClass) {
00559 $module = new $moduleClass ($this, $moduleName, null);
00560
00561 $msg = ApiMain::makeHelpMsgHeader($module, $paramName);
00562 $msg2 = $module->makeHelpMsg();
00563 if ($msg2 !== false)
00564 $msg .= $msg2;
00565 if ($module instanceof ApiQueryGeneratorBase) {
00566 $this->mAllowedGenerators[] = $moduleName;
00567 $msg .= "Generator:\n This module may be used as a generator\n";
00568 }
00569 $moduleDescriptions[] = $msg;
00570 }
00571
00572 return implode("\n", $moduleDescriptions);
00573 }
00574
00579 public function makeHelpMsgParameters() {
00580 $psModule = new ApiPageSet($this);
00581 return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
00582 }
00583
00584 public function shouldCheckMaxlag() {
00585 return true;
00586 }
00587
00588 public function getParamDescription() {
00589 return array (
00590 'prop' => 'Which properties to get for the titles/revisions/pageids',
00591 'list' => 'Which lists to get',
00592 'meta' => 'Which meta data to get about the site',
00593 'generator' => array('Use the output of a list as the input for other prop/list/meta items',
00594 'NOTE: generator parameter names must be prefixed with a \'g\', see examples.'),
00595 'redirects' => 'Automatically resolve redirects',
00596 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.',
00597 'export' => 'Export the current revisions of all given or generated pages',
00598 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
00599 );
00600 }
00601
00602 public function getDescription() {
00603 return array (
00604 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
00605 'and is loosely based on the old query.php interface.',
00606 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'
00607 );
00608 }
00609
00610 protected function getExamples() {
00611 return array (
00612 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
00613 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
00614 );
00615 }
00616
00617 public function getVersion() {
00618 $psModule = new ApiPageSet($this);
00619 $vers = array ();
00620 $vers[] = __CLASS__ . ': $Id: ApiQuery.php 69986 2010-07-27 03:57:39Z tstarling $';
00621 $vers[] = $psModule->getVersion();
00622 return $vers;
00623 }
00624 }