From 62216932c197f1c248ca2d95bc230f87a79ccd71 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 8 Feb 2013 15:39:40 -0500 Subject: [PATCH] API PageSet allows generator for non-query modules * PageSet can now be used in any action to process titles/pageids/revids or any generator, redirects resolution, and converttitle functionality. * action=purge proper usage of MustBePosted() * Add supports for all pageset capabilities - generators, redirects, converttitles to action=purge and action=setnotificationtimestamp * BREAKING CHANGE: ApiPageSet constructor now has two params instead of three, with only the first one keeping its meaning. ApiPageSet is now derived from ApiBase. * BREAKING CHANGE: ApiQuery::newGenerator() and executeGeneratorModule() were deleted. Change-Id: I7a3d7b6eb015d21ec1a9b9d9c6af9d97663f3f9a --- RELEASE-NOTES-1.21 | 7 + docs/hooks.txt | 1 + includes/api/ApiBase.php | 37 +- includes/api/ApiMain.php | 2 +- includes/api/ApiPageSet.php | 422 +++++++++++++----- includes/api/ApiParamInfo.php | 2 +- includes/api/ApiPurge.php | 128 ++++-- includes/api/ApiQuery.php | 254 ++--------- includes/api/ApiQueryAllImages.php | 2 +- includes/api/ApiQueryBase.php | 35 +- includes/api/ApiSetNotificationTimestamp.php | 44 +- .../phpunit/includes/api/ApiGeneratorTest.php | 4 +- .../includes/api/PrefixUniquenessTest.php | 2 +- 13 files changed, 541 insertions(+), 399 deletions(-) diff --git a/RELEASE-NOTES-1.21 b/RELEASE-NOTES-1.21 index 6ffe112e7c..d1976ddb5d 100644 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@ -206,6 +206,8 @@ production. that the imagerepository property will no longer be set on page objects not processed in the current query (i.e. non-images or those skipped due to iicontinue). +* Add supports for all pageset capabilities - generators, redirects, converttitles to + action=purge and action=setnotificationtimestamp. === API internal changes in 1.21 === * For debugging only, a new global $wgDebugAPI removes many API restrictions when true. @@ -213,6 +215,11 @@ production. Whenever enabled, a warning will also be added to all output. * ApiModuleManager now handles all submodules (actions,props,lists) and instantiation * Query stores prop/list/meta as submodules +* ApiPageSet can now be used in any action to process titles/pageids/revids or any generator. +* BREAKING CHANGE: ApiPageSet constructor now has two params instead of three, with only the + first one keeping its meaning. ApiPageSet is now derived from ApiBase. +* BREAKING CHANGE: ApiQuery::newGenerator() and executeGeneratorModule() were deleted. +* ApiQueryGeneratorBase::setGeneratorMode() now requires a pageset param. === Languages updated in 1.21 === diff --git a/docs/hooks.txt b/docs/hooks.txt index f9274a5ea1..28eedf4725 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -355,6 +355,7 @@ $text : the new text of the article (has yet to be saved) 'APIGetAllowedParams': Use this hook to modify a module's parameters. &$module: ApiBase Module object &$params: Array of parameters +$flags: int zero or OR-ed flags like ApiBase::GET_VALUES_FOR_HELP 'APIGetDescription': Use this hook to modify a module's description. &$module: ApiBase Module object diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 743fef0433..abb43e8d3e 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -66,7 +66,15 @@ abstract class ApiBase extends ContextSource { const LIMIT_SML1 = 50; // Slow query, std user limit const LIMIT_SML2 = 500; // Slow query, bot/sysop limit + /** + * getAllowedParams() flag: When set, the result could take longer to generate, + * but should be more thorough. E.g. get the list of generators for ApiSandBox extension + * @since 1.21 + */ + const GET_VALUES_FOR_HELP = 1; + private $mMainModule, $mModuleName, $mModulePrefix; + private $mSlaveDB = null; private $mParamCache = array(); /** @@ -358,7 +366,7 @@ abstract class ApiBase extends ContextSource { * @return string or false */ public function makeHelpMsgParameters() { - $params = $this->getFinalParams(); + $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); if ( $params ) { $paramsDescription = $this->getFinalParamDescription(); @@ -506,15 +514,22 @@ abstract class ApiBase extends ContextSource { * value) or (parameter name) => (array with PARAM_* constants as keys) * Don't call this function directly: use getFinalParams() to allow * hooks to modify parameters as needed. + * + * Some derived classes may choose to handle an integer $flags parameter + * in the overriding methods. Callers of this method can pass zero or + * more OR-ed flags like GET_VALUES_FOR_HELP. + * * @return array|bool */ - protected function getAllowedParams() { + protected function getAllowedParams( /* $flags = 0 */ ) { + // int $flags is not declared because it causes "Strict standards" + // warning. Most derived classes do not implement it. return false; } /** * Returns an array of parameter descriptions. - * Don't call this functon directly: use getFinalParamDescription() to + * Don't call this function directly: use getFinalParamDescription() to * allow hooks to modify descriptions as needed. * @return array|bool False on no parameter descriptions */ @@ -526,11 +541,13 @@ abstract class ApiBase extends ContextSource { * Get final list of parameters, after hooks have had a chance to * tweak it as needed. * + * @param $flags int Zero or more flags like GET_VALUES_FOR_HELP * @return array|Bool False on no parameters + * @since 1.21 $flags param added */ - public function getFinalParams() { - $params = $this->getAllowedParams(); - wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params ) ); + public function getFinalParams( $flags = 0 ) { + $params = $this->getAllowedParams( $flags ); + wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) ); return $params; } @@ -1650,10 +1667,16 @@ abstract class ApiBase extends ContextSource { } /** + * Gets a default slave database connection object * @return DatabaseBase */ protected function getDB() { - return wfGetDB( DB_SLAVE, 'api' ); + if ( !isset( $this->mSlaveDB ) ) { + $this->profileDBIn(); + $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' ); + $this->profileDBOut(); + } + return $this->mSlaveDB; } /** diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 3535cd04e0..953cec81f4 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -936,7 +936,7 @@ class ApiMain extends ApiBase { protected function printResult( $isError ) { global $wgDebugAPI; if( $wgDebugAPI !== false ) { - $this->getResult()->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); + $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); } $this->getResult()->cleanUpUTF8(); diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 4dbccc6c9d..3945104123 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -4,7 +4,7 @@ * * Created on Sep 24, 2006 * - * Copyright © 2006 Yuri Astrakhan "@gmail.com" + * Copyright © 2006, 2013 Yuri Astrakhan "@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 @@ -36,52 +36,140 @@ * the second instance for all their work. * * @ingroup API + * @since 1.21 derives from ApiBase instead of ApiQueryBase */ -class ApiPageSet extends ApiQueryBase { +class ApiPageSet extends ApiBase { - private $mAllPages; // [ns][dbkey] => page_id or negative when missing - private $mTitles, $mGoodTitles, $mMissingTitles, $mInvalidTitles; - private $mMissingPageIDs, $mRedirectTitles, $mSpecialTitles; - private $mNormalizedTitles, $mInterwikiTitles; - private $mResolveRedirects, $mPendingRedirectIDs; - private $mConvertTitles, $mConvertedTitles; - private $mGoodRevIDs, $mMissingRevIDs; - private $mFakePageId; + /** + * Constructor flag: The new instance of ApiPageSet will ignore the 'generator=' parameter + * @since 1.21 + */ + const DISABLE_GENERATORS = 1; - private $mRequestedPageFields; + private $mDbSource, $mParams; + private $mResolveRedirects, $mConvertTitles, $mAllowGenerator; + + private $mAllPages = array(); // [ns][dbkey] => page_id or negative when missing + private $mTitles = array(); + private $mGoodTitles = array(); + private $mMissingTitles = array(); + private $mInvalidTitles = array(); + private $mMissingPageIDs = array(); + private $mRedirectTitles = array(); + private $mSpecialTitles = array(); + private $mNormalizedTitles = array(); + private $mInterwikiTitles = array(); + private $mPendingRedirectIDs = array(); + private $mConvertedTitles = array(); + private $mGoodRevIDs = array(); + private $mMissingRevIDs = array(); + private $mFakePageId = -1; + private $mCacheMode = 'public'; + private $mRequestedPageFields = array(); /** * Constructor - * @param $query ApiBase - * @param $resolveRedirects bool Whether redirects should be resolved - * @param $convertTitles bool - */ - public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) { - parent::__construct( $query, 'query' ); - - $this->mAllPages = array(); - $this->mTitles = array(); - $this->mGoodTitles = array(); - $this->mMissingTitles = array(); - $this->mInvalidTitles = array(); - $this->mMissingPageIDs = array(); - $this->mRedirectTitles = array(); - $this->mNormalizedTitles = array(); - $this->mInterwikiTitles = array(); - $this->mGoodRevIDs = array(); - $this->mMissingRevIDs = array(); - $this->mSpecialTitles = array(); - - $this->mRequestedPageFields = array(); - $this->mResolveRedirects = $resolveRedirects; - if ( $resolveRedirects ) { - $this->mPendingRedirectIDs = array(); - } + * @param $dbSource ApiBase Module implementing getDB(). + * Allows PageSet to reuse existing db connection from the shared state like ApiQuery. + * @param $flags int Zero or more flags like DISABLE_GENERATORS + * @since 1.21 accepts $flags instead of two boolean values + */ + public function __construct( ApiBase $dbSource, $flags = 0 ) { + parent::__construct( $dbSource->getMain(), $dbSource->getModuleName() ); + $this->mDbSource = $dbSource; + $this->mAllowGenerator = ( $flags & ApiPageSet::DISABLE_GENERATORS ) == 0; - $this->mConvertTitles = $convertTitles; - $this->mConvertedTitles = array(); + $this->profileIn(); + $this->mParams = $this->extractRequestParams(); + $this->mResolveRedirects = $this->mParams['redirects']; + $this->mConvertTitles = $this->mParams['converttitles']; + $this->profileOut(); + } - $this->mFakePageId = - 1; + /** + * Populate the PageSet from the request parameters. + */ + public function execute() { + $this->profileIn(); + + $generatorName = $this->mAllowGenerator ? $this->mParams['generator'] : null; + if ( isset( $generatorName ) ) { + $dbSource = $this->mDbSource; + $isQuery = $dbSource instanceof ApiQuery; + if ( !$isQuery ) { + // If the parent container of this pageset is not ApiQuery, we must create it to run generator + $dbSource = $this->getMain()->getModuleManager()->getModule( 'query' ); + // Enable profiling for query module because it will be used for db sql profiling + $dbSource->profileIn(); + } + $generator = $dbSource->getModuleManager()->getModule( $generatorName, null, true ); + if ( $generator === null ) { + $this->dieUsage( 'Unknown generator=' . $generatorName, 'badgenerator' ); + } + if ( !$generator instanceof ApiQueryGeneratorBase ) { + $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' ); + } + // Create a temporary pageset to store generator's output, + // add any additional fields generator may need, and execute pageset to populate titles/pageids + $tmpPageSet = new ApiPageSet( $dbSource, ApiPageSet::DISABLE_GENERATORS ); + $generator->setGeneratorMode( $tmpPageSet ); + $this->mCacheMode = $generator->getCacheMode( $generator->extractRequestParams() ); + $generator->requestExtraData( $tmpPageSet ); + $tmpPageSet->execute(); + + // populate this pageset with the generator output + $this->profileOut(); + $generator->profileIn(); + $generator->executeGenerator( $this ); + wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) ); + $this->resolvePendingRedirects(); + $generator->profileOut(); + $this->profileIn(); + + if ( !$isQuery ) { + // If this pageset is not part of the query, we called profileIn() above + $dbSource->profileOut(); + } + } else { + // Only one of the titles/pageids/revids is allowed at the same time + $dataSource = null; + if ( isset( $this->mParams['titles'] ) ) { + $dataSource = 'titles'; + } + if ( isset( $this->mParams['pageids'] ) ) { + if ( isset( $dataSource ) ) { + $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' ); + } + $dataSource = 'pageids'; + } + if ( isset( $this->mParams['revids'] ) ) { + if ( isset( $dataSource ) ) { + $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' ); + } + $dataSource = 'revids'; + } + // Populate page information with the original user input + switch( $dataSource ) { + case 'titles': + $this->initFromTitles( $this->mParams['titles'] ); + break; + case 'pageids': + $this->initFromPageIds( $this->mParams['pageids'] ); + break; + case 'revids': + if ( $this->mResolveRedirects ) { + $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' . + 'Any redirects the revids= point to have not been resolved.' ); + } + $this->mResolveRedirects = false; + $this->initFromRevIDs( $this->mParams['revids'] ); + break; + default: + // Do nothing - some queries do not need any of the data sources. + break; + } + } + $this->profileOut(); } /** @@ -93,8 +181,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Request an additional field from the page table. Must be called - * before execute() + * Request an additional field from the page table. + * Must be called before execute() * @param $fieldName string Field name */ public function requestField( $fieldName ) { @@ -207,13 +295,38 @@ class ApiPageSet extends ApiQueryBase { /** * Get a list of redirect resolutions - maps a title to its redirect - * target. - * @return array prefixed_title (string) => Title object + * target, as an array of output-ready arrays + * @return array */ public function getRedirectTitles() { return $this->mRedirectTitles; } + /** + * Get a list of redirect resolutions - maps a title to its redirect + * target. + * @param $result ApiResult + * @return array of prefixed_title (string) => Title object + * @since 1.21 + */ + public function getRedirectTitlesAsResult( $result = null ) { + $values = array(); + foreach ( $this->getRedirectTitles() as $titleStrFrom => $titleTo ) { + $r = array( + 'from' => strval( $titleStrFrom ), + 'to' => $titleTo->getPrefixedText(), + ); + if ( $titleTo->getFragment() !== '' ) { + $r['tofragment'] = $titleTo->getFragment(); + } + $values[] = $r; + } + if ( !empty( $values ) && $result ) { + $result->setIndexedTagName( $values, 'r' ); + } + return $values; + } + /** * Get a list of title normalizations - maps a title to its normalized * version. @@ -223,6 +336,27 @@ class ApiPageSet extends ApiQueryBase { return $this->mNormalizedTitles; } + /** + * Get a list of title normalizations - maps a title to its normalized + * version in the form of result array. + * @param $result ApiResult + * @return array of raw_prefixed_title (string) => prefixed_title (string) + * @since 1.21 + */ + public function getNormalizedTitlesAsResult( $result = null ) { + $values = array(); + foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) { + $values[] = array( + 'from' => $rawTitleStr, + 'to' => $titleStr + ); + } + if ( !empty( $values ) && $result ) { + $result->setIndexedTagName( $values, 'n' ); + } + return $values; + } + /** * Get a list of title conversions - maps a title to its converted * version. @@ -232,6 +366,27 @@ class ApiPageSet extends ApiQueryBase { return $this->mConvertedTitles; } + /** + * Get a list of title conversions - maps a title to its converted + * version as a result array. + * @param $result ApiResult + * @return array of (from, to) strings + * @since 1.21 + */ + public function getConvertedTitlesAsResult( $result = null ) { + $values = array(); + foreach ( $this->getConvertedTitles() as $rawTitleStr => $titleStr ) { + $values[] = array( + 'from' => $rawTitleStr, + 'to' => $titleStr + ); + } + if ( !empty( $values ) && $result ) { + $result->setIndexedTagName( $values, 'c' ); + } + return $values; + } + /** * Get a list of interwiki titles - maps a title to its interwiki * prefix. @@ -241,6 +396,33 @@ class ApiPageSet extends ApiQueryBase { return $this->mInterwikiTitles; } + /** + * Get a list of interwiki titles - maps a title to its interwiki + * prefix as result. + * @param $result ApiResult + * @param $iwUrl boolean + * @return array raw_prefixed_title (string) => interwiki_prefix (string) + * @since 1.21 + */ + public function getInterwikiTitlesAsResult( $result = null, $iwUrl = false ) { + $values = array(); + foreach ( $this->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) { + $item = array( + 'title' => $rawTitleStr, + 'iw' => $interwikiStr, + ); + if ( $iwUrl ) { + $title = Title::newFromText( $rawTitleStr ); + $item['url'] = $title->getFullURL( '', false, PROTO_CURRENT ); + } + $values[] = $item; + } + if ( !empty( $values ) && $result ) { + $result->setIndexedTagName( $values, 'i' ); + } + return $values; + } + /** * Get the list of revision IDs (requested with the revids= parameter) * @return array revID (int) => pageID (int) @@ -257,6 +439,25 @@ class ApiPageSet extends ApiQueryBase { return $this->mMissingRevIDs; } + /** + * Revision IDs that were not found in the database as result array. + * @param $result ApiResult + * @return array of revision IDs + * @since 1.21 + */ + public function getMissingRevisionIDsAsResult( $result = null ) { + $values = array(); + foreach ( $this->getMissingRevisionIDs() as $revid ) { + $values[$revid] = array( + 'revid' => $revid + ); + } + if ( !empty( $values ) && $result ) { + $result->setIndexedTagName( $values, 'rev' ); + } + return $values; + } + /** * Get the list of titles with negative namespace * @return array Title @@ -273,53 +474,6 @@ class ApiPageSet extends ApiQueryBase { return count( $this->getRevisionIDs() ); } - /** - * Populate the PageSet from the request parameters. - */ - public function execute() { - $this->profileIn(); - $params = $this->extractRequestParams(); - - // Only one of the titles/pageids/revids is allowed at the same time - $dataSource = null; - if ( isset( $params['titles'] ) ) { - $dataSource = 'titles'; - } - if ( isset( $params['pageids'] ) ) { - if ( isset( $dataSource ) ) { - $this->dieUsage( "Cannot use 'pageids' at the same time as '$dataSource'", 'multisource' ); - } - $dataSource = 'pageids'; - } - if ( isset( $params['revids'] ) ) { - if ( isset( $dataSource ) ) { - $this->dieUsage( "Cannot use 'revids' at the same time as '$dataSource'", 'multisource' ); - } - $dataSource = 'revids'; - } - - switch ( $dataSource ) { - case 'titles': - $this->initFromTitles( $params['titles'] ); - break; - case 'pageids': - $this->initFromPageIds( $params['pageids'] ); - break; - case 'revids': - if ( $this->mResolveRedirects ) { - $this->setWarning( 'Redirect resolution cannot be used together with the revids= parameter. ' . - 'Any redirects the revids= point to have not been resolved.' ); - } - $this->mResolveRedirects = false; - $this->initFromRevIDs( $params['revids'] ); - break; - default: - // Do nothing - some queries do not need any of the data sources. - break; - } - $this->profileOut(); - } - /** * Populate this PageSet from a list of Titles * @param $titles array of Title objects @@ -385,12 +539,11 @@ class ApiPageSet extends ApiQueryBase { } /** - * Resolve redirects, if applicable + * Do not use, does nothing, will be removed + * @deprecated 1.21 */ public function finishPageSetGeneration() { - $this->profileIn(); - $this->resolvePendingRedirects(); - $this->profileOut(); + wfDeprecated( __METHOD__, '1.21' ); } /** @@ -437,7 +590,7 @@ class ApiPageSet extends ApiQueryBase { * @param $pageids array of page IDs */ private function initFromPageIds( $pageids ) { - if ( !count( $pageids ) ) { + if ( !$pageids ) { return; } @@ -447,7 +600,7 @@ class ApiPageSet extends ApiQueryBase { $pageids = self::getPositiveIntegers( $pageids ); $res = null; - if ( count( $pageids ) ) { + if ( !empty( $pageids ) ) { $set = array( 'page_id' => $pageids ); @@ -499,7 +652,7 @@ class ApiPageSet extends ApiQueryBase { $this->processDbRow( $row ); // Need gender information - if( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) { + if ( MWNamespace::hasGenderDistinction( $row->page_namespace ) ) { $usernames[] = $row->page_title; } } @@ -518,7 +671,7 @@ class ApiPageSet extends ApiQueryBase { $this->mTitles[] = $title; // need gender information - if( MWNamespace::hasGenderDistinction( $ns ) ) { + if ( MWNamespace::hasGenderDistinction( $ns ) ) { $usernames[] = $dbkey; } } @@ -544,7 +697,7 @@ class ApiPageSet extends ApiQueryBase { * @param $revids array of revision IDs */ private function initFromRevIDs( $revids ) { - if ( !count( $revids ) ) { + if ( !$revids ) { return; } @@ -555,7 +708,7 @@ class ApiPageSet extends ApiQueryBase { $revids = self::getPositiveIntegers( $revids ); - if ( count( $revids ) ) { + if ( !empty( $revids ) ) { $tables = array( 'revision', 'page' ); $fields = array( 'rev_id', 'rev_page' ); $where = array( 'rev_id' => $revids, 'rev_page = page_id' ); @@ -669,6 +822,23 @@ class ApiPageSet extends ApiQueryBase { return $lb; } + /** + * Get the cache mode for the data generated by this module. + * All PageSet users should take into account whether this returns a more-restrictive + * cache mode than the using module itself. For possible return values and other + * details about cache modes, see ApiMain::setCacheMode() + * + * Public caching will only be allowed if *all* the modules that supply + * data for a given request return a cache mode of public. + * + * @param $params + * @return string + * @since 1.21 + */ + public function getCacheMode( $params = null ) { + return $this->mCacheMode; + } + /** * Given an array of title strings, convert them into Title objects. * Alternativelly, an array of Title objects may be given. @@ -742,6 +912,14 @@ class ApiPageSet extends ApiQueryBase { return $linkBatch; } + /** + * Get the database connection (read-only) + * @return DatabaseBase + */ + protected function getDB() { + return $this->mDbSource->getDB(); + } + /** * Returns the input array of integers with all values < 0 removed * @@ -752,7 +930,7 @@ class ApiPageSet extends ApiQueryBase { // bug 25734 API: possible issue with revids validation // It seems with a load of revision rows, MySQL gets upset // Remove any < 0 integers, as they can't be valid - foreach( $array as $i => $int ) { + foreach ( $array as $i => $int ) { if ( $int < 0 ) { unset( $array[$i] ); } @@ -761,8 +939,8 @@ class ApiPageSet extends ApiQueryBase { return $array; } - public function getAllowedParams() { - return array( + public function getAllowedParams( $flags = 0 ) { + $result = array( 'titles' => array( ApiBase::PARAM_ISMULTI => true ), @@ -773,15 +951,48 @@ class ApiPageSet extends ApiQueryBase { 'revids' => array( ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_ISMULTI => true - ) + ), + 'redirects' => false, + 'converttitles' => false, ); + if ( $this->mAllowGenerator ) { + $result['generator'] = array( + ApiBase::PARAM_TYPE => $this->getGenerators() ); + } + return $result; + } + + private static $generators = null; + + /** + * Get an array of all available generators + * @return array + */ + private function getGenerators() { + if ( self::$generators === null ) { + $query = $this->mDbSource; + if ( !( $query instanceof ApiQuery ) ) { + // If the parent container of this pageset is not ApiQuery, + // we must create it to get module manager + $query = $this->getMain()->getModuleManager()->getModule( 'query' ); + } + $gens = array_keys( $query->getGenerators() ); + sort( $gens ); + self::$generators = $gens; + } + return self::$generators; } public function getParamDescription() { return array( 'titles' => 'A list of titles to work on', 'pageids' => 'A list of page IDs to work on', - 'revids' => 'A list of revision IDs to work on' + 'revids' => 'A list of revision IDs to work on', + 'generator' => array( 'Get the list of pages to work on by executing the specified query module.', + '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 ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ), ); } @@ -789,6 +1000,7 @@ class ApiPageSet extends ApiQueryBase { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'multisource', 'info' => "Cannot use 'pageids' at the same time as 'dataSource'" ), array( 'code' => 'multisource', 'info' => "Cannot use 'revids' at the same time as 'dataSource'" ), + array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ), ) ); } } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index c3112d04ad..6978a75536 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -125,7 +125,7 @@ class ApiParamInfo extends ApiBase { $retval['generator'] = ''; } - $allowedParams = $obj->getFinalParams(); + $allowedParams = $obj->getFinalParams( ApiBase::GET_VALUES_FOR_HELP ); if ( !is_array( $allowedParams ) ) { return $retval; } diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php index fa888c9aaa..bd92077ce8 100644 --- a/includes/api/ApiPurge.php +++ b/includes/api/ApiPurge.php @@ -31,57 +31,59 @@ */ class ApiPurge extends ApiBase { + private $mPageSet; + + /** + * Add all items from $values into the result + * @param $result array output + * @param $values array values to add + * @param $flag string the name of the boolean flag to mark this element + * @param $name string if given, name of the value + */ + private static function addValues( array &$result, $values, $flag = null, $name = null ) { + foreach ( $values as $val ) { + if( $val instanceof Title ) { + $v = array(); + ApiQueryBase::addTitleInfo( $v, $val ); + } elseif( $name !== null ) { + $v = array( $name => $val ); + } else { + $v = $val; + } + if( $flag !== null ) { + $v[$flag] = ''; + } + $result[] = $v; + } + } + /** * Purges the cache of a page */ public function execute() { - $user = $this->getUser(); $params = $this->extractRequestParams(); - if ( !$user->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() && - !$this->getRequest()->wasPosted() ) { - $this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) ); - } $forceLinkUpdate = $params['forcelinkupdate']; - $pageSet = new ApiPageSet( $this ); + $pageSet = $this->getPageSet(); $pageSet->execute(); $result = array(); - foreach( $pageSet->getInvalidTitles() as $title ) { + self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' ); + self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' ); + self::addValues( $result, $pageSet->getMissingPageIDs(), 'missing', 'pageid' ); + self::addValues( $result, $pageSet->getMissingRevisionIDs(), 'missing', 'revid' ); + self::addValues( $result, $pageSet->getMissingTitles(), 'missing' ); + self::addValues( $result, $pageSet->getInterwikiTitlesAsResult() ); + + foreach ( $pageSet->getGoodTitles() as $title ) { $r = array(); - $r['title'] = $title; - $r['invalid'] = ''; - $result[] = $r; - } - foreach( $pageSet->getMissingPageIDs() as $p ) { - $page = array(); - $page['pageid'] = $p; - $page['missing'] = ''; - $result[] = $page; - } - foreach( $pageSet->getMissingRevisionIDs() as $r ) { - $rev = array(); - $rev['revid'] = $r; - $rev['missing'] = ''; - $result[] = $rev; - } - - foreach ( $pageSet->getTitles() as $title ) { - $r = array(); - ApiQueryBase::addTitleInfo( $r, $title ); - if ( !$title->exists() ) { - $r['missing'] = ''; - $result[] = $r; - continue; - } - $page = WikiPage::factory( $title ); $page->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = ''; - if( $forceLinkUpdate ) { - if ( !$user->pingLimiter() ) { + if ( $forceLinkUpdate ) { + if ( !$this->getUser()->pingLimiter() ) { global $wgEnableParserCache; $popts = $page->makeParserOptions( 'canonical' ); @@ -112,24 +114,52 @@ class ApiPurge extends ApiBase { $apiResult = $this->getResult(); $apiResult->setIndexedTagName( $result, 'page' ); $apiResult->addValue( null, $this->getModuleName(), $result ); + + $values = $pageSet->getNormalizedTitlesAsResult( $apiResult ); + if ( $values ) { + $apiResult->addValue( null, 'normalized', $values ); + } + $values = $pageSet->getConvertedTitlesAsResult( $apiResult ); + if ( $values ) { + $apiResult->addValue( null, 'converted', $values ); + } + $values = $pageSet->getRedirectTitlesAsResult( $apiResult ); + if ( $values ) { + $apiResult->addValue( null, 'redirects', $values ); + } + } + + /** + * Get a cached instance of an ApiPageSet object + * @return ApiPageSet + */ + private function getPageSet() { + if ( !isset( $this->mPageSet ) ) { + $this->mPageSet = new ApiPageSet( $this ); + } + return $this->mPageSet; } public function isWriteMode() { return true; } - public function getAllowedParams() { - $psModule = new ApiPageSet( $this ); - return $psModule->getAllowedParams() + array( - 'forcelinkupdate' => false, - ); + public function mustBePosted() { + // Anonymous users are not allowed a non-POST request + return !$this->getUser()->isAllowed( 'purge' ); + } + + public function getAllowedParams( $flags = 0 ) { + $result = array( 'forcelinkupdate' => false ); + if ( $flags ) { + $result += $this->getPageSet()->getFinalParams( $flags ); + } + return $result; } public function getParamDescription() { - $psModule = new ApiPageSet( $this ); - return $psModule->getParamDescription() + array( - 'forcelinkupdate' => 'Update the links tables', - ); + return $this->getPageSet()->getParamDescription() + + array( 'forcelinkupdate' => 'Update the links tables' ); } public function getResultProperties() { @@ -153,9 +183,14 @@ class ApiPurge extends ApiBase { ApiBase::PROP_NULLABLE => true ), 'invalid' => 'boolean', + 'special' => 'boolean', 'missing' => 'boolean', 'purged' => 'boolean', - 'linkupdate' => 'boolean' + 'linkupdate' => 'boolean', + 'iw' => array( + ApiBase::PROP_TYPE => 'string', + ApiBase::PROP_NULLABLE => true + ), ) ); } @@ -167,10 +202,9 @@ class ApiPurge extends ApiBase { } public function getPossibleErrors() { - $psModule = new ApiPageSet( $this ); return array_merge( parent::getPossibleErrors(), - $psModule->getPossibleErrors() + $this->getPageSet()->getPossibleErrors() ); } diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 35dd6954ae..619d1cae51 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -140,13 +140,11 @@ class ApiQuery extends ApiBase { */ private $mPageSet; - private $params, $redirects, $convertTitles, $iwUrl; - private $mSlaveDB = null; + private $params; + private $iwUrl; private $mNamedDB = array(); private $mModuleMgr; - protected $mAllowedGenerators; - /** * @param $main ApiMain * @param $action string @@ -171,7 +169,9 @@ class ApiQuery extends ApiBase { $this->mQueryGenerators[$moduleName] = $moduleClass; } } - $this->mAllowedGenerators = array_keys( $this->mQueryGenerators ); + + // Create PageSet that will process titles/pageids/revids/generator + $this->mPageSet = new ApiPageSet( $this ); } /** @@ -182,19 +182,6 @@ class ApiQuery extends ApiBase { return $this->mModuleMgr; } - /** - * Gets a default slave database connection object - * @return DatabaseBase - */ - public function getDB() { - if ( !isset( $this->mSlaveDB ) ) { - $this->profileDBIn(); - $this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' ); - $this->profileDBOut(); - } - return $this->mSlaveDB; - } - /** * Get the query database connection with the given name. * If no such connection has been requested before, it will be created. @@ -228,6 +215,7 @@ class ApiQuery extends ApiBase { * @return array array(modulename => classname) */ public function getModules() { + wfDeprecated( __METHOD__, '1.21' ); return $this->getModuleManager()->getNamesWithClasses(); } @@ -276,34 +264,25 @@ class ApiQuery extends ApiBase { */ public function execute() { $this->params = $this->extractRequestParams(); - $this->redirects = $this->params['redirects']; - $this->convertTitles = $this->params['converttitles']; $this->iwUrl = $this->params['iwurl']; - // Create PageSet - $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles ); - // Instantiate requested modules $modules = array(); $this->instantiateModules( $modules, 'prop' ); $this->instantiateModules( $modules, 'list' ); $this->instantiateModules( $modules, 'meta' ); - $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 ); - $this->mPageSet->execute(); + // 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. + foreach ( $modules as $module ) { + $module->requestExtraData( $this->mPageSet ); } + // Populate page/revision information + $this->mPageSet->execute(); + $cacheMode = $this->mPageSet->getCacheMode(); + // Record page information (title, namespace, if exists, etc) $this->outputGeneralPageInfo(); @@ -347,23 +326,6 @@ class ApiQuery extends ApiBase { 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. - /** - * @var $module ApiQueryBase - */ - foreach ( $modules as $module ) { - $module->requestExtraData( $pageSet ); - } - } - /** * Create instances of all modules requested by the client * @param $modules Array to append instantiated modules to @@ -390,85 +352,25 @@ class ApiQuery extends ApiBase { // 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 - ); + $values = $pageSet->getNormalizedTitlesAsResult( $result ); + if ( $values ) { + $result->addValue( 'query', 'normalized', $values ); } - - if ( count( $normValues ) ) { - $result->setIndexedTagName( $normValues, 'n' ); - $result->addValue( 'query', 'normalized', $normValues ); + $values = $pageSet->getConvertedTitlesAsResult( $result ); + if ( $values ) { + $result->addValue( 'query', 'converted', $values ); } - - // Title conversions - $convValues = array(); - foreach ( $pageSet->getConvertedTitles() as $rawTitleStr => $titleStr ) { - $convValues[] = array( - 'from' => $rawTitleStr, - 'to' => $titleStr - ); + $values = $pageSet->getInterwikiTitlesAsResult( $result, $this->iwUrl ); + if ( $values ) { + $result->addValue( 'query', 'interwiki', $values ); } - - if ( count( $convValues ) ) { - $result->setIndexedTagName( $convValues, 'c' ); - $result->addValue( 'query', 'converted', $convValues ); + $values = $pageSet->getRedirectTitlesAsResult( $result ); + if ( $values ) { + $result->addValue( 'query', 'redirects', $values ); } - - // Interwiki titles - $intrwValues = array(); - foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) { - $item = array( - 'title' => $rawTitleStr, - 'iw' => $interwikiStr, - ); - if ( $this->iwUrl ) { - $title = Title::newFromText( $rawTitleStr ); - $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); - } - $intrwValues[] = $item; - } - - if ( count( $intrwValues ) ) { - $result->setIndexedTagName( $intrwValues, 'i' ); - $result->addValue( 'query', 'interwiki', $intrwValues ); - } - - // Show redirect information - $redirValues = array(); - /** - * @var $titleTo Title - */ - foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) { - $r = array( - 'from' => strval( $titleStrFrom ), - 'to' => $titleTo->getPrefixedText(), - ); - if ( $titleTo->getFragment() !== '' ) { - $r['tofragment'] = $titleTo->getFragment(); - } - $redirValues[] = $r; - } - - if ( count( $redirValues ) ) { - $result->setIndexedTagName( $redirValues, 'r' ); - $result->addValue( 'query', 'redirects', $redirValues ); - } - - // 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 ) { + $result->addValue( 'query', 'badrevids', $values ); } // Page elements @@ -533,8 +435,8 @@ class ApiQuery extends ApiBase { } /** - * @param $pageSet ApiPageSet Pages to be exported - * @param $result ApiResult Result to output to + * @param $pageSet ApiPageSet Pages to be exported + * @param $result ApiResult Result to output to */ private function doExport( $pageSet, $result ) { $exportTitles = array(); @@ -577,53 +479,8 @@ class ApiQuery extends ApiBase { $result->enableSizeCheck(); } - /** - * Create a generator object of the given type and return it - * @param $generatorName string Module name - * @return ApiQueryGeneratorBase - */ - public function newGenerator( $generatorName ) { - $generator = $this->mModuleMgr->getModule( $generatorName, null, true ); - if ( $generator === null ) { - $this->dieUsage( "Unknown generator=$generatorName", 'badgenerator' ); - } - 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( + public function getAllowedParams( $flags = 0 ) { + $result = array( 'prop' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' ) @@ -636,16 +493,15 @@ class ApiQuery extends ApiBase { ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' ) ), - 'generator' => array( - ApiBase::PARAM_TYPE => $this->mAllowedGenerators - ), - 'redirects' => false, - 'converttitles' => false, 'indexpageids' => false, 'export' => false, 'exportnowrap' => false, 'iwurl' => false, ); + if( $flags ) { + $result += $this->getPageSet()->getFinalParams( $flags ); + } + return $result; } /** @@ -653,13 +509,13 @@ class ApiQuery extends ApiBase { * @return string */ public function makeHelpMsg() { - // Make sure the internal object is empty - // (just in case a sub-module decides to optimize during instantiation) - $this->mPageSet = null; + + // Use parent to make default message for the query module + $msg = parent::makeHelpMsg(); $querySeparator = str_repeat( '--- ', 12 ); $moduleSeparator = str_repeat( '*** ', 14 ); - $msg = "\n$querySeparator Query: Prop $querySeparator\n\n"; + $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n"; $msg .= $this->makeHelpMsgHelper( 'prop' ); $msg .= "\n$querySeparator Query: List $querySeparator\n\n"; $msg .= $this->makeHelpMsgHelper( 'list' ); @@ -667,9 +523,6 @@ class ApiQuery extends ApiBase { $msg .= $this->makeHelpMsgHelper( 'meta' ); $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n"; - // Use parent to make default message for the query module - $msg = parent::makeHelpMsg() . $msg; - return $msg; } @@ -703,29 +556,15 @@ class ApiQuery extends ApiBase { 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; } public function getParamDescription() { - return array( + return $this->getPageSet()->getParamDescription() + 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 ' . implode( ', ', LanguageConverter::$languagesWithVariants ) ), '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', @@ -742,9 +581,10 @@ class ApiQuery extends ApiBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'badgenerator', 'info' => 'Module $generatorName cannot be used as a generator' ), - ) ); + return array_merge( + parent::getPossibleErrors(), + $this->getPageSet()->getPossibleErrors() + ); } public function getExamples() { diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php index bbc527244b..b7abb54f29 100644 --- a/includes/api/ApiQueryAllImages.php +++ b/includes/api/ApiQueryAllImages.php @@ -41,7 +41,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { } /** - * Override parent method to make sure to make sure the repo's DB is used + * Override parent method to make sure the repo's DB is used * which may not necesarilly be the same as the local DB. * * TODO: allow querying non-local repos. diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index addcf074ca..59e665209e 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -393,8 +393,7 @@ abstract class ApiQueryBase extends ApiBase { */ protected function getDB() { if ( is_null( $this->mDb ) ) { - $apiQuery = $this->getQuery(); - $this->mDb = $apiQuery->getDB(); + $this->mDb = $this->getQuery()->getDB(); } return $this->mDb; } @@ -585,24 +584,32 @@ abstract class ApiQueryBase extends ApiBase { */ abstract class ApiQueryGeneratorBase extends ApiQueryBase { - private $mIsGenerator; + private $mGeneratorPageSet = null; /** - * @param $query ApiBase - * @param $moduleName string - * @param $paramPrefix string + * Switch this module to generator mode. By default, generator mode is + * switched off and the module acts like a normal query module. + * @since 1.21 requires pageset parameter + * @param $generatorPageSet ApiPageSet object that the module will get + * by calling getPageSet() when in generator mode. */ - public function __construct( $query, $moduleName, $paramPrefix = '' ) { - parent::__construct( $query, $moduleName, $paramPrefix ); - $this->mIsGenerator = false; + public function setGeneratorMode( ApiPageSet $generatorPageSet ) { + if ( $generatorPageSet === null ) { + ApiBase::dieDebug( __METHOD__, 'Required parameter missing - $generatorPageSet' ); + } + $this->mGeneratorPageSet = $generatorPageSet; } /** - * Switch this module to generator mode. By default, generator mode is - * switched off and the module acts like a normal query module. + * Get the PageSet object to work on. + * If this module is generator, the pageSet object is different from other module's + * @return ApiPageSet */ - public function setGeneratorMode() { - $this->mIsGenerator = true; + protected function getPageSet() { + if ( $this->mGeneratorPageSet !== null ) { + return $this->mGeneratorPageSet; + } + return parent::getPageSet(); } /** @@ -611,7 +618,7 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { * @return string Prefixed parameter name */ public function encodeParamName( $paramName ) { - if ( $this->mIsGenerator ) { + if ( $this->mGeneratorPageSet !== null ) { return 'g' . parent::encodeParamName( $paramName ); } else { return parent::encodeParamName( $paramName ); diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php index 7a47a0ce88..3f5ebfe85a 100644 --- a/includes/api/ApiSetNotificationTimestamp.php +++ b/includes/api/ApiSetNotificationTimestamp.php @@ -31,6 +31,8 @@ */ class ApiSetNotificationTimestamp extends ApiBase { + private $mPageSet; + public function execute() { $user = $this->getUser(); @@ -41,7 +43,7 @@ class ApiSetNotificationTimestamp extends ApiBase { $params = $this->extractRequestParams(); $this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' ); - $pageSet = new ApiPageSet( $this ); + $pageSet = $this->getPageSet(); $args = array_merge( array( $params, 'entirewatchlist' ), array_keys( $pageSet->getAllowedParams() ) ); call_user_func_array( array( $this, 'requireOnlyOneParameter' ), $args ); @@ -92,20 +94,20 @@ class ApiSetNotificationTimestamp extends ApiBase { $result['notificationtimestamp'] = ( is_null( $timestamp ) ? '' : wfTimestamp( TS_ISO_8601, $timestamp ) ); } else { // First, log the invalid titles - foreach( $pageSet->getInvalidTitles() as $title ) { + foreach ( $pageSet->getInvalidTitles() as $title ) { $r = array(); $r['title'] = $title; $r['invalid'] = ''; $result[] = $r; } - foreach( $pageSet->getMissingPageIDs() as $p ) { + foreach ( $pageSet->getMissingPageIDs() as $p ) { $page = array(); $page['pageid'] = $p; $page['missing'] = ''; $page['notwatched'] = ''; $result[] = $page; } - foreach( $pageSet->getMissingRevisionIDs() as $r ) { + foreach ( $pageSet->getMissingRevisionIDs() as $r ) { $rev = array(); $rev['revid'] = $r; $rev['missing'] = ''; @@ -157,6 +159,17 @@ class ApiSetNotificationTimestamp extends ApiBase { $apiResult->addValue( null, $this->getModuleName(), $result ); } + /** + * Get a cached instance of an ApiPageSet object + * @return ApiPageSet + */ + private function getPageSet() { + if ( !isset( $this->mPageSet ) ) { + $this->mPageSet = new ApiPageSet( $this ); + } + return $this->mPageSet; + } + public function mustBePosted() { return true; } @@ -173,9 +186,8 @@ class ApiSetNotificationTimestamp extends ApiBase { return ''; } - public function getAllowedParams() { - $psModule = new ApiPageSet( $this ); - return $psModule->getAllowedParams() + array( + public function getAllowedParams( $flags = 0 ) { + $result = array( 'entirewatchlist' => array( ApiBase::PARAM_TYPE => 'boolean' ), @@ -190,11 +202,15 @@ class ApiSetNotificationTimestamp extends ApiBase { ApiBase::PARAM_TYPE => 'integer' ), ); + if ( $flags ) { + $result += $this->getPageSet()->getFinalParams( $flags ); + } + return $result; + } public function getParamDescription() { - $psModule = new ApiPageSet( $this ); - return $psModule->getParamDescription() + array( + return $this->getPageSet()->getParamDescription() + array( 'entirewatchlist' => 'Work on all watched pages', 'timestamp' => 'Timestamp to which to set the notification timestamp', 'torevid' => 'Revision to set the notification timestamp to (one page only)', @@ -249,12 +265,14 @@ class ApiSetNotificationTimestamp extends ApiBase { } public function getPossibleErrors() { - $psModule = new ApiPageSet( $this ); + $ps = $this->getPageSet(); return array_merge( parent::getPossibleErrors(), - $psModule->getPossibleErrors(), - $this->getRequireMaxOneParameterErrorMessages( array( 'timestamp', 'torevid', 'newerthanrevid' ) ), - $this->getRequireOnlyOneParameterErrorMessages( array_merge( array( 'entirewatchlist' ), array_keys( $psModule->getAllowedParams() ) ) ), + $ps->getPossibleErrors(), + $this->getRequireMaxOneParameterErrorMessages( + array( 'timestamp', 'torevid', 'newerthanrevid' ) ), + $this->getRequireOnlyOneParameterErrorMessages( + array_merge( array( 'entirewatchlist' ), array_keys( $ps->getFinalParams() ) ) ), array( array( 'code' => 'notloggedin', 'info' => 'Anonymous users cannot use watchlist change notifications' ), array( 'code' => 'multpages', 'info' => 'torevid may only be used with a single page' ), diff --git a/tests/phpunit/includes/api/ApiGeneratorTest.php b/tests/phpunit/includes/api/ApiGeneratorTest.php index e5a871726f..445969bde8 100644 --- a/tests/phpunit/includes/api/ApiGeneratorTest.php +++ b/tests/phpunit/includes/api/ApiGeneratorTest.php @@ -39,7 +39,7 @@ class ApiGeneratorTest extends MediaWikiTestCase { */ public function provideApiquerygeneratorbaseChilds() { $cases = array(); - $modules = $this->getApiQuery()->getModules(); + $modules = $this->getApiQuery()->getModuleManager()->getNamesWithClasses(); foreach( $modules as $moduleName => $moduleClass ) { if( !is_subclass_of( $moduleClass, 'ApiQueryGeneratorBase' ) ) { continue; @@ -55,7 +55,7 @@ class ApiGeneratorTest extends MediaWikiTestCase { public function testGeneratorsAreApiquerygeneratorbaseSubclasses( $generatorName, $generatorClass ) { - $modules = $this->getApiQuery()->getModules(); + $modules = $this->getApiQuery()->getModuleManager()->getNamesWithClasses(); $this->assertArrayHasKey( $generatorName, $modules, "Class '$generatorClass' of generator '$generatorName' must be a subclass of 'ApiQueryGeneratorBase'. Listed either in ApiQuery::\$mQueryGenerators or in \$wgAPIGeneratorModules." ); diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php index 3bacb054c4..d9be85e320 100644 --- a/tests/phpunit/includes/api/PrefixUniquenessTest.php +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -9,7 +9,7 @@ class PrefixUniquenessTest extends MediaWikiTestCase { public function testPrefixes() { $main = new ApiMain( new FauxRequest() ); $query = new ApiQuery( $main, 'foo', 'bar' ); - $modules = $query->getModules(); + $modules = $query->getModuleManager()->getNamesWithClasses(); $prefixes = array(); foreach ( $modules as $name => $class ) { -- 2.20.1