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
00213
00214
00215 if (isset ($this->params['generator'])) {
00216 $this->executeGeneratorModule($this->params['generator'], $modules);
00217 } else {
00218
00219 $this->addCustomFldsToPageSet($modules, $this->mPageSet);
00220 $this->mPageSet->execute();
00221 }
00222
00223
00224
00225
00226 $this->outputGeneralPageInfo();
00227
00228
00229
00230
00231 foreach ($modules as $module) {
00232 $module->profileIn();
00233 $module->execute();
00234 wfRunHooks('APIQueryAfterExecute', array(&$module));
00235 $module->profileOut();
00236 }
00237 }
00238
00246 private function addCustomFldsToPageSet($modules, $pageSet) {
00247
00248 foreach ($modules as $module) {
00249 $module->requestExtraData($pageSet);
00250 }
00251 }
00252
00259 private function InstantiateModules(&$modules, $param, $moduleList) {
00260 $list = @$this->params[$param];
00261 if (!is_null ($list))
00262 foreach ($list as $moduleName)
00263 $modules[] = new $moduleList[$moduleName] ($this, $moduleName);
00264 }
00265
00271 private function outputGeneralPageInfo() {
00272
00273 $pageSet = $this->getPageSet();
00274 $result = $this->getResult();
00275
00276 # We don't check for a full result set here because we can't be adding
00277 # more than 380K. The maximum revision size is in the megabyte range,
00278 # and the maximum result size must be even higher than that.
00279
00280
00281 $normValues = array ();
00282 foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) {
00283 $normValues[] = array (
00284 'from' => $rawTitleStr,
00285 'to' => $titleStr
00286 );
00287 }
00288
00289 if (count($normValues)) {
00290 $result->setIndexedTagName($normValues, 'n');
00291 $result->addValue('query', 'normalized', $normValues);
00292 }
00293
00294
00295 $intrwValues = array ();
00296 foreach ($pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr) {
00297 $intrwValues[] = array (
00298 'title' => $rawTitleStr,
00299 'iw' => $interwikiStr
00300 );
00301 }
00302
00303 if (count($intrwValues)) {
00304 $result->setIndexedTagName($intrwValues, 'i');
00305 $result->addValue('query', 'interwiki', $intrwValues);
00306 }
00307
00308
00309 $redirValues = array ();
00310 foreach ($pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo) {
00311 $redirValues[] = array (
00312 'from' => strval($titleStrFrom),
00313 'to' => $titleStrTo
00314 );
00315 }
00316
00317 if (count($redirValues)) {
00318 $result->setIndexedTagName($redirValues, 'r');
00319 $result->addValue('query', 'redirects', $redirValues);
00320 }
00321
00322
00323
00324
00325 $missingRevIDs = $pageSet->getMissingRevisionIDs();
00326 if (count($missingRevIDs)) {
00327 $revids = array ();
00328 foreach ($missingRevIDs as $revid) {
00329 $revids[$revid] = array (
00330 'revid' => $revid
00331 );
00332 }
00333 $result->setIndexedTagName($revids, 'rev');
00334 $result->addValue('query', 'badrevids', $revids);
00335 }
00336
00337
00338
00339
00340 $pages = array ();
00341
00342
00343 foreach ($pageSet->getMissingTitles() as $fakeId => $title) {
00344 $vals = array();
00345 ApiQueryBase :: addTitleInfo($vals, $title);
00346 $vals['missing'] = '';
00347 $pages[$fakeId] = $vals;
00348 }
00349
00350 foreach ($pageSet->getInvalidTitles() as $fakeId => $title)
00351 $pages[$fakeId] = array('title' => $title, 'invalid' => '');
00352
00353 foreach ($pageSet->getMissingPageIDs() as $pageid) {
00354 $pages[$pageid] = array (
00355 'pageid' => $pageid,
00356 'missing' => ''
00357 );
00358 }
00359
00360
00361 foreach ($pageSet->getGoodTitles() as $pageid => $title) {
00362 $vals = array();
00363 $vals['pageid'] = $pageid;
00364 ApiQueryBase :: addTitleInfo($vals, $title);
00365 $pages[$pageid] = $vals;
00366 }
00367
00368 if (count($pages)) {
00369
00370 if ($this->params['indexpageids']) {
00371 $pageIDs = array_keys($pages);
00372
00373 $pageIDs = array_map('strval', $pageIDs);
00374 $result->setIndexedTagName($pageIDs, 'id');
00375 $result->addValue('query', 'pageids', $pageIDs);
00376 }
00377
00378 $result->setIndexedTagName($pages, 'page');
00379 $result->addValue('query', 'pages', $pages);
00380 }
00381 if ($this->params['export']) {
00382 $exporter = new WikiExporter($this->getDB());
00383
00384
00385 ob_start();
00386 $exporter->openStream();
00387 foreach (@$pageSet->getGoodTitles() as $title)
00388 if ($title->userCanRead())
00389 $exporter->pageByTitle($title);
00390 $exporter->closeStream();
00391 $exportxml = ob_get_contents();
00392 ob_end_clean();
00393
00394
00395
00396 $result->disableSizeCheck();
00397 if ($this->params['exportnowrap']) {
00398 $result->reset();
00399
00400 $result->addValue(null, 'text', $exportxml);
00401 $result->addValue(null, 'mime', 'text/xml');
00402 } else {
00403 $r = array();
00404 ApiResult::setContent($r, $exportxml);
00405 $result->addValue('query', 'export', $r);
00406 }
00407 $result->enableSizeCheck();
00408 }
00409 }
00410
00417 protected function executeGeneratorModule($generatorName, $modules) {
00418
00419
00420 if (isset ($this->mQueryListModules[$generatorName])) {
00421 $className = $this->mQueryListModules[$generatorName];
00422 } elseif (isset ($this->mQueryPropModules[$generatorName])) {
00423 $className = $this->mQueryPropModules[$generatorName];
00424 } else {
00425 ApiBase :: dieDebug(__METHOD__, "Unknown generator=$generatorName");
00426 }
00427
00428
00429 $resultPageSet = new ApiPageSet($this, $this->redirects);
00430
00431
00432 $generator = new $className ($this, $generatorName);
00433 if (!$generator instanceof ApiQueryGeneratorBase)
00434 $this->dieUsage("Module $generatorName cannot be used as a generator", "badgenerator");
00435
00436 $generator->setGeneratorMode();
00437
00438
00439 $generator->requestExtraData($this->mPageSet);
00440 $this->addCustomFldsToPageSet($modules, $resultPageSet);
00441
00442
00443 $this->mPageSet->execute();
00444
00445
00446 $generator->profileIn();
00447 $generator->executeGenerator($resultPageSet);
00448 wfRunHooks('APIQueryGeneratorAfterExecute', array(&$generator, &$resultPageSet));
00449 $resultPageSet->finishPageSetGeneration();
00450 $generator->profileOut();
00451
00452
00453 $this->mPageSet = $resultPageSet;
00454 }
00455
00456 public function getAllowedParams() {
00457 return array (
00458 'prop' => array (
00459 ApiBase :: PARAM_ISMULTI => true,
00460 ApiBase :: PARAM_TYPE => $this->mPropModuleNames
00461 ),
00462 'list' => array (
00463 ApiBase :: PARAM_ISMULTI => true,
00464 ApiBase :: PARAM_TYPE => $this->mListModuleNames
00465 ),
00466 'meta' => array (
00467 ApiBase :: PARAM_ISMULTI => true,
00468 ApiBase :: PARAM_TYPE => $this->mMetaModuleNames
00469 ),
00470 'generator' => array (
00471 ApiBase :: PARAM_TYPE => $this->mAllowedGenerators
00472 ),
00473 'redirects' => false,
00474 'indexpageids' => false,
00475 'export' => false,
00476 'exportnowrap' => false,
00477 );
00478 }
00479
00484 public function makeHelpMsg() {
00485
00486 $msg = '';
00487
00488
00489
00490 $this->mPageSet = null;
00491 $this->mAllowedGenerators = array();
00492
00493 $astriks = str_repeat('--- ', 8);
00494 $astriks2 = str_repeat('*** ', 10);
00495 $msg .= "\n$astriks Query: Prop $astriks\n\n";
00496 $msg .= $this->makeHelpMsgHelper($this->mQueryPropModules, 'prop');
00497 $msg .= "\n$astriks Query: List $astriks\n\n";
00498 $msg .= $this->makeHelpMsgHelper($this->mQueryListModules, 'list');
00499 $msg .= "\n$astriks Query: Meta $astriks\n\n";
00500 $msg .= $this->makeHelpMsgHelper($this->mQueryMetaModules, 'meta');
00501 $msg .= "\n\n$astriks2 Modules: continuation $astriks2\n\n";
00502
00503
00504
00505
00506 $msg = parent :: makeHelpMsg() . $msg;
00507
00508 return $msg;
00509 }
00510
00517 private function makeHelpMsgHelper($moduleList, $paramName) {
00518
00519 $moduleDescriptions = array ();
00520
00521 foreach ($moduleList as $moduleName => $moduleClass) {
00522 $module = new $moduleClass ($this, $moduleName, null);
00523
00524 $msg = ApiMain::makeHelpMsgHeader($module, $paramName);
00525 $msg2 = $module->makeHelpMsg();
00526 if ($msg2 !== false)
00527 $msg .= $msg2;
00528 if ($module instanceof ApiQueryGeneratorBase) {
00529 $this->mAllowedGenerators[] = $moduleName;
00530 $msg .= "Generator:\n This module may be used as a generator\n";
00531 }
00532 $moduleDescriptions[] = $msg;
00533 }
00534
00535 return implode("\n", $moduleDescriptions);
00536 }
00537
00542 public function makeHelpMsgParameters() {
00543 $psModule = new ApiPageSet($this);
00544 return $psModule->makeHelpMsgParameters() . parent :: makeHelpMsgParameters();
00545 }
00546
00547 public function shouldCheckMaxlag() {
00548 return true;
00549 }
00550
00551 public function getParamDescription() {
00552 return array (
00553 'prop' => 'Which properties to get for the titles/revisions/pageids',
00554 'list' => 'Which lists to get',
00555 'meta' => 'Which meta data to get about the site',
00556 'generator' => array('Use the output of a list as the input for other prop/list/meta items',
00557 'NOTE: generator parameter names must be prefixed with a \'g\', see examples.'),
00558 'redirects' => 'Automatically resolve redirects',
00559 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.',
00560 'export' => 'Export the current revisions of all given or generated pages',
00561 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
00562 );
00563 }
00564
00565 public function getDescription() {
00566 return array (
00567 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
00568 'and is loosely based on the old query.php interface.',
00569 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'
00570 );
00571 }
00572
00573 protected function getExamples() {
00574 return array (
00575 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
00576 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
00577 );
00578 }
00579
00580 public function getVersion() {
00581 $psModule = new ApiPageSet($this);
00582 $vers = array ();
00583 $vers[] = __CLASS__ . ': $Id: ApiQuery.php 48629 2009-03-20 11:40:54Z catrope $';
00584 $vers[] = $psModule->getVersion();
00585 return $vers;
00586 }
00587 }