MediaWiki
REL1_19
|
00001 <?php 00038 class ApiQuery extends ApiBase { 00039 00040 private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames; 00041 00045 private $mPageSet; 00046 00047 private $params, $redirects, $convertTitles, $iwUrl; 00048 00049 private $mQueryPropModules = array( 00050 'info' => 'ApiQueryInfo', 00051 'revisions' => 'ApiQueryRevisions', 00052 'links' => 'ApiQueryLinks', 00053 'iwlinks' => 'ApiQueryIWLinks', 00054 'langlinks' => 'ApiQueryLangLinks', 00055 'images' => 'ApiQueryImages', 00056 'imageinfo' => 'ApiQueryImageInfo', 00057 'stashimageinfo' => 'ApiQueryStashImageInfo', 00058 'templates' => 'ApiQueryLinks', 00059 'categories' => 'ApiQueryCategories', 00060 'extlinks' => 'ApiQueryExternalLinks', 00061 'categoryinfo' => 'ApiQueryCategoryInfo', 00062 'duplicatefiles' => 'ApiQueryDuplicateFiles', 00063 'pageprops' => 'ApiQueryPageProps', 00064 ); 00065 00066 private $mQueryListModules = array( 00067 'allimages' => 'ApiQueryAllimages', 00068 'allpages' => 'ApiQueryAllpages', 00069 'alllinks' => 'ApiQueryAllLinks', 00070 'allcategories' => 'ApiQueryAllCategories', 00071 'allusers' => 'ApiQueryAllUsers', 00072 'backlinks' => 'ApiQueryBacklinks', 00073 'blocks' => 'ApiQueryBlocks', 00074 'categorymembers' => 'ApiQueryCategoryMembers', 00075 'deletedrevs' => 'ApiQueryDeletedrevs', 00076 'embeddedin' => 'ApiQueryBacklinks', 00077 'filearchive' => 'ApiQueryFilearchive', 00078 'imageusage' => 'ApiQueryBacklinks', 00079 'iwbacklinks' => 'ApiQueryIWBacklinks', 00080 'langbacklinks' => 'ApiQueryLangBacklinks', 00081 'logevents' => 'ApiQueryLogEvents', 00082 'recentchanges' => 'ApiQueryRecentChanges', 00083 'search' => 'ApiQuerySearch', 00084 'tags' => 'ApiQueryTags', 00085 'usercontribs' => 'ApiQueryContributions', 00086 'watchlist' => 'ApiQueryWatchlist', 00087 'watchlistraw' => 'ApiQueryWatchlistRaw', 00088 'exturlusage' => 'ApiQueryExtLinksUsage', 00089 'users' => 'ApiQueryUsers', 00090 'random' => 'ApiQueryRandom', 00091 'protectedtitles' => 'ApiQueryProtectedTitles', 00092 'querypage' => 'ApiQueryQueryPage', 00093 ); 00094 00095 private $mQueryMetaModules = array( 00096 'siteinfo' => 'ApiQuerySiteinfo', 00097 'userinfo' => 'ApiQueryUserInfo', 00098 'allmessages' => 'ApiQueryAllmessages', 00099 ); 00100 00101 private $mSlaveDB = null; 00102 private $mNamedDB = array(); 00103 00104 protected $mAllowedGenerators = array(); 00105 00106 public function __construct( $main, $action ) { 00107 parent::__construct( $main, $action ); 00108 00109 // Allow custom modules to be added in LocalSettings.php 00110 global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules; 00111 self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules ); 00112 self::appendUserModules( $this->mQueryListModules, $wgAPIListModules ); 00113 self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules ); 00114 00115 $this->mPropModuleNames = array_keys( $this->mQueryPropModules ); 00116 $this->mListModuleNames = array_keys( $this->mQueryListModules ); 00117 $this->mMetaModuleNames = array_keys( $this->mQueryMetaModules ); 00118 00119 $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' ); 00120 $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' ); 00121 } 00122 00128 private static function appendUserModules( &$modules, $newModules ) { 00129 if ( is_array( $newModules ) ) { 00130 foreach ( $newModules as $moduleName => $moduleClass ) { 00131 $modules[$moduleName] = $moduleClass; 00132 } 00133 } 00134 } 00135 00140 public function getDB() { 00141 if ( !isset( $this->mSlaveDB ) ) { 00142 $this->profileDBIn(); 00143 $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' ); 00144 $this->profileDBOut(); 00145 } 00146 return $this->mSlaveDB; 00147 } 00148 00159 public function getNamedDB( $name, $db, $groups ) { 00160 if ( !array_key_exists( $name, $this->mNamedDB ) ) { 00161 $this->profileDBIn(); 00162 $this->mNamedDB[$name] = wfGetDB( $db, $groups ); 00163 $this->profileDBOut(); 00164 } 00165 return $this->mNamedDB[$name]; 00166 } 00167 00172 public function getPageSet() { 00173 return $this->mPageSet; 00174 } 00175 00180 function getModules() { 00181 return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules ); 00182 } 00183 00189 function getModuleType( $moduleName ) { 00190 if ( isset( $this->mQueryPropModules[$moduleName] ) ) { 00191 return 'prop'; 00192 } 00193 00194 if ( isset( $this->mQueryListModules[$moduleName] ) ) { 00195 return 'list'; 00196 } 00197 00198 if ( isset( $this->mQueryMetaModules[$moduleName] ) ) { 00199 return 'meta'; 00200 } 00201 00202 return null; 00203 } 00204 00205 public function getCustomPrinter() { 00206 // If &exportnowrap is set, use the raw formatter 00207 if ( $this->getParameter( 'export' ) && 00208 $this->getParameter( 'exportnowrap' ) ) 00209 { 00210 return new ApiFormatRaw( $this->getMain(), 00211 $this->getMain()->createPrinterByName( 'xml' ) ); 00212 } else { 00213 return null; 00214 } 00215 } 00216 00227 public function execute() { 00228 $this->params = $this->extractRequestParams(); 00229 $this->redirects = $this->params['redirects']; 00230 $this->convertTitles = $this->params['converttitles']; 00231 $this->iwUrl = $this->params['iwurl']; 00232 00233 // Create PageSet 00234 $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles ); 00235 00236 // Instantiate requested modules 00237 $modules = array(); 00238 $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules ); 00239 $this->instantiateModules( $modules, 'list', $this->mQueryListModules ); 00240 $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules ); 00241 00242 $cacheMode = 'public'; 00243 00244 // If given, execute generator to substitute user supplied data with generated data. 00245 if ( isset( $this->params['generator'] ) ) { 00246 $generator = $this->newGenerator( $this->params['generator'] ); 00247 $params = $generator->extractRequestParams(); 00248 $cacheMode = $this->mergeCacheMode( $cacheMode, 00249 $generator->getCacheMode( $params ) ); 00250 $this->executeGeneratorModule( $generator, $modules ); 00251 } else { 00252 // Append custom fields and populate page/revision information 00253 $this->addCustomFldsToPageSet( $modules, $this->mPageSet ); 00254 $this->mPageSet->execute(); 00255 } 00256 00257 // Record page information (title, namespace, if exists, etc) 00258 $this->outputGeneralPageInfo(); 00259 00260 // Execute all requested modules. 00261 foreach ( $modules as $module ) { 00262 $params = $module->extractRequestParams(); 00263 $cacheMode = $this->mergeCacheMode( 00264 $cacheMode, $module->getCacheMode( $params ) ); 00265 $module->profileIn(); 00266 $module->execute(); 00267 wfRunHooks( 'APIQueryAfterExecute', array( &$module ) ); 00268 $module->profileOut(); 00269 } 00270 00271 // Set the cache mode 00272 $this->getMain()->setCacheMode( $cacheMode ); 00273 } 00274 00284 protected function mergeCacheMode( $cacheMode, $modCacheMode ) { 00285 if ( $modCacheMode === 'anon-public-user-private' ) { 00286 if ( $cacheMode !== 'private' ) { 00287 $cacheMode = 'anon-public-user-private'; 00288 } 00289 } elseif ( $modCacheMode === 'public' ) { 00290 // do nothing, if it's public already it will stay public 00291 } else { // private 00292 $cacheMode = 'private'; 00293 } 00294 return $cacheMode; 00295 } 00296 00304 private function addCustomFldsToPageSet( $modules, $pageSet ) { 00305 // Query all requested modules. 00306 foreach ( $modules as $module ) { 00307 $module->requestExtraData( $pageSet ); 00308 } 00309 } 00310 00317 private function instantiateModules( &$modules, $param, $moduleList ) { 00318 if ( isset( $this->params[$param] ) ) { 00319 foreach ( $this->params[$param] as $moduleName ) { 00320 $modules[] = new $moduleList[$moduleName] ( $this, $moduleName ); 00321 } 00322 } 00323 } 00324 00330 private function outputGeneralPageInfo() { 00331 $pageSet = $this->getPageSet(); 00332 $result = $this->getResult(); 00333 00334 // We don't check for a full result set here because we can't be adding 00335 // more than 380K. The maximum revision size is in the megabyte range, 00336 // and the maximum result size must be even higher than that. 00337 00338 // Title normalizations 00339 $normValues = array(); 00340 foreach ( $pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr ) { 00341 $normValues[] = array( 00342 'from' => $rawTitleStr, 00343 'to' => $titleStr 00344 ); 00345 } 00346 00347 if ( count( $normValues ) ) { 00348 $result->setIndexedTagName( $normValues, 'n' ); 00349 $result->addValue( 'query', 'normalized', $normValues ); 00350 } 00351 00352 // Title conversions 00353 $convValues = array(); 00354 foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) { 00355 $convValues[] = array( 00356 'from' => $rawTitleStr, 00357 'to' => $titleStr 00358 ); 00359 } 00360 00361 if ( count( $convValues ) ) { 00362 $result->setIndexedTagName( $convValues, 'c' ); 00363 $result->addValue( 'query', 'converted', $convValues ); 00364 } 00365 00366 // Interwiki titles 00367 $intrwValues = array(); 00368 foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) { 00369 $item = array( 00370 'title' => $rawTitleStr, 00371 'iw' => $interwikiStr, 00372 ); 00373 if ( $this->iwUrl ) { 00374 $title = Title::newFromText( $rawTitleStr ); 00375 $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); 00376 } 00377 $intrwValues[] = $item; 00378 } 00379 00380 if ( count( $intrwValues ) ) { 00381 $result->setIndexedTagName( $intrwValues, 'i' ); 00382 $result->addValue( 'query', 'interwiki', $intrwValues ); 00383 } 00384 00385 // Show redirect information 00386 $redirValues = array(); 00387 foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) { 00388 $r = array( 00389 'from' => strval( $titleStrFrom ), 00390 'to' => $titleTo->getPrefixedText(), 00391 ); 00392 if ( $titleTo->getFragment() !== '' ) { 00393 $r['tofragment'] = $titleTo->getFragment(); 00394 } 00395 $redirValues[] = $r; 00396 } 00397 00398 if ( count( $redirValues ) ) { 00399 $result->setIndexedTagName( $redirValues, 'r' ); 00400 $result->addValue( 'query', 'redirects', $redirValues ); 00401 } 00402 00403 // Missing revision elements 00404 $missingRevIDs = $pageSet->getMissingRevisionIDs(); 00405 if ( count( $missingRevIDs ) ) { 00406 $revids = array(); 00407 foreach ( $missingRevIDs as $revid ) { 00408 $revids[$revid] = array( 00409 'revid' => $revid 00410 ); 00411 } 00412 $result->setIndexedTagName( $revids, 'rev' ); 00413 $result->addValue( 'query', 'badrevids', $revids ); 00414 } 00415 00416 // Page elements 00417 $pages = array(); 00418 00419 // Report any missing titles 00420 foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) { 00421 $vals = array(); 00422 ApiQueryBase::addTitleInfo( $vals, $title ); 00423 $vals['missing'] = ''; 00424 $pages[$fakeId] = $vals; 00425 } 00426 // Report any invalid titles 00427 foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) { 00428 $pages[$fakeId] = array( 'title' => $title, 'invalid' => '' ); 00429 } 00430 // Report any missing page ids 00431 foreach ( $pageSet->getMissingPageIDs() as $pageid ) { 00432 $pages[$pageid] = array( 00433 'pageid' => $pageid, 00434 'missing' => '' 00435 ); 00436 } 00437 // Report special pages 00438 foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) { 00439 $vals = array(); 00440 ApiQueryBase::addTitleInfo( $vals, $title ); 00441 $vals['special'] = ''; 00442 if ( $title->isSpecialPage() && 00443 !SpecialPageFactory::exists( $title->getDbKey() ) ) { 00444 $vals['missing'] = ''; 00445 } elseif ( $title->getNamespace() == NS_MEDIA && 00446 !wfFindFile( $title ) ) { 00447 $vals['missing'] = ''; 00448 } 00449 $pages[$fakeId] = $vals; 00450 } 00451 00452 // Output general page information for found titles 00453 foreach ( $pageSet->getGoodTitles() as $pageid => $title ) { 00454 $vals = array(); 00455 $vals['pageid'] = $pageid; 00456 ApiQueryBase::addTitleInfo( $vals, $title ); 00457 $pages[$pageid] = $vals; 00458 } 00459 00460 if ( count( $pages ) ) { 00461 if ( $this->params['indexpageids'] ) { 00462 $pageIDs = array_keys( $pages ); 00463 // json treats all map keys as strings - converting to match 00464 $pageIDs = array_map( 'strval', $pageIDs ); 00465 $result->setIndexedTagName( $pageIDs, 'id' ); 00466 $result->addValue( 'query', 'pageids', $pageIDs ); 00467 } 00468 00469 $result->setIndexedTagName( $pages, 'page' ); 00470 $result->addValue( 'query', 'pages', $pages ); 00471 } 00472 if ( $this->params['export'] ) { 00473 $this->doExport( $pageSet, $result ); 00474 } 00475 } 00476 00481 private function doExport( $pageSet, $result ) { 00482 $exportTitles = array(); 00483 $titles = $pageSet->getGoodTitles(); 00484 if ( count( $titles ) ) { 00485 foreach ( $titles as $title ) { 00486 if ( $title->userCan( 'read' ) ) { 00487 $exportTitles[] = $title; 00488 } 00489 } 00490 } 00491 00492 $exporter = new WikiExporter( $this->getDB() ); 00493 // WikiExporter writes to stdout, so catch its 00494 // output with an ob 00495 ob_start(); 00496 $exporter->openStream(); 00497 foreach ( $exportTitles as $title ) { 00498 $exporter->pageByTitle( $title ); 00499 } 00500 $exporter->closeStream(); 00501 $exportxml = ob_get_contents(); 00502 ob_end_clean(); 00503 00504 // Don't check the size of exported stuff 00505 // It's not continuable, so it would cause more 00506 // problems than it'd solve 00507 $result->disableSizeCheck(); 00508 if ( $this->params['exportnowrap'] ) { 00509 $result->reset(); 00510 // Raw formatter will handle this 00511 $result->addValue( null, 'text', $exportxml ); 00512 $result->addValue( null, 'mime', 'text/xml' ); 00513 } else { 00514 $r = array(); 00515 ApiResult::setContent( $r, $exportxml ); 00516 $result->addValue( 'query', 'export', $r ); 00517 } 00518 $result->enableSizeCheck(); 00519 } 00520 00526 public function newGenerator( $generatorName ) { 00527 // Find class that implements requested generator 00528 if ( isset( $this->mQueryListModules[$generatorName] ) ) { 00529 $className = $this->mQueryListModules[$generatorName]; 00530 } elseif ( isset( $this->mQueryPropModules[$generatorName] ) ) { 00531 $className = $this->mQueryPropModules[$generatorName]; 00532 } else { 00533 ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" ); 00534 } 00535 $generator = new $className ( $this, $generatorName ); 00536 if ( !$generator instanceof ApiQueryGeneratorBase ) { 00537 $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' ); 00538 } 00539 $generator->setGeneratorMode(); 00540 return $generator; 00541 } 00542 00549 protected function executeGeneratorModule( $generator, $modules ) { 00550 // Generator results 00551 $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles ); 00552 00553 // Add any additional fields modules may need 00554 $generator->requestExtraData( $this->mPageSet ); 00555 $this->addCustomFldsToPageSet( $modules, $resultPageSet ); 00556 00557 // Populate page information with the original user input 00558 $this->mPageSet->execute(); 00559 00560 // populate resultPageSet with the generator output 00561 $generator->profileIn(); 00562 $generator->executeGenerator( $resultPageSet ); 00563 wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$resultPageSet ) ); 00564 $resultPageSet->finishPageSetGeneration(); 00565 $generator->profileOut(); 00566 00567 // Swap the resulting pageset back in 00568 $this->mPageSet = $resultPageSet; 00569 } 00570 00571 public function getAllowedParams() { 00572 return array( 00573 'prop' => array( 00574 ApiBase::PARAM_ISMULTI => true, 00575 ApiBase::PARAM_TYPE => $this->mPropModuleNames 00576 ), 00577 'list' => array( 00578 ApiBase::PARAM_ISMULTI => true, 00579 ApiBase::PARAM_TYPE => $this->mListModuleNames 00580 ), 00581 'meta' => array( 00582 ApiBase::PARAM_ISMULTI => true, 00583 ApiBase::PARAM_TYPE => $this->mMetaModuleNames 00584 ), 00585 'generator' => array( 00586 ApiBase::PARAM_TYPE => $this->mAllowedGenerators 00587 ), 00588 'redirects' => false, 00589 'converttitles' => false, 00590 'indexpageids' => false, 00591 'export' => false, 00592 'exportnowrap' => false, 00593 'iwurl' => false, 00594 ); 00595 } 00596 00601 public function makeHelpMsg() { 00602 // Make sure the internal object is empty 00603 // (just in case a sub-module decides to optimize during instantiation) 00604 $this->mPageSet = null; 00605 $this->mAllowedGenerators = array(); // Will be repopulated 00606 00607 $querySeparator = str_repeat( '--- ', 12 ); 00608 $moduleSeparator = str_repeat( '*** ', 14 ); 00609 $msg = "\n$querySeparator Query: Prop $querySeparator\n\n"; 00610 $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' ); 00611 $msg .= "\n$querySeparator Query: List $querySeparator\n\n"; 00612 $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' ); 00613 $msg .= "\n$querySeparator Query: Meta $querySeparator\n\n"; 00614 $msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' ); 00615 $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n"; 00616 00617 // Perform the base call last because the $this->mAllowedGenerators 00618 // will be updated inside makeHelpMsgHelper() 00619 // Use parent to make default message for the query module 00620 $msg = parent::makeHelpMsg() . $msg; 00621 00622 return $msg; 00623 } 00624 00631 private function makeHelpMsgHelper( $moduleList, $paramName ) { 00632 $moduleDescriptions = array(); 00633 00634 foreach ( $moduleList as $moduleName => $moduleClass ) { 00638 $module = new $moduleClass( $this, $moduleName, null ); 00639 00640 $msg = ApiMain::makeHelpMsgHeader( $module, $paramName ); 00641 $msg2 = $module->makeHelpMsg(); 00642 if ( $msg2 !== false ) { 00643 $msg .= $msg2; 00644 } 00645 if ( $module instanceof ApiQueryGeneratorBase ) { 00646 $this->mAllowedGenerators[] = $moduleName; 00647 $msg .= "Generator:\n This module may be used as a generator\n"; 00648 } 00649 $moduleDescriptions[] = $msg; 00650 } 00651 00652 return implode( "\n", $moduleDescriptions ); 00653 } 00654 00659 public function makeHelpMsgParameters() { 00660 $psModule = new ApiPageSet( $this ); 00661 return $psModule->makeHelpMsgParameters() . parent::makeHelpMsgParameters(); 00662 } 00663 00664 public function shouldCheckMaxlag() { 00665 return true; 00666 } 00667 00668 public function getParamDescription() { 00669 return array( 00670 'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below', 00671 'list' => 'Which lists to get. Module help is available below', 00672 'meta' => 'Which metadata to get about the site. Module help is available below', 00673 'generator' => array( 'Use the output of a list as the input for other prop/list/meta items', 00674 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ), 00675 'redirects' => 'Automatically resolve redirects', 00676 'converttitles' => array( "Convert titles to other variants if necessary. Only works if the wiki's content language supports variant conversion.", 00677 'Languages that support variant conversion include gan, iu, kk, ku, shi, sr, tg, zh' ), 00678 'indexpageids' => 'Include an additional pageids section listing all returned page IDs', 00679 'export' => 'Export the current revisions of all given or generated pages', 00680 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export', 00681 'iwurl' => 'Whether to get the full URL if the title is an interwiki link', 00682 ); 00683 } 00684 00685 public function getDescription() { 00686 return array( 00687 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,', 00688 'and is loosely based on the old query.php interface.', 00689 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites' 00690 ); 00691 } 00692 00693 public function getPossibleErrors() { 00694 return array_merge( parent::getPossibleErrors(), array( 00695 array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ), 00696 ) ); 00697 } 00698 00699 public function getExamples() { 00700 return array( 00701 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment', 00702 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions', 00703 ); 00704 } 00705 00706 public function getHelpUrls() { 00707 return array( 00708 'https://www.mediawiki.org/wiki/API:Meta', 00709 'https://www.mediawiki.org/wiki/API:Properties', 00710 'https://www.mediawiki.org/wiki/API:Lists', 00711 ); 00712 } 00713 00714 public function getVersion() { 00715 $psModule = new ApiPageSet( $this ); 00716 $vers = array(); 00717 $vers[] = __CLASS__ . ': $Id$'; 00718 $vers[] = $psModule->getVersion(); 00719 return $vers; 00720 } 00721 }