<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 7, 2006
*
- * Copyright © 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
+use Wikimedia\Rdbms\IDatabase;
/**
* This is the main query class. It behaves similar to ApiMain: based on the
*/
class ApiQuery extends ApiBase {
- private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
- private $mPageSet;
- private $params, $redirects, $convertTitles;
-
- private $mQueryPropModules = array(
+ /**
+ * List of Api Query prop modules
+ * @var array
+ */
+ private static $QueryPropModules = [
+ 'categories' => 'ApiQueryCategories',
+ 'categoryinfo' => 'ApiQueryCategoryInfo',
+ 'contributors' => 'ApiQueryContributors',
+ 'deletedrevisions' => 'ApiQueryDeletedRevisions',
+ 'duplicatefiles' => 'ApiQueryDuplicateFiles',
+ 'extlinks' => 'ApiQueryExternalLinks',
+ 'fileusage' => 'ApiQueryBacklinksprop',
+ 'images' => 'ApiQueryImages',
+ 'imageinfo' => 'ApiQueryImageInfo',
'info' => 'ApiQueryInfo',
- 'revisions' => 'ApiQueryRevisions',
'links' => 'ApiQueryLinks',
+ 'linkshere' => 'ApiQueryBacklinksprop',
'iwlinks' => 'ApiQueryIWLinks',
'langlinks' => 'ApiQueryLangLinks',
- 'images' => 'ApiQueryImages',
- 'imageinfo' => 'ApiQueryImageInfo',
+ 'pageprops' => 'ApiQueryPageProps',
+ 'redirects' => 'ApiQueryBacklinksprop',
+ 'revisions' => 'ApiQueryRevisions',
'stashimageinfo' => 'ApiQueryStashImageInfo',
'templates' => 'ApiQueryLinks',
- 'categories' => 'ApiQueryCategories',
- 'extlinks' => 'ApiQueryExternalLinks',
- 'categoryinfo' => 'ApiQueryCategoryInfo',
- 'duplicatefiles' => 'ApiQueryDuplicateFiles',
- 'pageprops' => 'ApiQueryPageProps',
- );
+ 'transcludedin' => 'ApiQueryBacklinksprop',
+ ];
- private $mQueryListModules = array(
- 'allimages' => 'ApiQueryAllimages',
- 'allpages' => 'ApiQueryAllpages',
- 'alllinks' => 'ApiQueryAllLinks',
+ /**
+ * List of Api Query list modules
+ * @var array
+ */
+ private static $QueryListModules = [
'allcategories' => 'ApiQueryAllCategories',
+ 'alldeletedrevisions' => 'ApiQueryAllDeletedRevisions',
+ 'allfileusages' => 'ApiQueryAllLinks',
+ 'allimages' => 'ApiQueryAllImages',
+ 'alllinks' => 'ApiQueryAllLinks',
+ 'allpages' => 'ApiQueryAllPages',
+ 'allredirects' => 'ApiQueryAllLinks',
+ 'allrevisions' => 'ApiQueryAllRevisions',
+ 'mystashedfiles' => 'ApiQueryMyStashedFiles',
+ 'alltransclusions' => 'ApiQueryAllLinks',
'allusers' => 'ApiQueryAllUsers',
'backlinks' => 'ApiQueryBacklinks',
'blocks' => 'ApiQueryBlocks',
'categorymembers' => 'ApiQueryCategoryMembers',
'deletedrevs' => 'ApiQueryDeletedrevs',
'embeddedin' => 'ApiQueryBacklinks',
+ 'exturlusage' => 'ApiQueryExtLinksUsage',
'filearchive' => 'ApiQueryFilearchive',
'imageusage' => 'ApiQueryBacklinks',
'iwbacklinks' => 'ApiQueryIWBacklinks',
+ 'langbacklinks' => 'ApiQueryLangBacklinks',
'logevents' => 'ApiQueryLogEvents',
+ 'pageswithprop' => 'ApiQueryPagesWithProp',
+ 'pagepropnames' => 'ApiQueryPagePropNames',
+ 'prefixsearch' => 'ApiQueryPrefixSearch',
+ 'protectedtitles' => 'ApiQueryProtectedTitles',
+ 'querypage' => 'ApiQueryQueryPage',
+ 'random' => 'ApiQueryRandom',
'recentchanges' => 'ApiQueryRecentChanges',
'search' => 'ApiQuerySearch',
'tags' => 'ApiQueryTags',
'usercontribs' => 'ApiQueryContributions',
+ 'users' => 'ApiQueryUsers',
'watchlist' => 'ApiQueryWatchlist',
'watchlistraw' => 'ApiQueryWatchlistRaw',
- 'exturlusage' => 'ApiQueryExtLinksUsage',
- 'users' => 'ApiQueryUsers',
- 'random' => 'ApiQueryRandom',
- 'protectedtitles' => 'ApiQueryProtectedTitles',
- );
+ ];
- private $mQueryMetaModules = array(
+ /**
+ * List of Api Query meta modules
+ * @var array
+ */
+ private static $QueryMetaModules = [
+ 'allmessages' => 'ApiQueryAllMessages',
+ 'authmanagerinfo' => 'ApiQueryAuthManagerInfo',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
- 'allmessages' => 'ApiQueryAllmessages',
- );
+ 'filerepoinfo' => 'ApiQueryFileRepoInfo',
+ 'tokens' => 'ApiQueryTokens',
+ ];
- private $mSlaveDB = null;
- private $mNamedDB = array();
+ /**
+ * @var ApiPageSet
+ */
+ private $mPageSet;
- public function __construct( $main, $action ) {
- parent::__construct( $main, $action );
-
- // Allow custom modules to be added in LocalSettings.php
- global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules;
- self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules );
- self::appendUserModules( $this->mQueryListModules, $wgAPIListModules );
- self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules );
-
- $this->mPropModuleNames = array_keys( $this->mQueryPropModules );
- $this->mListModuleNames = array_keys( $this->mQueryListModules );
- $this->mMetaModuleNames = array_keys( $this->mQueryMetaModules );
-
- // Allow the entire list of modules at first,
- // but during module instantiation check if it can be used as a generator.
- $this->mAllowedGenerators = array_merge( $this->mListModuleNames, $this->mPropModuleNames );
- }
+ private $mParams;
+ private $mNamedDB = [];
+ private $mModuleMgr;
/**
- * Helper function to append any add-in modules to the list
- * @param $modules array Module array
- * @param $newModules array Module array to add to $modules
+ * @param ApiMain $main
+ * @param string $action
*/
- private static function appendUserModules( &$modules, $newModules ) {
- if ( is_array( $newModules ) ) {
- foreach ( $newModules as $moduleName => $moduleClass ) {
- $modules[$moduleName] = $moduleClass;
- }
- }
+ public function __construct( ApiMain $main, $action ) {
+ parent::__construct( $main, $action );
+
+ $this->mModuleMgr = new ApiModuleManager( $this );
+
+ // Allow custom modules to be added in LocalSettings.php
+ $config = $this->getConfig();
+ $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' );
+ $this->mModuleMgr->addModules( $config->get( 'APIPropModules' ), 'prop' );
+ $this->mModuleMgr->addModules( self::$QueryListModules, 'list' );
+ $this->mModuleMgr->addModules( $config->get( 'APIListModules' ), 'list' );
+ $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' );
+ $this->mModuleMgr->addModules( $config->get( 'APIMetaModules' ), 'meta' );
+
+ Hooks::run( 'ApiQuery::moduleManager', [ $this->mModuleMgr ] );
+
+ // Create PageSet that will process titles/pageids/revids/generator
+ $this->mPageSet = new ApiPageSet( $this );
}
/**
- * Gets a default slave database connection object
- * @return Database
+ * Overrides to return this instance's module manager.
+ * @return ApiModuleManager
*/
- public function getDB() {
- if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
- $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
- }
- return $this->mSlaveDB;
+ public function getModuleManager() {
+ return $this->mModuleMgr;
}
/**
* If no such connection has been requested before, it will be created.
* Subsequent calls with the same $name will return the same connection
* as the first, regardless of the values of $db and $groups
- * @param $name string Name to assign to the database connection
- * @param $db int One of the DB_* constants
- * @param $groups array Query groups
- * @return Database
+ * @param string $name Name to assign to the database connection
+ * @param int $db One of the DB_* constants
+ * @param string|string[] $groups Query groups
+ * @return IDatabase
*/
public function getNamedDB( $name, $db, $groups ) {
if ( !array_key_exists( $name, $this->mNamedDB ) ) {
- $this->profileDBIn();
$this->mNamedDB[$name] = wfGetDB( $db, $groups );
- $this->profileDBOut();
}
+
return $this->mNamedDB[$name];
}
}
/**
- * Get the array mapping module names to class names
- * @return array(modulename => classname)
+ * @return ApiFormatRaw|null
*/
- function getModules() {
- return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
- }
-
- /**
- * Get whether the specified module is a prop, list or a meta query module
- * @param $moduleName string Name of the module to find type for
- * @return mixed string or null
- */
- function getModuleType( $moduleName ) {
- if ( array_key_exists ( $moduleName, $this->mQueryPropModules ) ) {
- return 'prop';
- }
-
- if ( array_key_exists ( $moduleName, $this->mQueryListModules ) ) {
- return 'list';
- }
-
- if ( array_key_exists ( $moduleName, $this->mQueryMetaModules ) ) {
- return 'meta';
- }
-
- return null;
- }
-
public function getCustomPrinter() {
// If &exportnowrap is set, use the raw formatter
if ( $this->getParameter( 'export' ) &&
- $this->getParameter( 'exportnowrap' ) )
- {
+ $this->getParameter( 'exportnowrap' )
+ ) {
return new ApiFormatRaw( $this->getMain(),
$this->getMain()->createPrinterByName( 'xml' ) );
} else {
* #5 Execute all requested modules
*/
public function execute() {
- $this->params = $this->extractRequestParams();
- $this->redirects = $this->params['redirects'];
- $this->convertTitles = $this->params['converttitles'];
-
- // Create PageSet
- $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
+ $this->mParams = $this->extractRequestParams();
// Instantiate requested modules
- $modules = array();
- $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules );
- $this->instantiateModules( $modules, 'list', $this->mQueryListModules );
- $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules );
-
- $cacheMode = 'public';
-
- // If given, execute generator to substitute user supplied data with generated data.
- if ( isset( $this->params['generator'] ) ) {
- $generator = $this->newGenerator( $this->params['generator'] );
- $params = $generator->extractRequestParams();
- $cacheMode = $this->mergeCacheMode( $cacheMode,
- $generator->getCacheMode( $params ) );
- $this->executeGeneratorModule( $generator, $modules );
- } else {
- // Append custom fields and populate page/revision information
- $this->addCustomFldsToPageSet( $modules, $this->mPageSet );
+ $allModules = [];
+ $this->instantiateModules( $allModules, 'prop' );
+ $propModules = array_keys( $allModules );
+ $this->instantiateModules( $allModules, 'list' );
+ $this->instantiateModules( $allModules, 'meta' );
+
+ // Filter modules based on continue parameter
+ $continuationManager = new ApiContinuationManager( $this, $allModules, $propModules );
+ $this->setContinuationManager( $continuationManager );
+ $modules = $continuationManager->getRunModules();
+
+ if ( !$continuationManager->isGeneratorDone() ) {
+ // Query modules may optimize data requests through the $this->getPageSet()
+ // object by adding extra fields from the page table.
+ foreach ( $modules as $module ) {
+ $module->requestExtraData( $this->mPageSet );
+ }
+ // Populate page/revision information
$this->mPageSet->execute();
+ // Record page information (title, namespace, if exists, etc)
+ $this->outputGeneralPageInfo();
+ } else {
+ $this->mPageSet->executeDryRun();
}
- // Record page information (title, namespace, if exists, etc)
- $this->outputGeneralPageInfo();
+ $cacheMode = $this->mPageSet->getCacheMode();
- // Execute all requested modules.
+ // Execute all unfinished modules
+ /** @var ApiQueryBase $module */
foreach ( $modules as $module ) {
$params = $module->extractRequestParams();
$cacheMode = $this->mergeCacheMode(
$cacheMode, $module->getCacheMode( $params ) );
- $module->profileIn();
$module->execute();
- wfRunHooks( 'APIQueryAfterExecute', array( &$module ) );
- $module->profileOut();
+ Hooks::run( 'APIQueryAfterExecute', [ &$module ] );
}
// Set the cache mode
$this->getMain()->setCacheMode( $cacheMode );
+
+ // Write the continuation data into the result
+ $this->setContinuationManager( null );
+ if ( $this->mParams['rawcontinue'] ) {
+ $data = $continuationManager->getRawNonContinuation();
+ if ( $data ) {
+ $this->getResult()->addValue( null, 'query-noncontinue', $data,
+ ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ $data = $continuationManager->getRawContinuation();
+ if ( $data ) {
+ $this->getResult()->addValue( null, 'query-continue', $data,
+ ApiResult::ADD_ON_TOP | ApiResult::NO_SIZE_CHECK );
+ }
+ } else {
+ $continuationManager->setContinuationIntoResult( $this->getResult() );
+ }
}
/**
* Update a cache mode string, applying the cache mode of a new module to it.
* The cache mode may increase in the level of privacy, but public modules
* added to private data do not decrease the level of privacy.
+ *
+ * @param string $cacheMode
+ * @param string $modCacheMode
+ * @return string
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
if ( $modCacheMode === 'anon-public-user-private' ) {
} else { // private
$cacheMode = 'private';
}
- return $cacheMode;
- }
- /**
- * Query modules may optimize data requests through the $this->getPageSet() object
- * by adding extra fields from the page table.
- * This function will gather all the extra request fields from the modules.
- * @param $modules array of module objects
- * @param $pageSet ApiPageSet
- */
- private function addCustomFldsToPageSet( $modules, $pageSet ) {
- // Query all requested modules.
- foreach ( $modules as $module ) {
- $module->requestExtraData( $pageSet );
- }
+ return $cacheMode;
}
/**
* Create instances of all modules requested by the client
- * @param $modules Array to append instantiated modules to
- * @param $param string Parameter name to read modules from
- * @param $moduleList Array array(modulename => classname)
+ * @param array $modules To append instantiated modules to
+ * @param string $param Parameter name to read modules from
*/
- private function instantiateModules( &$modules, $param, $moduleList ) {
- $list = @$this->params[$param];
- if ( !is_null ( $list ) ) {
- foreach ( $list as $moduleName ) {
- $modules[] = new $moduleList[$moduleName] ( $this, $moduleName );
+ private function instantiateModules( &$modules, $param ) {
+ $wasPosted = $this->getRequest()->wasPosted();
+ if ( isset( $this->mParams[$param] ) ) {
+ foreach ( $this->mParams[$param] as $moduleName ) {
+ $instance = $this->mModuleMgr->getModule( $moduleName, $param );
+ if ( $instance === null ) {
+ ApiBase::dieDebug( __METHOD__, 'Error instantiating module' );
+ }
+ if ( !$wasPosted && $instance->mustBePosted() ) {
+ $this->dieWithErrorOrDebug( [ 'apierror-mustbeposted', $moduleName ] );
+ }
+ // Ignore duplicates. TODO 2.0: die()?
+ if ( !array_key_exists( $moduleName, $modules ) ) {
+ $modules[$moduleName] = $instance;
+ }
}
}
}
$pageSet = $this->getPageSet();
$result = $this->getResult();
- // We don't check for a full result set here because we can't be adding
- // more than 380K. The maximum revision size is in the megabyte range,
- // and the maximum result size must be even higher than that.
-
- // Title normalizations
- $normValues = array();
- foreach ( $pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr ) {
- $normValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
- }
-
- if ( count( $normValues ) ) {
- $result->setIndexedTagName( $normValues, 'n' );
- $result->addValue( 'query', 'normalized', $normValues );
- }
-
- // Title conversions
- $convValues = array();
- foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) {
- $convValues[] = array(
- 'from' => $rawTitleStr,
- 'to' => $titleStr
- );
- }
-
- if ( count( $convValues ) ) {
- $result->setIndexedTagName( $convValues, 'c' );
- $result->addValue( 'query', 'converted', $convValues );
- }
+ // We can't really handle max-result-size failure here, but we need to
+ // check anyway in case someone set the limit stupidly low.
+ $fit = true;
- // Interwiki titles
- $intrwValues = array();
- foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
- $intrwValues[] = array(
- 'title' => $rawTitleStr,
- 'iw' => $interwikiStr
- );
+ $values = $pageSet->getNormalizedTitlesAsResult( $result );
+ if ( $values ) {
+ $fit = $fit && $result->addValue( 'query', 'normalized', $values );
}
-
- if ( count( $intrwValues ) ) {
- $result->setIndexedTagName( $intrwValues, 'i' );
- $result->addValue( 'query', 'interwiki', $intrwValues );
+ $values = $pageSet->getConvertedTitlesAsResult( $result );
+ if ( $values ) {
+ $fit = $fit && $result->addValue( 'query', 'converted', $values );
}
-
- // Show redirect information
- $redirValues = array();
- foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo ) {
- $redirValues[] = array(
- 'from' => strval( $titleStrFrom ),
- 'to' => $titleStrTo
- );
+ $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->mParams['iwurl'] );
+ if ( $values ) {
+ $fit = $fit && $result->addValue( 'query', 'interwiki', $values );
}
-
- if ( count( $redirValues ) ) {
- $result->setIndexedTagName( $redirValues, 'r' );
- $result->addValue( 'query', 'redirects', $redirValues );
+ $values = $pageSet->getRedirectTitlesAsResult( $result );
+ if ( $values ) {
+ $fit = $fit && $result->addValue( 'query', 'redirects', $values );
}
-
- // Missing revision elements
- $missingRevIDs = $pageSet->getMissingRevisionIDs();
- if ( count( $missingRevIDs ) ) {
- $revids = array();
- foreach ( $missingRevIDs as $revid ) {
- $revids[$revid] = array(
- 'revid' => $revid
- );
- }
- $result->setIndexedTagName( $revids, 'rev' );
- $result->addValue( 'query', 'badrevids', $revids );
+ $values = $pageSet->getMissingRevisionIDsAsResult( $result );
+ if ( $values ) {
+ $fit = $fit && $result->addValue( 'query', 'badrevids', $values );
}
// Page elements
- $pages = array();
+ $pages = [];
// Report any missing titles
foreach ( $pageSet->getMissingTitles() as $fakeId => $title ) {
- $vals = array();
+ $vals = [];
ApiQueryBase::addTitleInfo( $vals, $title );
- $vals['missing'] = '';
+ $vals['missing'] = true;
+ if ( $title->isKnown() ) {
+ $vals['known'] = true;
+ }
$pages[$fakeId] = $vals;
}
// Report any invalid titles
- foreach ( $pageSet->getInvalidTitles() as $fakeId => $title ) {
- $pages[$fakeId] = array( 'title' => $title, 'invalid' => '' );
+ foreach ( $pageSet->getInvalidTitlesAndReasons() as $fakeId => $data ) {
+ $pages[$fakeId] = $data + [ 'invalid' => true ];
}
// Report any missing page ids
foreach ( $pageSet->getMissingPageIDs() as $pageid ) {
- $pages[$pageid] = array(
+ $pages[$pageid] = [
'pageid' => $pageid,
- 'missing' => ''
- );
+ 'missing' => true,
+ ];
}
// Report special pages
+ /** @var Title $title */
foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
- $vals = array();
+ $vals = [];
ApiQueryBase::addTitleInfo( $vals, $title );
- $vals['special'] = '';
- if ( $title->getNamespace() == NS_SPECIAL &&
- !SpecialPage::exists( $title->getDbKey() ) ) {
- $vals['missing'] = '';
- } elseif ( $title->getNamespace() == NS_MEDIA &&
- !wfFindFile( $title ) ) {
- $vals['missing'] = '';
+ $vals['special'] = true;
+ if ( !$title->isKnown() ) {
+ $vals['missing'] = true;
}
$pages[$fakeId] = $vals;
}
// Output general page information for found titles
foreach ( $pageSet->getGoodTitles() as $pageid => $title ) {
- $vals = array();
+ $vals = [];
$vals['pageid'] = $pageid;
ApiQueryBase::addTitleInfo( $vals, $title );
$pages[$pageid] = $vals;
}
if ( count( $pages ) ) {
- if ( $this->params['indexpageids'] ) {
- $pageIDs = array_keys( $pages );
+ $pageSet->populateGeneratorData( $pages );
+ ApiResult::setArrayType( $pages, 'BCarray' );
+
+ if ( $this->mParams['indexpageids'] ) {
+ $pageIDs = array_keys( ApiResult::stripMetadataNonRecursive( $pages ) );
// json treats all map keys as strings - converting to match
$pageIDs = array_map( 'strval', $pageIDs );
- $result->setIndexedTagName( $pageIDs, 'id' );
- $result->addValue( 'query', 'pageids', $pageIDs );
+ ApiResult::setIndexedTagName( $pageIDs, 'id' );
+ $fit = $fit && $result->addValue( 'query', 'pageids', $pageIDs );
}
- $result->setIndexedTagName( $pages, 'page' );
- $result->addValue( 'query', 'pages', $pages );
+ ApiResult::setIndexedTagName( $pages, 'page' );
+ $fit = $fit && $result->addValue( 'query', 'pages', $pages );
+ }
+
+ if ( !$fit ) {
+ $this->dieWithError( 'apierror-badconfig-resulttoosmall', 'badconfig' );
}
- if ( $this->params['export'] ) {
+
+ if ( $this->mParams['export'] ) {
$this->doExport( $pageSet, $result );
}
}
/**
- * @param $pageSet ApiPageSet Pages to be exported
- * @param $result ApiResult Result to output to
+ * @param ApiPageSet $pageSet Pages to be exported
+ * @param ApiResult $result Result to output to
*/
- private function doExport( $pageSet, $result ) {
- $exportTitles = array();
+ private function doExport( $pageSet, $result ) {
+ $exportTitles = [];
$titles = $pageSet->getGoodTitles();
- if( count( $titles ) ) {
+ if ( count( $titles ) ) {
+ $user = $this->getUser();
+ /** @var Title $title */
foreach ( $titles as $title ) {
- if ( $title->userCanRead() ) {
+ if ( $title->userCan( 'read', $user ) ) {
$exportTitles[] = $title;
}
}
}
- // only export when there are titles
- if ( !count( $exportTitles ) ) {
- return;
- }
$exporter = new WikiExporter( $this->getDB() );
- // WikiExporter writes to stdout, so catch its
- // output with an ob
- ob_start();
+ $sink = new DumpStringOutput;
+ $exporter->setOutputSink( $sink );
$exporter->openStream();
foreach ( $exportTitles as $title ) {
$exporter->pageByTitle( $title );
}
$exporter->closeStream();
- $exportxml = ob_get_contents();
- ob_end_clean();
// Don't check the size of exported stuff
// It's not continuable, so it would cause more
// problems than it'd solve
- $result->disableSizeCheck();
- if ( $this->params['exportnowrap'] ) {
+ if ( $this->mParams['exportnowrap'] ) {
$result->reset();
// Raw formatter will handle this
- $result->addValue( null, 'text', $exportxml );
- $result->addValue( null, 'mime', 'text/xml' );
+ $result->addValue( null, 'text', $sink, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'mime', 'text/xml', ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'filename', 'export.xml', ApiResult::NO_SIZE_CHECK );
} else {
- $r = array();
- ApiResult::setContent( $r, $exportxml );
- $result->addValue( 'query', 'export', $r );
+ $result->addValue( 'query', 'export', $sink, ApiResult::NO_SIZE_CHECK );
+ $result->addValue( 'query', ApiResult::META_BC_SUBELEMENTS, [ 'export' ] );
}
- $result->enableSizeCheck();
}
- /**
- * Create a generator object of the given type and return it
- * @param $generatorName string Module name
- * @return ApiQueryGeneratorBase
- */
- public function newGenerator( $generatorName ) {
- // Find class that implements requested generator
- if ( isset( $this->mQueryListModules[$generatorName] ) ) {
- $className = $this->mQueryListModules[$generatorName];
- } elseif ( isset( $this->mQueryPropModules[$generatorName] ) ) {
- $className = $this->mQueryPropModules[$generatorName];
- } else {
- ApiBase::dieDebug( __METHOD__, "Unknown generator=$generatorName" );
- }
- $generator = new $className ( $this, $generatorName );
- if ( !$generator instanceof ApiQueryGeneratorBase ) {
- $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' );
- }
- $generator->setGeneratorMode();
- return $generator;
- }
-
- /**
- * For generator mode, execute generator, and use its output as new
- * ApiPageSet
- * @param $generator ApiQueryGeneratorBase Generator Module
- * @param $modules array of module objects
- */
- protected function executeGeneratorModule( $generator, $modules ) {
- // Generator results
- $resultPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
-
- // Add any additional fields modules may need
- $generator->requestExtraData( $this->mPageSet );
- $this->addCustomFldsToPageSet( $modules, $resultPageSet );
-
- // Populate page information with the original user input
- $this->mPageSet->execute();
-
- // populate resultPageSet with the generator output
- $generator->profileIn();
- $generator->executeGenerator( $resultPageSet );
- wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$resultPageSet ) );
- $resultPageSet->finishPageSetGeneration();
- $generator->profileOut();
-
- // Swap the resulting pageset back in
- $this->mPageSet = $resultPageSet;
- }
-
- public function getAllowedParams() {
- return array(
- 'prop' => array(
+ public function getAllowedParams( $flags = 0 ) {
+ $result = [
+ 'prop' => [
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mPropModuleNames
- ),
- 'list' => array(
+ ApiBase::PARAM_TYPE => 'submodule',
+ ],
+ 'list' => [
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mListModuleNames
- ),
- 'meta' => array(
+ ApiBase::PARAM_TYPE => 'submodule',
+ ],
+ 'meta' => [
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_TYPE => $this->mMetaModuleNames
- ),
- 'generator' => array(
- ApiBase::PARAM_TYPE => $this->mAllowedGenerators
- ),
- 'redirects' => false,
- 'converttitles' => false,
+ ApiBase::PARAM_TYPE => 'submodule',
+ ],
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
- );
- }
-
- /**
- * Override the parent to generate help messages for all available query modules.
- * @return string
- */
- public function makeHelpMsg() {
- $msg = '';
-
- // Make sure the internal object is empty
- // (just in case a sub-module decides to optimize during instantiation)
- $this->mPageSet = null;
- $this->mAllowedGenerators = array(); // Will be repopulated
-
- $querySeparator = str_repeat( '--- ', 8 );
- $moduleSeparator = str_repeat( '*** ', 10 );
- $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
- $msg .= "\n$querySeparator Query: List $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
- $msg .= "\n$querySeparator Query: Meta $querySeparator\n\n";
- $msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' );
- $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n";
-
- // Perform the base call last because the $this->mAllowedGenerators
- // will be updated inside makeHelpMsgHelper()
- // Use parent to make default message for the query module
- $msg = parent::makeHelpMsg() . $msg;
-
- return $msg;
- }
-
- /**
- * For all modules in $moduleList, generate help messages and join them together
- * @param $moduleList Array array(modulename => classname)
- * @param $paramName string Parameter name
- * @return string
- */
- private function makeHelpMsgHelper( $moduleList, $paramName ) {
- $moduleDescriptions = array();
-
- foreach ( $moduleList as $moduleName => $moduleClass ) {
- $module = new $moduleClass ( $this, $moduleName, null );
-
- $msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
- $msg2 = $module->makeHelpMsg();
- if ( $msg2 !== false ) {
- $msg .= $msg2;
- }
- if ( $module instanceof ApiQueryGeneratorBase ) {
- $this->mAllowedGenerators[] = $moduleName;
- $msg .= "Generator:\n This module may be used as a generator\n";
- }
- $moduleDescriptions[] = $msg;
+ 'iwurl' => false,
+ 'continue' => [
+ ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
+ ],
+ 'rawcontinue' => false,
+ ];
+ if ( $flags ) {
+ $result += $this->getPageSet()->getFinalParams( $flags );
}
- return implode( "\n", $moduleDescriptions );
- }
-
- /**
- * Override to add extra parameters from PageSet
- * @return string
- */
- public function makeHelpMsgParameters() {
- $psModule = new ApiPageSet( $this );
- return $psModule->makeHelpMsgParameters() . parent::makeHelpMsgParameters();
- }
-
- public function shouldCheckMaxlag() {
- return true;
+ return $result;
}
- public function getParamDescription() {
- return array(
- 'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below',
- 'list' => 'Which lists to get. Module help is available below',
- 'meta' => 'Which metadata to get about the site. Module help is available below',
- 'generator' => array( 'Use the output of a list as the input for other prop/list/meta items',
- 'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
- 'redirects' => 'Automatically resolve redirects',
- 'converttitles' => array( "Convert titles to other variants if necessary. Only works if the wiki's content language supports variant conversion.",
- 'Languages that support variant conversion include kk, ku, gan, tg, sr, zh' ),
- 'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
- 'export' => 'Export the current revisions of all given or generated pages',
- 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
+ public function isReadMode() {
+ // We need to make an exception for certain meta modules that should be
+ // accessible even without the 'read' right. Restrict the exception as
+ // much as possible: no other modules allowed, and no pageset
+ // parameters either. We do allow the 'rawcontinue' and 'indexpageids'
+ // parameters since frameworks might add these unconditionally and they
+ // can't expose anything here.
+ $this->mParams = $this->extractRequestParams();
+ $params = array_filter(
+ array_diff_key(
+ $this->mParams + $this->getPageSet()->extractRequestParams(),
+ [ 'rawcontinue' => 1, 'indexpageids' => 1 ]
+ )
);
- }
+ if ( array_keys( $params ) !== [ 'meta' ] ) {
+ return true;
+ }
- public function getDescription() {
- return array(
- 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',
- 'and is loosely based on the old query.php interface.',
- 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites'
- );
- }
+ // Ask each module if it requires read mode. Any true => this returns
+ // true.
+ $modules = [];
+ $this->instantiateModules( $modules, 'meta' );
+ foreach ( $modules as $module ) {
+ if ( $module->isReadMode() ) {
+ return true;
+ }
+ }
- public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ),
- ) );
+ return false;
}
- protected function getExamples() {
- return array(
- 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
- 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
- );
+ protected function getExamplesMessages() {
+ return [
+ 'action=query&prop=revisions&meta=siteinfo&' .
+ 'titles=Main%20Page&rvprop=user|comment&continue='
+ => 'apihelp-query-example-revisions',
+ 'action=query&generator=allpages&gapprefix=API/&prop=revisions&continue='
+ => 'apihelp-query-example-allpages',
+ ];
}
- public function getVersion() {
- $psModule = new ApiPageSet( $this );
- $vers = array();
- $vers[] = __CLASS__ . ': $Id: ApiQuery.php 80897 2011-01-24 18:57:42Z catrope $';
- $vers[] = $psModule->getVersion();
- return $vers;
+ public function getHelpUrls() {
+ return [
+ 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Query',
+ 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Meta',
+ 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Properties',
+ 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Lists',
+ ];
}
}