MediaWiki
REL1_24
|
00001 <?php 00038 class ApiQuery extends ApiBase { 00039 00044 private static $QueryPropModules = array( 00045 'categories' => 'ApiQueryCategories', 00046 'categoryinfo' => 'ApiQueryCategoryInfo', 00047 'contributors' => 'ApiQueryContributors', 00048 'duplicatefiles' => 'ApiQueryDuplicateFiles', 00049 'extlinks' => 'ApiQueryExternalLinks', 00050 'fileusage' => 'ApiQueryBacklinksprop', 00051 'images' => 'ApiQueryImages', 00052 'imageinfo' => 'ApiQueryImageInfo', 00053 'info' => 'ApiQueryInfo', 00054 'links' => 'ApiQueryLinks', 00055 'linkshere' => 'ApiQueryBacklinksprop', 00056 'iwlinks' => 'ApiQueryIWLinks', 00057 'langlinks' => 'ApiQueryLangLinks', 00058 'pageprops' => 'ApiQueryPageProps', 00059 'redirects' => 'ApiQueryBacklinksprop', 00060 'revisions' => 'ApiQueryRevisions', 00061 'stashimageinfo' => 'ApiQueryStashImageInfo', 00062 'templates' => 'ApiQueryLinks', 00063 'transcludedin' => 'ApiQueryBacklinksprop', 00064 ); 00065 00070 private static $QueryListModules = array( 00071 'allcategories' => 'ApiQueryAllCategories', 00072 'allfileusages' => 'ApiQueryAllLinks', 00073 'allimages' => 'ApiQueryAllImages', 00074 'alllinks' => 'ApiQueryAllLinks', 00075 'allpages' => 'ApiQueryAllPages', 00076 'allredirects' => 'ApiQueryAllLinks', 00077 'alltransclusions' => 'ApiQueryAllLinks', 00078 'allusers' => 'ApiQueryAllUsers', 00079 'backlinks' => 'ApiQueryBacklinks', 00080 'blocks' => 'ApiQueryBlocks', 00081 'categorymembers' => 'ApiQueryCategoryMembers', 00082 'deletedrevs' => 'ApiQueryDeletedrevs', 00083 'embeddedin' => 'ApiQueryBacklinks', 00084 'exturlusage' => 'ApiQueryExtLinksUsage', 00085 'filearchive' => 'ApiQueryFilearchive', 00086 'imageusage' => 'ApiQueryBacklinks', 00087 'iwbacklinks' => 'ApiQueryIWBacklinks', 00088 'langbacklinks' => 'ApiQueryLangBacklinks', 00089 'logevents' => 'ApiQueryLogEvents', 00090 'pageswithprop' => 'ApiQueryPagesWithProp', 00091 'pagepropnames' => 'ApiQueryPagePropNames', 00092 'prefixsearch' => 'ApiQueryPrefixSearch', 00093 'protectedtitles' => 'ApiQueryProtectedTitles', 00094 'querypage' => 'ApiQueryQueryPage', 00095 'random' => 'ApiQueryRandom', 00096 'recentchanges' => 'ApiQueryRecentChanges', 00097 'search' => 'ApiQuerySearch', 00098 'tags' => 'ApiQueryTags', 00099 'usercontribs' => 'ApiQueryContributions', 00100 'users' => 'ApiQueryUsers', 00101 'watchlist' => 'ApiQueryWatchlist', 00102 'watchlistraw' => 'ApiQueryWatchlistRaw', 00103 ); 00104 00109 private static $QueryMetaModules = array( 00110 'allmessages' => 'ApiQueryAllMessages', 00111 'siteinfo' => 'ApiQuerySiteinfo', 00112 'userinfo' => 'ApiQueryUserInfo', 00113 'filerepoinfo' => 'ApiQueryFileRepoInfo', 00114 'tokens' => 'ApiQueryTokens', 00115 ); 00116 00120 private $mPageSet; 00121 00122 private $mParams; 00123 private $mNamedDB = array(); 00124 private $mModuleMgr; 00125 00130 public function __construct( ApiMain $main, $action ) { 00131 parent::__construct( $main, $action ); 00132 00133 $this->mModuleMgr = new ApiModuleManager( $this ); 00134 00135 // Allow custom modules to be added in LocalSettings.php 00136 $config = $this->getConfig(); 00137 $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' ); 00138 $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' ); 00139 $this->mModuleMgr->addModules( self::$QueryListModules, 'list' ); 00140 $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' ); 00141 $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' ); 00142 $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' ); 00143 00144 // Create PageSet that will process titles/pageids/revids/generator 00145 $this->mPageSet = new ApiPageSet( $this ); 00146 } 00147 00152 public function getModuleManager() { 00153 return $this->mModuleMgr; 00154 } 00155 00166 public function getNamedDB( $name, $db, $groups ) { 00167 if ( !array_key_exists( $name, $this->mNamedDB ) ) { 00168 $this->profileDBIn(); 00169 $this->mNamedDB[$name] = wfGetDB( $db, $groups ); 00170 $this->profileDBOut(); 00171 } 00172 00173 return $this->mNamedDB[$name]; 00174 } 00175 00180 public function getPageSet() { 00181 return $this->mPageSet; 00182 } 00183 00189 public function getModules() { 00190 wfDeprecated( __METHOD__, '1.21' ); 00191 00192 return $this->getModuleManager()->getNamesWithClasses(); 00193 } 00194 00200 public function getGenerators() { 00201 wfDeprecated( __METHOD__, '1.21' ); 00202 $gens = array(); 00203 foreach ( $this->mModuleMgr->getNamesWithClasses() as $name => $class ) { 00204 if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) { 00205 $gens[$name] = $class; 00206 } 00207 } 00208 00209 return $gens; 00210 } 00211 00218 function getModuleType( $moduleName ) { 00219 return $this->getModuleManager()->getModuleGroup( $moduleName ); 00220 } 00221 00225 public function getCustomPrinter() { 00226 // If &exportnowrap is set, use the raw formatter 00227 if ( $this->getParameter( 'export' ) && 00228 $this->getParameter( 'exportnowrap' ) 00229 ) { 00230 return new ApiFormatRaw( $this->getMain(), 00231 $this->getMain()->createPrinterByName( 'xml' ) ); 00232 } else { 00233 return null; 00234 } 00235 } 00236 00247 public function execute() { 00248 $this->mParams = $this->extractRequestParams(); 00249 00250 // Instantiate requested modules 00251 $allModules = array(); 00252 $this->instantiateModules( $allModules, 'prop' ); 00253 $propModules = array_keys( $allModules ); 00254 $this->instantiateModules( $allModules, 'list' ); 00255 $this->instantiateModules( $allModules, 'meta' ); 00256 00257 // Filter modules based on continue parameter 00258 list( $generatorDone, $modules ) = $this->getResult()->beginContinuation( 00259 $this->mParams['continue'], $allModules, $propModules 00260 ); 00261 00262 if ( !$generatorDone ) { 00263 // Query modules may optimize data requests through the $this->getPageSet() 00264 // object by adding extra fields from the page table. 00265 foreach ( $modules as $module ) { 00266 $module->requestExtraData( $this->mPageSet ); 00267 } 00268 // Populate page/revision information 00269 $this->mPageSet->execute(); 00270 // Record page information (title, namespace, if exists, etc) 00271 $this->outputGeneralPageInfo(); 00272 } else { 00273 $this->mPageSet->executeDryRun(); 00274 } 00275 00276 $cacheMode = $this->mPageSet->getCacheMode(); 00277 00278 // Execute all unfinished modules 00280 foreach ( $modules as $module ) { 00281 $params = $module->extractRequestParams(); 00282 $cacheMode = $this->mergeCacheMode( 00283 $cacheMode, $module->getCacheMode( $params ) ); 00284 $module->profileIn(); 00285 $module->execute(); 00286 wfRunHooks( 'APIQueryAfterExecute', array( &$module ) ); 00287 $module->profileOut(); 00288 } 00289 00290 // Set the cache mode 00291 $this->getMain()->setCacheMode( $cacheMode ); 00292 00293 // Write the continuation data into the result 00294 $this->getResult()->endContinuation( 00295 $this->mParams['continue'] === null ? 'raw' : 'standard' 00296 ); 00297 } 00298 00308 protected function mergeCacheMode( $cacheMode, $modCacheMode ) { 00309 if ( $modCacheMode === 'anon-public-user-private' ) { 00310 if ( $cacheMode !== 'private' ) { 00311 $cacheMode = 'anon-public-user-private'; 00312 } 00313 } elseif ( $modCacheMode === 'public' ) { 00314 // do nothing, if it's public already it will stay public 00315 } else { // private 00316 $cacheMode = 'private'; 00317 } 00318 00319 return $cacheMode; 00320 } 00321 00327 private function instantiateModules( &$modules, $param ) { 00328 $wasPosted = $this->getRequest()->wasPosted(); 00329 if ( isset( $this->mParams[$param] ) ) { 00330 foreach ( $this->mParams[$param] as $moduleName ) { 00331 $instance = $this->mModuleMgr->getModule( $moduleName, $param ); 00332 if ( $instance === null ) { 00333 ApiBase::dieDebug( __METHOD__, 'Error instantiating module' ); 00334 } 00335 if ( !$wasPosted && $instance->mustBePosted() ) { 00336 $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) ); 00337 } 00338 // Ignore duplicates. TODO 2.0: die()? 00339 if ( !array_key_exists( $moduleName, $modules ) ) { 00340 $modules[$moduleName] = $instance; 00341 } 00342 } 00343 } 00344 } 00345 00351 private function outputGeneralPageInfo() { 00352 $pageSet = $this->getPageSet(); 00353 $result = $this->getResult(); 00354 00355 // We can't really handle max-result-size failure here, but we need to 00356 // check anyway in case someone set the limit stupidly low. 00357 $fit = true; 00358 00359 $values = $pageSet->getNormalizedTitlesAsResult( $result ); 00360 if ( $values ) { 00361 $fit = $fit && $result->addValue( 'query', 'normalized', $values ); 00362 } 00363 $values = $pageSet->getConvertedTitlesAsResult( $result ); 00364 if ( $values ) { 00365 $fit = $fit && $result->addValue( 'query', 'converted', $values ); 00366 } 00367 $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] ); 00368 if ( $values ) { 00369 $fit = $fit && $result->addValue( 'query', 'interwiki', $values ); 00370 } 00371 $values = $pageSet->getRedirectTitlesAsResult( $result ); 00372 if ( $values ) { 00373 $fit = $fit && $result->addValue( 'query', 'redirects', $values ); 00374 } 00375 $values = $pageSet->getMissingRevisionIDsAsResult( $result ); 00376 if ( $values ) { 00377 $fit = $fit && $result->addValue( 'query', 'badrevids', $values ); 00378 } 00379 00380 // Page elements 00381 $pages = array(); 00382 00383 // Report any missing titles 00384 foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) { 00385 $vals = array(); 00386 ApiQueryBase::addTitleInfo( $vals, $title ); 00387 $vals['missing'] = ''; 00388 $pages[$fakeId] = $vals; 00389 } 00390 // Report any invalid titles 00391 foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) { 00392 $pages[$fakeId] = array( 'title' => $title, 'invalid' => '' ); 00393 } 00394 // Report any missing page ids 00395 foreach ( $pageSet->getMissingPageIDs() as $pageid ) { 00396 $pages[$pageid] = array( 00397 'pageid' => $pageid, 00398 'missing' => '' 00399 ); 00400 } 00401 // Report special pages 00403 foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) { 00404 $vals = array(); 00405 ApiQueryBase::addTitleInfo( $vals, $title ); 00406 $vals['special'] = ''; 00407 if ( $title->isSpecialPage() && 00408 !SpecialPageFactory::exists( $title->getDBkey() ) 00409 ) { 00410 $vals['missing'] = ''; 00411 } elseif ( $title->getNamespace() == NS_MEDIA && 00412 !wfFindFile( $title ) 00413 ) { 00414 $vals['missing'] = ''; 00415 } 00416 $pages[$fakeId] = $vals; 00417 } 00418 00419 // Output general page information for found titles 00420 foreach ( $pageSet->getGoodTitles() as $pageid => $title ) { 00421 $vals = array(); 00422 $vals['pageid'] = $pageid; 00423 ApiQueryBase::addTitleInfo( $vals, $title ); 00424 $pages[$pageid] = $vals; 00425 } 00426 00427 if ( count( $pages ) ) { 00428 if ( $this->mParams['indexpageids'] ) { 00429 $pageIDs = array_keys( $pages ); 00430 // json treats all map keys as strings - converting to match 00431 $pageIDs = array_map( 'strval', $pageIDs ); 00432 $result->setIndexedTagName( $pageIDs, 'id' ); 00433 $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs ); 00434 } 00435 00436 $result->setIndexedTagName( $pages, 'page' ); 00437 $fit = $fit && $result->addValue( 'query', 'pages', $pages ); 00438 } 00439 00440 if ( !$fit ) { 00441 $this->dieUsage( 00442 'The value of $wgAPIMaxResultSize on this wiki is ' . 00443 'too small to hold basic result information', 00444 'badconfig' 00445 ); 00446 } 00447 00448 if ( $this->mParams['export'] ) { 00449 $this->doExport( $pageSet, $result ); 00450 } 00451 } 00452 00463 public function setGeneratorContinue( $module, $paramName, $paramValue ) { 00464 wfDeprecated( __METHOD__, '1.24' ); 00465 $this->getResult()->setGeneratorContinueParam( $module, $paramName, $paramValue ); 00466 return $this->getParameter( 'continue' ) !== null; 00467 } 00468 00473 private function doExport( $pageSet, $result ) { 00474 $exportTitles = array(); 00475 $titles = $pageSet->getGoodTitles(); 00476 if ( count( $titles ) ) { 00477 $user = $this->getUser(); 00479 foreach ( $titles as $title ) { 00480 if ( $title->userCan( 'read', $user ) ) { 00481 $exportTitles[] = $title; 00482 } 00483 } 00484 } 00485 00486 $exporter = new WikiExporter( $this->getDB() ); 00487 // WikiExporter writes to stdout, so catch its 00488 // output with an ob 00489 ob_start(); 00490 $exporter->openStream(); 00491 foreach ( $exportTitles as $title ) { 00492 $exporter->pageByTitle( $title ); 00493 } 00494 $exporter->closeStream(); 00495 $exportxml = ob_get_contents(); 00496 ob_end_clean(); 00497 00498 // Don't check the size of exported stuff 00499 // It's not continuable, so it would cause more 00500 // problems than it'd solve 00501 if ( $this->mParams['exportnowrap'] ) { 00502 $result->reset(); 00503 // Raw formatter will handle this 00504 $result->addValue( null, 'text', $exportxml, ApiResult::NO_SIZE_CHECK ); 00505 $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK ); 00506 } else { 00507 $r = array(); 00508 ApiResult::setContent( $r, $exportxml ); 00509 $result->addValue( 'query', 'export', $r, ApiResult::NO_SIZE_CHECK ); 00510 } 00511 } 00512 00513 public function getAllowedParams( $flags = 0 ) { 00514 $result = array( 00515 'prop' => array( 00516 ApiBase::PARAM_ISMULTI => true, 00517 ApiBase::PARAM_TYPE => 'submodule', 00518 ), 00519 'list' => array( 00520 ApiBase::PARAM_ISMULTI => true, 00521 ApiBase::PARAM_TYPE => 'submodule', 00522 ), 00523 'meta' => array( 00524 ApiBase::PARAM_ISMULTI => true, 00525 ApiBase::PARAM_TYPE => 'submodule', 00526 ), 00527 'indexpageids' => false, 00528 'export' => false, 00529 'exportnowrap' => false, 00530 'iwurl' => false, 00531 'continue' => null, 00532 'rawcontinue' => false, 00533 ); 00534 if ( $flags ) { 00535 $result += $this->getPageSet()->getFinalParams( $flags ); 00536 } 00537 00538 return $result; 00539 } 00540 00545 public function makeHelpMsg() { 00546 00547 // Use parent to make default message for the query module 00548 $msg = parent::makeHelpMsg(); 00549 00550 $querySeparator = str_repeat( '--- ', 12 ); 00551 $moduleSeparator = str_repeat( '*** ', 14 ); 00552 $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n"; 00553 $msg .= $this->makeHelpMsgHelper( 'prop' ); 00554 $msg .= "\n$querySeparator Query: List $querySeparator\n\n"; 00555 $msg .= $this->makeHelpMsgHelper( 'list' ); 00556 $msg .= "\n$querySeparator Query: Meta $querySeparator\n\n"; 00557 $msg .= $this->makeHelpMsgHelper( 'meta' ); 00558 $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n"; 00559 00560 return $msg; 00561 } 00562 00568 private function makeHelpMsgHelper( $group ) { 00569 $moduleDescriptions = array(); 00570 00571 $moduleNames = $this->mModuleMgr->getNames( $group ); 00572 sort( $moduleNames ); 00573 foreach ( $moduleNames as $name ) { 00577 $module = $this->mModuleMgr->getModule( $name ); 00578 00579 $msg = ApiMain::makeHelpMsgHeader( $module, $group ); 00580 $msg2 = $module->makeHelpMsg(); 00581 if ( $msg2 !== false ) { 00582 $msg .= $msg2; 00583 } 00584 if ( $module instanceof ApiQueryGeneratorBase ) { 00585 $msg .= "Generator:\n This module may be used as a generator\n"; 00586 } 00587 $moduleDescriptions[] = $msg; 00588 } 00589 00590 return implode( "\n", $moduleDescriptions ); 00591 } 00592 00593 public function shouldCheckMaxlag() { 00594 return true; 00595 } 00596 00597 public function getParamDescription() { 00598 return $this->getPageSet()->getFinalParamDescription() + array( 00599 'prop' => 'Which properties to get for the titles/revisions/pageids. ' . 00600 'Module help is available below', 00601 'list' => 'Which lists to get. Module help is available below', 00602 'meta' => 'Which metadata to get about the site. Module help is available below', 00603 'indexpageids' => 'Include an additional pageids section listing all returned page IDs', 00604 'export' => 'Export the current revisions of all given or generated pages', 00605 'exportnowrap' => 'Return the export XML without wrapping it in an ' . 00606 'XML result (same format as Special:Export). Can only be used with export', 00607 'iwurl' => 'Whether to get the full URL if the title is an interwiki link', 00608 'continue' => array( 00609 'When present, formats query-continue as key-value pairs that ' . 00610 'should simply be merged into the original request.', 00611 'This parameter must be set to an empty string in the initial query.', 00612 'This parameter is recommended for all new development, and ' . 00613 'will be made default in the next API version.' 00614 ), 00615 'rawcontinue' => 'Currently ignored. In the future, \'continue=\' will become the ' . 00616 'default and this will be needed to receive the raw query-continue data.', 00617 ); 00618 } 00619 00620 public function getDescription() { 00621 return array( 00622 'Query API module allows applications to get needed pieces of data ' . 00623 'from the MediaWiki databases,', 00624 'and is loosely based on the old query.php interface.', 00625 'All data modifications will first have to use query to acquire a ' . 00626 'token to prevent abuse from malicious sites.' 00627 ); 00628 } 00629 00630 public function getExamples() { 00631 return array( 00632 'api.php?action=query&prop=revisions&meta=siteinfo&' . 00633 'titles=Main%20Page&rvprop=user|comment&continue=', 00634 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions&continue=', 00635 ); 00636 } 00637 00638 public function getHelpUrls() { 00639 return array( 00640 'https://www.mediawiki.org/wiki/API:Query', 00641 'https://www.mediawiki.org/wiki/API:Meta', 00642 'https://www.mediawiki.org/wiki/API:Properties', 00643 'https://www.mediawiki.org/wiki/API:Lists', 00644 ); 00645 } 00646 }