MediaWiki  REL1_23
ApiQuery.php
Go to the documentation of this file.
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         'images' => 'ApiQueryImages',
00051         'imageinfo' => 'ApiQueryImageInfo',
00052         'info' => 'ApiQueryInfo',
00053         'links' => 'ApiQueryLinks',
00054         'iwlinks' => 'ApiQueryIWLinks',
00055         'langlinks' => 'ApiQueryLangLinks',
00056         'pageprops' => 'ApiQueryPageProps',
00057         'redirects' => 'ApiQueryRedirects',
00058         'revisions' => 'ApiQueryRevisions',
00059         'stashimageinfo' => 'ApiQueryStashImageInfo',
00060         'templates' => 'ApiQueryLinks',
00061     );
00062 
00067     private static $QueryListModules = array(
00068         'allcategories' => 'ApiQueryAllCategories',
00069         'allfileusages' => 'ApiQueryAllLinks',
00070         'allimages' => 'ApiQueryAllImages',
00071         'alllinks' => 'ApiQueryAllLinks',
00072         'allpages' => 'ApiQueryAllPages',
00073         'allredirects' => 'ApiQueryAllLinks',
00074         'alltransclusions' => 'ApiQueryAllLinks',
00075         'allusers' => 'ApiQueryAllUsers',
00076         'backlinks' => 'ApiQueryBacklinks',
00077         'blocks' => 'ApiQueryBlocks',
00078         'categorymembers' => 'ApiQueryCategoryMembers',
00079         'deletedrevs' => 'ApiQueryDeletedrevs',
00080         'embeddedin' => 'ApiQueryBacklinks',
00081         'exturlusage' => 'ApiQueryExtLinksUsage',
00082         'filearchive' => 'ApiQueryFilearchive',
00083         'imageusage' => 'ApiQueryBacklinks',
00084         'iwbacklinks' => 'ApiQueryIWBacklinks',
00085         'langbacklinks' => 'ApiQueryLangBacklinks',
00086         'logevents' => 'ApiQueryLogEvents',
00087         'pageswithprop' => 'ApiQueryPagesWithProp',
00088         'pagepropnames' => 'ApiQueryPagePropNames',
00089         'prefixsearch' => 'ApiQueryPrefixSearch',
00090         'protectedtitles' => 'ApiQueryProtectedTitles',
00091         'querypage' => 'ApiQueryQueryPage',
00092         'random' => 'ApiQueryRandom',
00093         'recentchanges' => 'ApiQueryRecentChanges',
00094         'search' => 'ApiQuerySearch',
00095         'tags' => 'ApiQueryTags',
00096         'usercontribs' => 'ApiQueryContributions',
00097         'users' => 'ApiQueryUsers',
00098         'watchlist' => 'ApiQueryWatchlist',
00099         'watchlistraw' => 'ApiQueryWatchlistRaw',
00100     );
00101 
00106     private static $QueryMetaModules = array(
00107         'allmessages' => 'ApiQueryAllMessages',
00108         'siteinfo' => 'ApiQuerySiteinfo',
00109         'userinfo' => 'ApiQueryUserInfo',
00110         'filerepoinfo' => 'ApiQueryFileRepoInfo',
00111     );
00112 
00116     private $mPageSet;
00117 
00118     private $mParams;
00119     private $mNamedDB = array();
00120     private $mModuleMgr;
00121     private $mGeneratorContinue;
00122     private $mUseLegacyContinue;
00123 
00128     public function __construct( $main, $action ) {
00129         parent::__construct( $main, $action );
00130 
00131         $this->mModuleMgr = new ApiModuleManager( $this );
00132 
00133         // Allow custom modules to be added in LocalSettings.php
00134         global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
00135         $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
00136         $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' );
00137         $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
00138         $this->mModuleMgr->addModules( $wgAPIListModules, 'list' );
00139         $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
00140         $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' );
00141 
00142         // Create PageSet that will process titles/pageids/revids/generator
00143         $this->mPageSet = new ApiPageSet( $this );
00144     }
00145 
00150     public function getModuleManager() {
00151         return $this->mModuleMgr;
00152     }
00153 
00164     public function getNamedDB( $name, $db, $groups ) {
00165         if ( !array_key_exists( $name, $this->mNamedDB ) ) {
00166             $this->profileDBIn();
00167             $this->mNamedDB[$name] = wfGetDB( $db, $groups );
00168             $this->profileDBOut();
00169         }
00170 
00171         return $this->mNamedDB[$name];
00172     }
00173 
00178     public function getPageSet() {
00179         return $this->mPageSet;
00180     }
00181 
00187     public function getModules() {
00188         wfDeprecated( __METHOD__, '1.21' );
00189 
00190         return $this->getModuleManager()->getNamesWithClasses();
00191     }
00192 
00198     public function getGenerators() {
00199         wfDeprecated( __METHOD__, '1.21' );
00200         $gens = array();
00201         foreach ( $this->mModuleMgr->getNamesWithClasses() as $name => $class ) {
00202             if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
00203                 $gens[$name] = $class;
00204             }
00205         }
00206 
00207         return $gens;
00208     }
00209 
00216     function getModuleType( $moduleName ) {
00217         return $this->getModuleManager()->getModuleGroup( $moduleName );
00218     }
00219 
00223     public function getCustomPrinter() {
00224         // If &exportnowrap is set, use the raw formatter
00225         if ( $this->getParameter( 'export' ) &&
00226             $this->getParameter( 'exportnowrap' )
00227         ) {
00228             return new ApiFormatRaw( $this->getMain(),
00229                 $this->getMain()->createPrinterByName( 'xml' ) );
00230         } else {
00231             return null;
00232         }
00233     }
00234 
00245     public function execute() {
00246         $this->mParams = $this->extractRequestParams();
00247 
00248         // $pagesetParams is a array of parameter names used by the pageset generator
00249         //   or null if pageset has already finished and is no longer needed
00250         // $completeModules is a set of complete modules with the name as key
00251         $this->initContinue( $pagesetParams, $completeModules );
00252 
00253         // Instantiate requested modules
00254         $allModules = array();
00255         $this->instantiateModules( $allModules, 'prop' );
00256         $propModules = $allModules; // Keep a copy
00257         $this->instantiateModules( $allModules, 'list' );
00258         $this->instantiateModules( $allModules, 'meta' );
00259 
00260         // Filter modules based on continue parameter
00261         $modules = $this->initModules( $allModules, $completeModules, $pagesetParams !== null );
00262 
00263         // Execute pageset if in legacy mode or if pageset is not done
00264         if ( $completeModules === null || $pagesetParams !== null ) {
00265             // Populate page/revision information
00266             $this->mPageSet->execute();
00267             // Record page information (title, namespace, if exists, etc)
00268             $this->outputGeneralPageInfo();
00269         } else {
00270             $this->mPageSet->executeDryRun();
00271         }
00272 
00273         $cacheMode = $this->mPageSet->getCacheMode();
00274 
00275         // Execute all unfinished modules
00277         foreach ( $modules as $module ) {
00278             $params = $module->extractRequestParams();
00279             $cacheMode = $this->mergeCacheMode(
00280                 $cacheMode, $module->getCacheMode( $params ) );
00281             $module->profileIn();
00282             $module->execute();
00283             wfRunHooks( 'APIQueryAfterExecute', array( &$module ) );
00284             $module->profileOut();
00285         }
00286 
00287         // Set the cache mode
00288         $this->getMain()->setCacheMode( $cacheMode );
00289 
00290         if ( $completeModules === null ) {
00291             return; // Legacy continue, we are done
00292         }
00293 
00294         // Reformat query-continue result section
00295         $result = $this->getResult();
00296         $qc = $result->getData();
00297         if ( isset( $qc['query-continue'] ) ) {
00298             $qc = $qc['query-continue'];
00299             $result->unsetValue( null, 'query-continue' );
00300         } elseif ( $this->mGeneratorContinue !== null ) {
00301             $qc = array();
00302         } else {
00303             // no more "continue"s, we are done!
00304             return;
00305         }
00306 
00307         // we are done with all the modules that do not have result in query-continue
00308         $completeModules = array_merge( $completeModules, array_diff_key( $modules, $qc ) );
00309         if ( $pagesetParams !== null ) {
00310             // The pageset is still in use, check if all props have finished
00311             $incompleteProps = array_intersect_key( $propModules, $qc );
00312             if ( count( $incompleteProps ) > 0 ) {
00313                 // Properties are not done, continue with the same pageset state - copy current parameters
00314                 $main = $this->getMain();
00315                 $contValues = array();
00316                 foreach ( $pagesetParams as $param ) {
00317                     // The param name is already prefix-encoded
00318                     $contValues[$param] = $main->getVal( $param );
00319                 }
00320             } elseif ( $this->mGeneratorContinue !== null ) {
00321                 // Move to the next set of pages produced by pageset, properties need to be restarted
00322                 $contValues = $this->mGeneratorContinue;
00323                 $pagesetParams = array_keys( $contValues );
00324                 $completeModules = array_diff_key( $completeModules, $propModules );
00325             } else {
00326                 // Done with the pageset, finish up with the the lists and meta modules
00327                 $pagesetParams = null;
00328             }
00329         }
00330 
00331         $continue = '||' . implode( '|', array_keys( $completeModules ) );
00332         if ( $pagesetParams !== null ) {
00333             // list of all pageset parameters to use in the next request
00334             $continue = implode( '|', $pagesetParams ) . $continue;
00335         } else {
00336             // we are done with the pageset
00337             $contValues = array();
00338             $continue = '-' . $continue;
00339         }
00340         $contValues['continue'] = $continue;
00341         foreach ( $qc as $qcModule ) {
00342             foreach ( $qcModule as $qcKey => $qcValue ) {
00343                 $contValues[$qcKey] = $qcValue;
00344             }
00345         }
00346         $this->getResult()->addValue( null, 'continue', $contValues );
00347     }
00348 
00354     private function initContinue( &$pagesetParams, &$completeModules ) {
00355         $pagesetParams = array();
00356         $continue = $this->mParams['continue'];
00357         if ( $continue !== null ) {
00358             $this->mUseLegacyContinue = false;
00359             if ( $continue !== '' ) {
00360                 // Format: ' pagesetParam1 | pagesetParam2 || module1 | module2 | module3 | ...
00361                 // If pageset is done, use '-'
00362                 $continue = explode( '||', $continue );
00363                 $this->dieContinueUsageIf( count( $continue ) !== 2 );
00364                 if ( $continue[0] === '-' ) {
00365                     $pagesetParams = null; // No need to execute pageset
00366                 } elseif ( $continue[0] !== '' ) {
00367                     // list of pageset params that might need to be repeated
00368                     $pagesetParams = explode( '|', $continue[0] );
00369                 }
00370                 $continue = $continue[1];
00371             }
00372             if ( $continue !== '' ) {
00373                 $completeModules = array_flip( explode( '|', $continue ) );
00374             } else {
00375                 $completeModules = array();
00376             }
00377         } else {
00378             $this->mUseLegacyContinue = true;
00379             $completeModules = null;
00380         }
00381     }
00382 
00390     private function initModules( $allModules, $completeModules, $usePageset ) {
00391         $modules = $allModules;
00392         $tmp = $completeModules;
00393         $wasPosted = $this->getRequest()->wasPosted();
00394 
00396         foreach ( $allModules as $moduleName => $module ) {
00397             if ( !$wasPosted && $module->mustBePosted() ) {
00398                 $this->dieUsageMsgOrDebug( array( 'mustbeposted', $moduleName ) );
00399             }
00400             if ( $completeModules !== null && array_key_exists( $moduleName, $completeModules ) ) {
00401                 // If this module is done, mark all its params as used
00402                 $module->extractRequestParams();
00403                 // Make sure this module is not used during execution
00404                 unset( $modules[$moduleName] );
00405                 unset( $tmp[$moduleName] );
00406             } elseif ( $completeModules === null || $usePageset ) {
00407                 // Query modules may optimize data requests through the $this->getPageSet()
00408                 // object by adding extra fields from the page table.
00409                 // This function will gather all the extra request fields from the modules.
00410                 $module->requestExtraData( $this->mPageSet );
00411             } else {
00412                 // Error - this prop module must have finished before generator is done
00413                 $this->dieContinueUsageIf( $this->mModuleMgr->getModuleGroup( $moduleName ) === 'prop' );
00414             }
00415         }
00416         $this->dieContinueUsageIf( $completeModules !== null && count( $tmp ) !== 0 );
00417 
00418         return $modules;
00419     }
00420 
00430     protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
00431         if ( $modCacheMode === 'anon-public-user-private' ) {
00432             if ( $cacheMode !== 'private' ) {
00433                 $cacheMode = 'anon-public-user-private';
00434             }
00435         } elseif ( $modCacheMode === 'public' ) {
00436             // do nothing, if it's public already it will stay public
00437         } else { // private
00438             $cacheMode = 'private';
00439         }
00440 
00441         return $cacheMode;
00442     }
00443 
00449     private function instantiateModules( &$modules, $param ) {
00450         if ( isset( $this->mParams[$param] ) ) {
00451             foreach ( $this->mParams[$param] as $moduleName ) {
00452                 $instance = $this->mModuleMgr->getModule( $moduleName, $param );
00453                 if ( $instance === null ) {
00454                     ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
00455                 }
00456                 // Ignore duplicates. TODO 2.0: die()?
00457                 if ( !array_key_exists( $moduleName, $modules ) ) {
00458                     $modules[$moduleName] = $instance;
00459                 }
00460             }
00461         }
00462     }
00463 
00469     private function outputGeneralPageInfo() {
00470         $pageSet = $this->getPageSet();
00471         $result = $this->getResult();
00472 
00473         // We don't check for a full result set here because we can't be adding
00474         // more than 380K. The maximum revision size is in the megabyte range,
00475         // and the maximum result size must be even higher than that.
00476 
00477         $values = $pageSet->getNormalizedTitlesAsResult( $result );
00478         if ( $values ) {
00479             $result->addValue( 'query', 'normalized', $values );
00480         }
00481         $values = $pageSet->getConvertedTitlesAsResult( $result );
00482         if ( $values ) {
00483             $result->addValue( 'query', 'converted', $values );
00484         }
00485         $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
00486         if ( $values ) {
00487             $result->addValue( 'query', 'interwiki', $values );
00488         }
00489         $values = $pageSet->getRedirectTitlesAsResult( $result );
00490         if ( $values ) {
00491             $result->addValue( 'query', 'redirects', $values );
00492         }
00493         $values = $pageSet->getMissingRevisionIDsAsResult( $result );
00494         if ( $values ) {
00495             $result->addValue( 'query', 'badrevids', $values );
00496         }
00497 
00498         // Page elements
00499         $pages = array();
00500 
00501         // Report any missing titles
00502         foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
00503             $vals = array();
00504             ApiQueryBase::addTitleInfo( $vals, $title );
00505             $vals['missing'] = '';
00506             $pages[$fakeId] = $vals;
00507         }
00508         // Report any invalid titles
00509         foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) {
00510             $pages[$fakeId] = array( 'title' => $title, 'invalid' => '' );
00511         }
00512         // Report any missing page ids
00513         foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
00514             $pages[$pageid] = array(
00515                 'pageid' => $pageid,
00516                 'missing' => ''
00517             );
00518         }
00519         // Report special pages
00521         foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
00522             $vals = array();
00523             ApiQueryBase::addTitleInfo( $vals, $title );
00524             $vals['special'] = '';
00525             if ( $title->isSpecialPage() &&
00526                 !SpecialPageFactory::exists( $title->getDBkey() )
00527             ) {
00528                 $vals['missing'] = '';
00529             } elseif ( $title->getNamespace() == NS_MEDIA &&
00530                 !wfFindFile( $title )
00531             ) {
00532                 $vals['missing'] = '';
00533             }
00534             $pages[$fakeId] = $vals;
00535         }
00536 
00537         // Output general page information for found titles
00538         foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
00539             $vals = array();
00540             $vals['pageid'] = $pageid;
00541             ApiQueryBase::addTitleInfo( $vals, $title );
00542             $pages[$pageid] = $vals;
00543         }
00544 
00545         if ( count( $pages ) ) {
00546             if ( $this->mParams['indexpageids'] ) {
00547                 $pageIDs = array_keys( $pages );
00548                 // json treats all map keys as strings - converting to match
00549                 $pageIDs = array_map( 'strval', $pageIDs );
00550                 $result->setIndexedTagName( $pageIDs, 'id' );
00551                 $result->addValue( 'query', 'pageids', $pageIDs );
00552             }
00553 
00554             $result->setIndexedTagName( $pages, 'page' );
00555             $result->addValue( 'query', 'pages', $pages );
00556         }
00557         if ( $this->mParams['export'] ) {
00558             $this->doExport( $pageSet, $result );
00559         }
00560     }
00561 
00571     public function setGeneratorContinue( $module, $paramName, $paramValue ) {
00572         if ( $this->mUseLegacyContinue ) {
00573             return false;
00574         }
00575         $paramName = $module->encodeParamName( $paramName );
00576         if ( $this->mGeneratorContinue === null ) {
00577             $this->mGeneratorContinue = array();
00578         }
00579         $this->mGeneratorContinue[$paramName] = $paramValue;
00580 
00581         return true;
00582     }
00583 
00588     private function doExport( $pageSet, $result ) {
00589         $exportTitles = array();
00590         $titles = $pageSet->getGoodTitles();
00591         if ( count( $titles ) ) {
00592             $user = $this->getUser();
00594             foreach ( $titles as $title ) {
00595                 if ( $title->userCan( 'read', $user ) ) {
00596                     $exportTitles[] = $title;
00597                 }
00598             }
00599         }
00600 
00601         $exporter = new WikiExporter( $this->getDB() );
00602         // WikiExporter writes to stdout, so catch its
00603         // output with an ob
00604         ob_start();
00605         $exporter->openStream();
00606         foreach ( $exportTitles as $title ) {
00607             $exporter->pageByTitle( $title );
00608         }
00609         $exporter->closeStream();
00610         $exportxml = ob_get_contents();
00611         ob_end_clean();
00612 
00613         // Don't check the size of exported stuff
00614         // It's not continuable, so it would cause more
00615         // problems than it'd solve
00616         $result->disableSizeCheck();
00617         if ( $this->mParams['exportnowrap'] ) {
00618             $result->reset();
00619             // Raw formatter will handle this
00620             $result->addValue( null, 'text', $exportxml );
00621             $result->addValue( null, 'mime', 'text/xml' );
00622         } else {
00623             $r = array();
00624             ApiResult::setContent( $r, $exportxml );
00625             $result->addValue( 'query', 'export', $r );
00626         }
00627         $result->enableSizeCheck();
00628     }
00629 
00630     public function getAllowedParams( $flags = 0 ) {
00631         $result = array(
00632             'prop' => array(
00633                 ApiBase::PARAM_ISMULTI => true,
00634                 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' )
00635             ),
00636             'list' => array(
00637                 ApiBase::PARAM_ISMULTI => true,
00638                 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' )
00639             ),
00640             'meta' => array(
00641                 ApiBase::PARAM_ISMULTI => true,
00642                 ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' )
00643             ),
00644             'indexpageids' => false,
00645             'export' => false,
00646             'exportnowrap' => false,
00647             'iwurl' => false,
00648             'continue' => null,
00649         );
00650         if ( $flags ) {
00651             $result += $this->getPageSet()->getFinalParams( $flags );
00652         }
00653 
00654         return $result;
00655     }
00656 
00661     public function makeHelpMsg() {
00662 
00663         // Use parent to make default message for the query module
00664         $msg = parent::makeHelpMsg();
00665 
00666         $querySeparator = str_repeat( '--- ', 12 );
00667         $moduleSeparator = str_repeat( '*** ', 14 );
00668         $msg .= "\n$querySeparator Query: Prop  $querySeparator\n\n";
00669         $msg .= $this->makeHelpMsgHelper( 'prop' );
00670         $msg .= "\n$querySeparator Query: List  $querySeparator\n\n";
00671         $msg .= $this->makeHelpMsgHelper( 'list' );
00672         $msg .= "\n$querySeparator Query: Meta  $querySeparator\n\n";
00673         $msg .= $this->makeHelpMsgHelper( 'meta' );
00674         $msg .= "\n\n$moduleSeparator Modules: continuation  $moduleSeparator\n\n";
00675 
00676         return $msg;
00677     }
00678 
00684     private function makeHelpMsgHelper( $group ) {
00685         $moduleDescriptions = array();
00686 
00687         $moduleNames = $this->mModuleMgr->getNames( $group );
00688         sort( $moduleNames );
00689         foreach ( $moduleNames as $name ) {
00693             $module = $this->mModuleMgr->getModule( $name );
00694 
00695             $msg = ApiMain::makeHelpMsgHeader( $module, $group );
00696             $msg2 = $module->makeHelpMsg();
00697             if ( $msg2 !== false ) {
00698                 $msg .= $msg2;
00699             }
00700             if ( $module instanceof ApiQueryGeneratorBase ) {
00701                 $msg .= "Generator:\n  This module may be used as a generator\n";
00702             }
00703             $moduleDescriptions[] = $msg;
00704         }
00705 
00706         return implode( "\n", $moduleDescriptions );
00707     }
00708 
00709     public function shouldCheckMaxlag() {
00710         return true;
00711     }
00712 
00713     public function getParamDescription() {
00714         return $this->getPageSet()->getFinalParamDescription() + array(
00715             'prop' => 'Which properties to get for the titles/revisions/pageids. ' .
00716                 'Module help is available below',
00717             'list' => 'Which lists to get. Module help is available below',
00718             'meta' => 'Which metadata to get about the site. Module help is available below',
00719             'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
00720             'export' => 'Export the current revisions of all given or generated pages',
00721             'exportnowrap' => 'Return the export XML without wrapping it in an ' .
00722                 'XML result (same format as Special:Export). Can only be used with export',
00723             'iwurl' => 'Whether to get the full URL if the title is an interwiki link',
00724             'continue' => array(
00725                 'When present, formats query-continue as key-value pairs that ' .
00726                     'should simply be merged into the original request.',
00727                 'This parameter must be set to an empty string in the initial query.',
00728                 'This parameter is recommended for all new development, and ' .
00729                     'will be made default in the next API version.'
00730             ),
00731         );
00732     }
00733 
00734     public function getDescription() {
00735         return array(
00736             'Query API module allows applications to get needed pieces of data ' .
00737                 'from the MediaWiki databases,',
00738             'and is loosely based on the old query.php interface.',
00739             'All data modifications will first have to use query to acquire a ' .
00740                 'token to prevent abuse from malicious sites.'
00741         );
00742     }
00743 
00744     public function getPossibleErrors() {
00745         return array_merge(
00746             parent::getPossibleErrors(),
00747             $this->getPageSet()->getFinalPossibleErrors()
00748         );
00749     }
00750 
00751     public function getExamples() {
00752         return array(
00753             'api.php?action=query&prop=revisions&meta=siteinfo&' .
00754                 'titles=Main%20Page&rvprop=user|comment&continue=',
00755             'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions&continue=',
00756         );
00757     }
00758 
00759     public function getHelpUrls() {
00760         return array(
00761             'https://www.mediawiki.org/wiki/API:Query',
00762             'https://www.mediawiki.org/wiki/API:Meta',
00763             'https://www.mediawiki.org/wiki/API:Properties',
00764             'https://www.mediawiki.org/wiki/API:Lists',
00765         );
00766     }
00767 }