From a97bb3acfa7e61d79b7c689b2e31ca8a1da432a7 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Tue, 5 Feb 2013 01:52:55 -0500 Subject: [PATCH] API module manager and help rewrite This is a non-versioned part of the larger patch #41014 https://gerrit.wikimedia.org/r/#/c/41014 It will allow help subsystem optimization (merging paraminfo and help), path towards per-module or per-system versioning, removal of the manually maintained generator lists. Changes: * ApiModuleManager now handles all submodules (actions,props,lists) and instantiation * ApiModuleManager maintains a cache of all instantiated modules * Query stores prop/list/meta as submodules * action=help suports generalized submodules (modules=query+value), querymodules obsolete Change-Id: Ie2dee41e44a29cd5d5935eeaa5240b708d95a8f0 --- RELEASE-NOTES-1.21 | 3 + includes/AutoLoader.php | 1 + includes/api/ApiBase.php | 14 ++- includes/api/ApiHelp.php | 83 +++++++++------ includes/api/ApiMain.php | 78 ++++++++------ includes/api/ApiModuleManager.php | 171 ++++++++++++++++++++++++++++++ includes/api/ApiParamInfo.php | 102 ++++++++---------- includes/api/ApiQuery.php | 127 ++++++++++------------ 8 files changed, 388 insertions(+), 191 deletions(-) create mode 100644 includes/api/ApiModuleManager.php diff --git a/RELEASE-NOTES-1.21 b/RELEASE-NOTES-1.21 index 9852162c93..c735271391 100644 --- a/RELEASE-NOTES-1.21 +++ b/RELEASE-NOTES-1.21 @@ -194,11 +194,14 @@ production. * (bug 43849) ApiQueryImageInfo no longer throws exceptions with ForeignDBRepo redirects. * On error, any warnings generated before that error will be shown in the result. +* action=help suports generalized submodules (modules=query+value), querymodules obsolete === API internal changes in 1.21 === * For debugging only, a new global $wgDebugAPI removes many API restrictions when true. Never use on the production servers, as this flag introduces security holes. 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 === Languages updated in 1.21 === diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 1ee8da70d7..be642e89fb 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -363,6 +363,7 @@ $wgAutoloadLocalClasses = array( 'ApiLogin' => 'includes/api/ApiLogin.php', 'ApiLogout' => 'includes/api/ApiLogout.php', 'ApiMain' => 'includes/api/ApiMain.php', + 'ApiModuleManager' => 'includes/api/ApiModuleManager.php', 'ApiMove' => 'includes/api/ApiMove.php', 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php', 'ApiOptions' => 'includes/api/ApiOptions.php', diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 68302b1fa3..743fef0433 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -127,6 +127,16 @@ abstract class ApiBase extends ContextSource { return $this->mModuleName; } + + /** + * Get the module manager, or null if this module has no sub-modules + * @since 1.21 + * @return ApiModuleManager + */ + public function getModuleManager() { + return null; + } + /** * Get parameter prefix (usually two letters or an empty string). * @return string @@ -258,6 +268,8 @@ abstract class ApiBase extends ContextSource { } $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n"; + $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() ); + if ( $this->isReadMode() ) { $msg .= "\nThis module requires read rights"; } @@ -301,8 +313,6 @@ abstract class ApiBase extends ContextSource { } } } - - $msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() ); } return $msg; diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 86f8112978..9cafc5bbfa 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -43,43 +43,62 @@ class ApiHelp extends ApiBase { } $this->getMain()->setHelp(); - $result = $this->getResult(); - $queryObj = new ApiQuery( $this->getMain(), 'query' ); - $r = array(); - if ( is_array( $params['modules'] ) ) { - $modArr = $this->getMain()->getModules(); - - foreach ( $params['modules'] as $m ) { - if ( !isset( $modArr[$m] ) ) { - $r[] = array( 'name' => $m, 'missing' => '' ); - continue; - } - $module = new $modArr[$m]( $this->getMain(), $m ); - $r[] = $this->buildModuleHelp( $module, 'action' ); - } + if ( is_array( $params['modules'] ) ) { + $modules = $params['modules']; + } else { + $modules = array(); } if ( is_array( $params['querymodules'] ) ) { - $qmodArr = $queryObj->getModules(); + $queryModules = $params['querymodules']; + foreach ( $queryModules as $m ) { + $modules[] = 'query+' . $m; + } + } else { + $queryModules = array(); + } - foreach ( $params['querymodules'] as $qm ) { - if ( !isset( $qmodArr[$qm] ) ) { - $r[] = array( 'name' => $qm, 'missing' => '' ); - continue; + $r = array(); + foreach ( $modules as $m ) { + // sub-modules could be given in the form of "name[+name[+name...]]" + $subNames = explode( '+', $m ); + if ( count( $subNames ) === 1 ) { + // In case the '+' was typed into URL, it resolves as a space + $subNames = explode( ' ', $m ); + } + $module = $this->getMain(); + for ( $i = 0; $i < count( $subNames ); $i++ ) { + $subs = $module->getModuleManager(); + if ( $subs === null ) { + $module = null; + } else { + $module = $subs->getModule( $subNames[$i] ); } - $module = new $qmodArr[$qm]( $this, $qm ); - $type = $queryObj->getModuleType( $qm ); - - if ( $type === null ) { - $r[] = array( 'name' => $qm, 'missing' => '' ); - continue; + if ( $module === null ) { + if ( count( $subNames ) === 2 + && $i === 1 + && $subNames[0] === 'query' + && in_array( $subNames[1], $queryModules ) + ) { + // Legacy: This is one of the renamed 'querymodule=...' parameters, + // do not use '+' notation in the output, use submodule's name instead. + $name = $subNames[1]; + } else { + $name = implode( '+', array_slice( $subNames, 0, $i + 1 ) ); + } + $r[] = array( 'name' => $name, 'missing' => '' ); + break; + } else { + $type = $subs->getModuleGroup( $subNames[$i] ); } - + } + if ( $module !== null ) { $r[] = $this->buildModuleHelp( $module, $type ); } } + $result->setIndexedTagName( $r, 'module' ); $result->addValue( null, $this->getModuleName(), $r ); } @@ -114,15 +133,16 @@ class ApiHelp extends ApiBase { ApiBase::PARAM_ISMULTI => true ), 'querymodules' => array( - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_DEPRECATED => true ), ); } public function getParamDescription() { return array( - 'modules' => 'List of module names (value of the action= parameter)', - 'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)', + 'modules' => 'List of module names (value of the action= parameter). Can specify submodules with a \'+\'', + 'querymodules' => 'Use modules=query+value instead. List of query module names (value of prop=, meta= or list= parameter)', ); } @@ -134,9 +154,8 @@ class ApiHelp extends ApiBase { return array( 'api.php?action=help' => 'Whole help page', 'api.php?action=help&modules=protect' => 'Module (action) help page', - 'api.php?action=help&querymodules=categorymembers' => 'Query (list) modules help page', - 'api.php?action=help&querymodules=info' => 'Query (prop) modules help page', - 'api.php?action=help&querymodules=siteinfo' => 'Query (meta) modules help page', + 'api.php?action=help&modules=query+categorymembers' => 'Help for the query/categorymembers module', + 'api.php?action=help&modules=login|query+info' => 'Help for the login and query/info modules', ); } diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 70c31c1cce..3535cd04e0 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -131,8 +131,9 @@ class ApiMain extends ApiBase { */ private $mPrinter; - private $mModules, $mModuleNames, $mFormats, $mFormatNames; - private $mResult, $mAction, $mEnableWrite; + private $mModuleMgr, $mResult; + private $mAction; + private $mEnableWrite; private $mInternalMode, $mSquidMaxage, $mModule; private $mCacheMode = 'private'; @@ -180,12 +181,11 @@ class ApiMain extends ApiBase { } } - global $wgAPIModules; // extension modules - $this->mModules = $wgAPIModules + self::$Modules; - - $this->mModuleNames = array_keys( $this->mModules ); - $this->mFormats = self::$Formats; - $this->mFormatNames = array_keys( $this->mFormats ); + global $wgAPIModules; + $this->mModuleMgr = new ApiModuleManager( $this ); + $this->mModuleMgr->addModules( self::$Modules, 'action' ); + $this->mModuleMgr->addModules( $wgAPIModules, 'action' ); + $this->mModuleMgr->addModules( self::$Formats, 'format' ); $this->mResult = new ApiResult( $this ); $this->mEnableWrite = $enableWrite; @@ -332,10 +332,11 @@ class ApiMain extends ApiBase { * @return ApiFormatBase */ public function createPrinterByName( $format ) { - if ( !isset( $this->mFormats[$format] ) ) { + $printer = $this->mModuleMgr->getModule( $format, 'format' ); + if ( $printer === null ) { $this->dieUsage( "Unrecognized format: {$format}", 'unknown_format' ); } - return new $this->mFormats[$format] ( $this, $format ); + return $printer; } /** @@ -604,7 +605,7 @@ class ApiMain extends ApiBase { if ( !isset ( $this->mPrinter ) ) { // The printer has not been created yet. Try to manually get formatter value. $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); - if ( !in_array( $value, $this->mFormatNames ) ) { + if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) { $value = self::API_DEFAULT_FORMAT; } @@ -700,9 +701,10 @@ class ApiMain extends ApiBase { */ protected function setupModule() { // Instantiate the module requested by the user - $module = new $this->mModules[$this->mAction] ( $this, $this->mAction ); - $this->mModule = $module; - + $module = $this->mModuleMgr->getModule( $this->mAction, 'action' ); + if ( $module === null ) { + $this->dieUsage( 'The API requires a valid action parameter', 'unknown_action' ); + } $moduleParams = $module->extractRequestParams(); // Die if token required, but not provided (unless there is a gettoken parameter) @@ -814,6 +816,7 @@ class ApiMain extends ApiBase { protected function executeAction() { $params = $this->setupExecuteAction(); $module = $this->setupModule(); + $this->mModule = $module; $this->checkExecutePermissions( $module ); @@ -971,11 +974,11 @@ class ApiMain extends ApiBase { return array( 'format' => array( ApiBase::PARAM_DFLT => ApiMain::API_DEFAULT_FORMAT, - ApiBase::PARAM_TYPE => $this->mFormatNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'format' ) ), 'action' => array( ApiBase::PARAM_DFLT => 'help', - ApiBase::PARAM_TYPE => $this->mModuleNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' ) ), 'maxlag' => array( ApiBase::PARAM_TYPE => 'integer' @@ -1092,7 +1095,7 @@ class ApiMain extends ApiBase { ' Victor Vasiliev - vasilvv at gee mail dot com', ' Bryan Tong Minh - bryan . tongminh @ gmail . com', ' Sam Reed - sam @ reedyboy . net', - ' Yuri Astrakhan "@gmail.com" (creator, lead developer Sep 2006-Sep 2007)', + ' Yuri Astrakhan "@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012)', '', 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org', 'or file a bug report at https://bugzilla.wikimedia.org/' @@ -1143,8 +1146,9 @@ class ApiMain extends ApiBase { $astriks = str_repeat( '*** ', 14 ); $msg .= "\n\n$astriks Modules $astriks\n\n"; - foreach ( array_keys( $this->mModules ) as $moduleName ) { - $module = new $this->mModules[$moduleName] ( $this, $moduleName ); + + foreach ( $this->mModuleMgr->getNames( 'action' ) as $name ) { + $module = $this->mModuleMgr->getModule( $name ); $msg .= self::makeHelpMsgHeader( $module, 'action' ); $msg2 = $module->makeHelpMsg(); @@ -1162,8 +1166,8 @@ class ApiMain extends ApiBase { } $msg .= "\n$astriks Formats $astriks\n\n"; - foreach ( array_keys( $this->mFormats ) as $formatName ) { - $module = $this->createPrinterByName( $formatName ); + foreach ( $this->mModuleMgr->getNames( 'format' ) as $name ) { + $module = $this->mModuleMgr->getModule( $name ); $msg .= self::makeHelpMsgHeader( $module, 'format' ); $msg2 = $module->makeHelpMsg(); if ( $msg2 !== false ) { @@ -1215,45 +1219,57 @@ class ApiMain extends ApiBase { return false; } + /** + * Overrides to return this instance's module manager. + * @return ApiModuleManager + */ + public function getModuleManager() { + return $this->mModuleMgr; + } + /** * Add or overwrite a module in this ApiMain instance. Intended for use by extending * classes who wish to add their own modules to their lexicon or override the * behavior of inherent ones. * - * @param $mdlName String The identifier for this module. - * @param $mdlClass String The class where this module is implemented. + * @deprecated since 1.21, Use getModuleManager()->addModule() instead. + * @param $name string The identifier for this module. + * @param $class ApiBase The class where this module is implemented. */ - protected function addModule( $mdlName, $mdlClass ) { - $this->mModules[$mdlName] = $mdlClass; + protected function addModule( $name, $class ) { + $this->getModuleManager()->addModule( $name, 'action', $class ); } /** * Add or overwrite an output format for this ApiMain. Intended for use by extending * classes who wish to add to or modify current formatters. * - * @param $fmtName string The identifier for this format. - * @param $fmtClass ApiFormatBase The class implementing this format. + * @deprecated since 1.21, Use getModuleManager()->addModule() instead. + * @param $name string The identifier for this format. + * @param $class ApiFormatBase The class implementing this format. */ - protected function addFormat( $fmtName, $fmtClass ) { - $this->mFormats[$fmtName] = $fmtClass; + protected function addFormat( $name, $class ) { + $this->getModuleManager->addModule( $name, 'format', $class ); } /** * Get the array mapping module names to class names + * @deprecated since 1.21, Use getModuleManager()'s methods instead. * @return array */ function getModules() { - return $this->mModules; + return $this->getModuleManager()->getNamesWithClasses( 'action' ); } /** * Returns the list of supported formats in form ( 'format' => 'ClassName' ) * * @since 1.18 + * @deprecated since 1.21, Use getModuleManager()'s methods instead. * @return array */ public function getFormats() { - return $this->mFormats; + return $this->getModuleManager()->getNamesWithClasses( 'format' ); } } diff --git a/includes/api/ApiModuleManager.php b/includes/api/ApiModuleManager.php new file mode 100644 index 0000000000..db1d36d733 --- /dev/null +++ b/includes/api/ApiModuleManager.php @@ -0,0 +1,171 @@ +@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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + */ + +/** + * This class holds a list of modules and handles instantiation + * + * @since 1.21 + * @ingroup API + */ +class ApiModuleManager extends ContextSource { + + private $mParent; + private $mInstances = array(); + private $mGroups = array(); + private $mModules = array(); + + /** + * Construct new module manager + * @param ApiBase $parentModule Parent module instance will be used during instantiation + */ + public function __construct( ApiBase $parentModule ) { + $this->mParent = $parentModule; + } + + /** + * Add a list of modules to the manager + * @param array $modules A map of ModuleName => ModuleClass + * @param string $group Which group modules belong to (action,format,...) + */ + public function addModules( array $modules, $group ) { + foreach ( $modules as $name => $class ) { + $this->addModule( $name, $group, $class ); + } + } + + /** + * Add or overwrite a module in this ApiMain instance. Intended for use by extending + * classes who wish to add their own modules to their lexicon or override the + * behavior of inherent ones. + * + * @param $group string Name of the module group + * @param $name string The identifier for this module. + * @param $class string The class where this module is implemented. + */ + public function addModule( $name, $group, $class ) { + $this->mGroups[$group] = null; + $this->mModules[$name] = array( $group, $class ); + } + + /** + * Get module instance by name, or instantiate it if it does not exist + * @param $moduleName string module name + * @param $group string optionally validate that the module is in a specific group + * @param $ignoreCache bool if true, force-creates a new instance and does not cache it + * @return mixed the new module instance, or null if failed + */ + public function getModule( $moduleName, $group = null, $ignoreCache = false ) { + if ( !isset( $this->mModules[$moduleName] ) ) { + return null; + } + $grpCls = $this->mModules[$moduleName]; + if ( $group !== null && $grpCls[0] !== $group ) { + return null; + } + if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) { + // already exists + return $this->mInstances[$moduleName]; + } else { + // new instance + $class = $grpCls[1]; + $instance = new $class ( $this->mParent, $moduleName ); + if ( !$ignoreCache ) { + // cache this instance in case it is needed later + $this->mInstances[$moduleName] = $instance; + } + return $instance; + } + } + + /** + * Get an array of modules in a specific group or all if no group is set. + * @param string $group optional group filter + * @return array list of module names + */ + public function getNames( $group = null ) { + if ( $group === null ) { + return array_keys( $this->mModules ); + } + $result = array(); + foreach ( $this->mModules as $name => $grpCls ) { + if ( $grpCls[0] === $group ) { + $result[] = $name; + } + } + return $result; + } + + /** + * Create an array of (moduleName => moduleClass) for a specific group or for all. + * @param string $group name of the group to get or null for all + * @return array name=>class map + */ + public function getNamesWithClasses( $group = null ) { + $result = array(); + foreach ( $this->mModules as $name => $grpCls ) { + if ( $group === null || $grpCls[0] === $group ) { + $result[$name] = $grpCls[1]; + } + } + return $result; + } + + /** + * Returns true if the specific module is defined at all or in a specific group. + * @param string $moduleName module name + * @param string $group group name to check against, or null to check all groups, + * @return boolean true if defined + */ + public function isDefined( $moduleName, $group = null ) { + if ( isset( $this->mModules[$moduleName] ) ) { + return $group === null || $this->mModules[$moduleName][0] === $group; + } else { + return false; + } + } + + /** + * Returns the group name for the given module + * @param string $moduleName + * @return string group name or null if missing + */ + public function getModuleGroup( $moduleName ) { + if ( isset( $this->mModules[$moduleName] ) ) { + return $this->mModules[$moduleName][0]; + } else { + return null; + } + } + + /** + * Get a list of groups this manager contains. + * @return array + */ + public function getGroups() { + return array_keys( $this->mGroups ); + } +} diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index 42c490e2eb..c3112d04ad 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -42,42 +42,13 @@ class ApiParamInfo extends ApiBase { public function execute() { // Get parameters $params = $this->extractRequestParams(); - $result = $this->getResult(); + $resultObj = $this->getResult(); $res = array(); - if ( is_array( $params['modules'] ) ) { - $modules = $this->getMain()->getModules(); - $res['modules'] = array(); - foreach ( $params['modules'] as $mod ) { - if ( !isset( $modules[$mod] ) ) { - $res['modules'][] = array( 'name' => $mod, 'missing' => '' ); - continue; - } - $obj = new $modules[$mod]( $this->getMain(), $mod ); - $item = $this->getClassInfo( $obj ); - $item['name'] = $mod; - $res['modules'][] = $item; - } - $result->setIndexedTagName( $res['modules'], 'module' ); - } + $this->addModulesInfo( $params, 'modules', $res, $resultObj ); - if ( is_array( $params['querymodules'] ) ) { - $queryModules = $this->queryObj->getModules(); - $res['querymodules'] = array(); - foreach ( $params['querymodules'] as $qm ) { - if ( !isset( $queryModules[$qm] ) ) { - $res['querymodules'][] = array( 'name' => $qm, 'missing' => '' ); - continue; - } - $obj = new $queryModules[$qm]( $this, $qm ); - $item = $this->getClassInfo( $obj ); - $item['name'] = $qm; - $item['querytype'] = $this->queryObj->getModuleType( $qm ); - $res['querymodules'][] = $item; - } - $result->setIndexedTagName( $res['querymodules'], 'module' ); - } + $this->addModulesInfo( $params, 'querymodules', $res, $resultObj ); if ( $params['mainmodule'] ) { $res['mainmodule'] = $this->getClassInfo( $this->getMain() ); @@ -88,29 +59,50 @@ class ApiParamInfo extends ApiBase { $res['pagesetmodule'] = $this->getClassInfo( $pageSet ); } - if ( is_array( $params['formatmodules'] ) ) { - $formats = $this->getMain()->getFormats(); - $res['formatmodules'] = array(); - foreach ( $params['formatmodules'] as $f ) { - if ( !isset( $formats[$f] ) ) { - $res['formatmodules'][] = array( 'name' => $f, 'missing' => '' ); - continue; - } - $obj = new $formats[$f]( $this, $f ); - $item = $this->getClassInfo( $obj ); - $item['name'] = $f; - $res['formatmodules'][] = $item; + $this->addModulesInfo( $params, 'formatmodules', $res, $resultObj ); + + $resultObj->addValue( null, $this->getModuleName(), $res ); + } + + /** + * If the type is requested in parameters, adds a section to res with module info. + * @param array $params user parameters array + * @param string $type parameter name + * @param array $res store results in this array + * @param array $resultObj results object to set indexed tag. + */ + private function addModulesInfo( $params, $type, &$res, $resultObj ) { + if ( !is_array( $params[$type] ) ) { + return; + } + $isQuery = ( $type === 'querymodules' ); + if ( $isQuery ) { + $mgr = $this->queryObj->getModuleManager(); + } else { + $mgr = $this->getMain()->getModuleManager(); + } + $res[$type] = array(); + foreach ( $params[$type] as $mod ) { + if ( !$mgr->isDefined( $mod ) ) { + $res[$type][] = array( 'name' => $mod, 'missing' => '' ); + continue; + } + $obj = $mgr->getModule( $mod ); + $item = $this->getClassInfo( $obj ); + $item['name'] = $mod; + if ( $isQuery ) { + $item['querytype'] = $mgr->getModuleGroup( $mod ); } - $result->setIndexedTagName( $res['formatmodules'], 'module' ); + $res[$type][] = $item; } - $result->addValue( null, $this->getModuleName(), $res ); + $resultObj->setIndexedTagName( $res[$type], 'module' ); } /** * @param $obj ApiBase * @return ApiResult */ - function getClassInfo( $obj ) { + private function getClassInfo( $obj ) { $result = $this->getResult(); $retval['classname'] = get_class( $obj ); $retval['description'] = implode( "\n", (array)$obj->getFinalDescription() ); @@ -150,7 +142,7 @@ class ApiParamInfo extends ApiBase { if ( is_string( $examples ) ) { $examples = array( $examples ); } - foreach( $examples as $k => $v ) { + foreach ( $examples as $k => $v ) { if ( strlen( $retval['examples'] ) ) { $retval['examples'] .= ' '; } @@ -181,7 +173,7 @@ class ApiParamInfo extends ApiBase { } //handle shorthand - if( !is_array( $p ) ) { + if ( !is_array( $p ) ) { $p = array( ApiBase::PARAM_DFLT => $p, ); @@ -208,11 +200,11 @@ class ApiParamInfo extends ApiBase { if ( isset( $p[ApiBase::PARAM_DFLT] ) ) { $type = $p[ApiBase::PARAM_TYPE]; - if( $type === 'boolean' ) { + if ( $type === 'boolean' ) { $a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' ); - } elseif( $type === 'string' ) { + } elseif ( $type === 'string' ) { $a['default'] = strval( $p[ApiBase::PARAM_DFLT] ); - } elseif( $type === 'integer' ) { + } elseif ( $type === 'integer' ) { $a['default'] = intval( $p[ApiBase::PARAM_DFLT] ); } else { $a['default'] = $p[ApiBase::PARAM_DFLT]; @@ -319,11 +311,11 @@ class ApiParamInfo extends ApiBase { } public function getAllowedParams() { - $modules = array_keys( $this->getMain()->getModules() ); + $modules = $this->getMain()->getModuleManager()->getNames( 'action' ); sort( $modules ); - $querymodules = array_keys( $this->queryObj->getModules() ); + $querymodules = $this->queryObj->getModuleManager()->getNames(); sort( $querymodules ); - $formatmodules = array_keys( $this->getMain()->getFormats() ); + $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' ); sort( $formatmodules ); return array( 'modules' => array( diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index e5e6ca45b1..fa1b2d3874 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -37,20 +37,11 @@ */ class ApiQuery extends ApiBase { - private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames; - - /** - * @var ApiPageSet - */ - private $mPageSet; - - private $params, $redirects, $convertTitles, $iwUrl; - /** * List of Api Query prop modules * @var array */ - private $mQueryPropModules = array( + private static $QueryPropModules = array( 'categories' => 'ApiQueryCategories', 'categoryinfo' => 'ApiQueryCategoryInfo', 'duplicatefiles' => 'ApiQueryDuplicateFiles', @@ -71,7 +62,7 @@ class ApiQuery extends ApiBase { * List of Api Query list modules * @var array */ - private $mQueryListModules = array( + private static $QueryListModules = array( 'allcategories' => 'ApiQueryAllCategories', 'allimages' => 'ApiQueryAllImages', 'alllinks' => 'ApiQueryAllLinks', @@ -105,7 +96,7 @@ class ApiQuery extends ApiBase { * List of Api Query meta modules * @var array */ - private $mQueryMetaModules = array( + private static $QueryMetaModules = array( 'allmessages' => 'ApiQueryAllMessages', 'siteinfo' => 'ApiQuerySiteinfo', 'userinfo' => 'ApiQueryUserInfo', @@ -144,8 +135,15 @@ class ApiQuery extends ApiBase { 'watchlistraw' => 'ApiQueryWatchlistRaw', ); + /** + * @var ApiPageSet + */ + private $mPageSet; + + private $params, $redirects, $convertTitles, $iwUrl; private $mSlaveDB = null; private $mNamedDB = array(); + private $mModuleMgr; protected $mAllowedGenerators; @@ -156,30 +154,32 @@ class ApiQuery extends ApiBase { public function __construct( $main, $action ) { parent::__construct( $main, $action ); + $this->mModuleMgr = new ApiModuleManager( $this ); + // Allow custom modules to be added in LocalSettings.php - global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules, $wgAPIGeneratorModules; - self::appendUserModules( $this->mQueryPropModules, $wgAPIPropModules ); - self::appendUserModules( $this->mQueryListModules, $wgAPIListModules ); - self::appendUserModules( $this->mQueryMetaModules, $wgAPIMetaModules ); - self::appendUserModules( $this->mQueryGenerators, $wgAPIGeneratorModules ); - - $this->mPropModuleNames = array_keys( $this->mQueryPropModules ); - $this->mListModuleNames = array_keys( $this->mQueryListModules ); - $this->mMetaModuleNames = array_keys( $this->mQueryMetaModules ); + global $wgAPIPropModules, $wgAPIListModules, $wgAPIMetaModules; + $this->mModuleMgr->addModules( self::$QueryPropModules, 'prop' ); + $this->mModuleMgr->addModules( $wgAPIPropModules, 'prop' ); + $this->mModuleMgr->addModules( self::$QueryListModules, 'list' ); + $this->mModuleMgr->addModules( $wgAPIListModules, 'list' ); + $this->mModuleMgr->addModules( self::$QueryMetaModules, 'meta' ); + $this->mModuleMgr->addModules( $wgAPIMetaModules, 'meta' ); + + global $wgAPIGeneratorModules; + if ( is_array( $wgAPIGeneratorModules ) ) { + foreach ( $wgAPIGeneratorModules as $moduleName => $moduleClass ) { + $this->mQueryGenerators[$moduleName] = $moduleClass; + } + } $this->mAllowedGenerators = array_keys( $this->mQueryGenerators ); } /** - * 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 + * Overrides to return this instance's module manager. + * @return ApiModuleManager */ - private static function appendUserModules( &$modules, $newModules ) { - if ( is_array( $newModules ) ) { - foreach ( $newModules as $moduleName => $moduleClass ) { - $modules[$moduleName] = $moduleClass; - } - } + public function getModuleManager() { + return $this->mModuleMgr; } /** @@ -224,10 +224,11 @@ class ApiQuery extends ApiBase { /** * Get the array mapping module names to class names + * @deprecated since 1.21, use getModuleManager()'s methods instead * @return array array(modulename => classname) */ public function getModules() { - return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules ); + return $this->getModuleManager()->getNamesWithClasses(); } /** @@ -240,23 +241,12 @@ class ApiQuery extends ApiBase { /** * Get whether the specified module is a prop, list or a meta query module + * @deprecated since 1.21, use getModuleManager()->getModuleGroup() * @param $moduleName string Name of the module to find type for * @return mixed string or null */ function getModuleType( $moduleName ) { - if ( isset( $this->mQueryPropModules[$moduleName] ) ) { - return 'prop'; - } - - if ( isset( $this->mQueryListModules[$moduleName] ) ) { - return 'list'; - } - - if ( isset( $this->mQueryMetaModules[$moduleName] ) ) { - return 'meta'; - } - - return null; + return $this->getModuleManager()->getModuleGroup( $moduleName ); } /** @@ -295,9 +285,9 @@ class ApiQuery extends ApiBase { // Instantiate requested modules $modules = array(); - $this->instantiateModules( $modules, 'prop', $this->mQueryPropModules ); - $this->instantiateModules( $modules, 'list', $this->mQueryListModules ); - $this->instantiateModules( $modules, 'meta', $this->mQueryMetaModules ); + $this->instantiateModules( $modules, 'prop' ); + $this->instantiateModules( $modules, 'list' ); + $this->instantiateModules( $modules, 'meta' ); $cacheMode = 'public'; @@ -378,12 +368,11 @@ class ApiQuery extends ApiBase { * 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) */ - private function instantiateModules( &$modules, $param, $moduleList ) { + private function instantiateModules( &$modules, $param ) { if ( isset( $this->params[$param] ) ) { foreach ( $this->params[$param] as $moduleName ) { - $modules[] = new $moduleList[$moduleName] ( $this, $moduleName ); + $modules[] = $this->mModuleMgr->getModule( $moduleName ); } } } @@ -594,15 +583,10 @@ class ApiQuery extends ApiBase { * @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 = $this->mModuleMgr->getModule( $generatorName ); + if ( $generator === null ) { + $this->dieUsage( "Unknown generator=$generatorName", 'badgenerator' ); } - $generator = new $className ( $this, $generatorName ); if ( !$generator instanceof ApiQueryGeneratorBase ) { $this->dieUsage( "Module $generatorName cannot be used as a generator", 'badgenerator' ); } @@ -642,15 +626,15 @@ class ApiQuery extends ApiBase { return array( 'prop' => array( ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mPropModuleNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'prop' ) ), 'list' => array( ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mListModuleNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'list' ) ), 'meta' => array( ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => $this->mMetaModuleNames + ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'meta' ) ), 'generator' => array( ApiBase::PARAM_TYPE => $this->mAllowedGenerators @@ -676,11 +660,11 @@ class ApiQuery extends ApiBase { $querySeparator = str_repeat( '--- ', 12 ); $moduleSeparator = str_repeat( '*** ', 14 ); $msg = "\n$querySeparator Query: Prop $querySeparator\n\n"; - $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' ); + $msg .= $this->makeHelpMsgHelper( 'prop' ); $msg .= "\n$querySeparator Query: List $querySeparator\n\n"; - $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' ); + $msg .= $this->makeHelpMsgHelper( 'list' ); $msg .= "\n$querySeparator Query: Meta $querySeparator\n\n"; - $msg .= $this->makeHelpMsgHelper( $this->mQueryMetaModules, 'meta' ); + $msg .= $this->makeHelpMsgHelper( 'meta' ); $msg .= "\n\n$moduleSeparator Modules: continuation $moduleSeparator\n\n"; // Use parent to make default message for the query module @@ -690,21 +674,22 @@ class ApiQuery extends ApiBase { } /** - * For all modules in $moduleList, generate help messages and join them together - * @param $moduleList Array array(modulename => classname) - * @param $paramName string Parameter name + * For all modules of a given group, generate help messages and join them together + * @param $group string Module group * @return string */ - private function makeHelpMsgHelper( $moduleList, $paramName ) { + private function makeHelpMsgHelper( $group ) { $moduleDescriptions = array(); - foreach ( $moduleList as $moduleName => $moduleClass ) { + $moduleNames = $this->mModuleMgr->getNames( $group ); + sort( $moduleNames ); + foreach ( $moduleNames as $name ) { /** * @var $module ApiQueryBase */ - $module = new $moduleClass( $this, $moduleName, null ); + $module = $this->mModuleMgr->getModule( $name ); - $msg = ApiMain::makeHelpMsgHeader( $module, $paramName ); + $msg = ApiMain::makeHelpMsgHeader( $module, $group ); $msg2 = $module->makeHelpMsg(); if ( $msg2 !== false ) { $msg .= $msg2; -- 2.20.1