Merge "Sanitize TestUser"
[lhc/web/wiklou.git] / includes / api / ApiBase.php
index 6914b02..a214f2e 100644 (file)
@@ -64,16 +64,16 @@ abstract class ApiBase extends ContextSource {
        // Boolean, if MIN/MAX are set, enforce (die) these?
        // Only applies if TYPE='integer' Use with extreme caution
        const PARAM_RANGE_ENFORCE = 9;
-
-       // Name of property group that is on the root element of the result,
-       // i.e. not part of a list
-       const PROP_ROOT = 'ROOT';
-       // Boolean, is the result multiple items? Defaults to true for query modules,
-       // to false for other modules
-       const PROP_LIST = 'LIST';
-       const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE
-       // Boolean, can the property be not included in the result? Defaults to false
-       const PROP_NULLABLE = 1;
+       /// @since 1.25
+       // Specify an alternative i18n message for this help parameter.
+       // Value can be a string key, an array giving key and parameters, or a
+       // Message object.
+       const PARAM_HELP_MSG = 10;
+       /// @since 1.25
+       // Specify additional i18n messages to append to the normal message. Value
+       // is an array of any of strings giving the message key, arrays giving key and
+       // parameters, or Message objects.
+       const PARAM_HELP_MSG_APPEND = 11;
 
        const LIMIT_BIG1 = 500; // Fast query, std user limit
        const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
@@ -153,27 +153,64 @@ abstract class ApiBase extends ContextSource {
        }
 
        /**
-        * Returns the description string for this module
-        * @return string|array
+        * Returns usage examples for this module.
+        *
+        * Return value has query strings as keys, with values being either strings
+        * (message key), arrays (message key + parameter), or Message objects.
+        *
+        * Do not call this base class implementation when overriding this method.
+        *
+        * @since 1.25
+        * @return array
         */
-       protected function getDescription() {
-               return false;
-       }
+       protected function getExamplesMessages() {
+               // Fall back to old non-localised method
+               $ret = array();
+
+               $examples = $this->getExamples();
+               if ( $examples ) {
+                       if ( !is_array( $examples ) ) {
+                               $examples = array( $examples );
+                       } elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
+                               array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
+                               !preg_match( '/^\s*api\.php\?/', $examples[0] )
+                       ) {
+                               // Fix up the ugly "even numbered elements are description, odd
+                               // numbered elemts are the link" format (see doc for self::getExamples)
+                               $tmp = array();
+                               for ( $i = 0; $i < count( $examples ); $i += 2 ) {
+                                       $tmp[$examples[$i + 1]] = $examples[$i];
+                               }
+                               $examples = $tmp;
+                       }
 
-       /**
-        * Returns usage examples for this module. Return false if no examples are available.
-        * @return bool|string|array
-        */
-       protected function getExamples() {
-               return false;
+                       foreach ( $examples as $k => $v ) {
+                               if ( is_numeric( $k ) ) {
+                                       $qs = $v;
+                                       $msg = '';
+                               } else {
+                                       $qs = $k;
+                                       $msg = self::escapeWikiText( $v );
+                                       if ( is_array( $msg ) ) {
+                                               $msg = join( " ", $msg );
+                                       }
+                               }
+
+                               $qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
+                               $ret[$qs] = $this->msg( 'api-help-fallback-example', array( $msg ) );
+                       }
+               }
+
+               return $ret;
        }
 
        /**
-        * @return bool|string|array Returns a false if the module has no help URL,
-        *   else returns a (array of) string
+        * Return links to more detailed help pages about the module.
+        * @since 1.25, returning boolean false is deprecated
+        * @return string|array
         */
        public function getHelpUrls() {
-               return false;
+               return array();
        }
 
        /**
@@ -186,22 +223,12 @@ abstract class ApiBase extends ContextSource {
         * in the overriding methods. Callers of this method can pass zero or
         * more OR-ed flags like GET_VALUES_FOR_HELP.
         *
-        * @return array|bool
+        * @return array
         */
        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 function directly: use getFinalParamDescription() to
-        * allow hooks to modify descriptions as needed.
-        * @return array|bool False on no parameter descriptions
-        */
-       protected function getParamDescription() {
-               return false;
+               return array();
        }
 
        /**
@@ -236,6 +263,24 @@ abstract class ApiBase extends ContextSource {
                return $this->needsToken() !== false;
        }
 
+       /**
+        * Indicates whether this module is deprecated
+        * @since 1.25
+        * @return bool
+        */
+       public function isDeprecated() {
+               return false;
+       }
+
+       /**
+        * Indicates whether this module is "internal" or unstable
+        * @since 1.25
+        * @return bool
+        */
+       public function isInternal() {
+               return false;
+       }
+
        /**
         * Returns the token type this module requires in order to execute.
         *
@@ -244,11 +289,9 @@ abstract class ApiBase extends ContextSource {
         * core types, you must use the ApiQueryTokensRegisterTypes hook to
         * register it.
         *
-        * Returning a non-falsey value here will cause self::getFinalParams() to
-        * return a required string 'token' parameter and
-        * self::getFinalParamDescription() to ensure there is standardized
-        * documentation for it. Also, self::mustBePosted() must return true when
-        * tokens are used.
+        * Returning a non-falsey value here will force the addition of an
+        * appropriate 'token' parameter in self::getFinalParams(). Also,
+        * self::mustBePosted() must return true when tokens are used.
         *
         * In previous versions of MediaWiki, true was a valid return value.
         * Returning true will generate errors indicating that the API module needs
@@ -313,6 +356,73 @@ abstract class ApiBase extends ContextSource {
                return $this === $this->mMainModule;
        }
 
+       /**
+        * Get the parent of this module
+        * @since 1.25
+        * @return ApiBase|null
+        */
+       public function getParent() {
+               return $this->isMain() ? null : $this->getMain();
+       }
+
+       /**
+        * Get the path to this module
+        *
+        * @since 1.25
+        * @return string
+        */
+       public function getModulePath() {
+               if ( $this->isMain() ) {
+                       return 'main';
+               } elseif ( $this->getParent()->isMain() ) {
+                       return $this->getModuleName();
+               } else {
+                       return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
+               }
+       }
+
+       /**
+        * Get a module from its module path
+        *
+        * @since 1.25
+        * @param string $path
+        * @return ApiBase|null
+        * @throws UsageException
+        */
+       public function getModuleFromPath( $path ) {
+               $module = $this->getMain();
+               if ( $path === 'main' ) {
+                       return $module;
+               }
+
+               $parts = explode( '+', $path );
+               if ( count( $parts ) === 1 ) {
+                       // In case the '+' was typed into URL, it resolves as a space
+                       $parts = explode( ' ', $path );
+               }
+
+               $count = count( $parts );
+               for ( $i = 0; $i < $count; $i++ ) {
+                       $parent = $module;
+                       $manager = $parent->getModuleManager();
+                       if ( $manager === null ) {
+                               $errorPath = join( '+', array_slice( $parts, 0, $i ) );
+                               $this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
+                       }
+                       $module = $manager->getModule( $parts[$i] );
+
+                       if ( $module === null ) {
+                               $errorPath = $i ? join( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
+                               $this->dieUsage(
+                                       "The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
+                                       'badmodule'
+                               );
+                       }
+               }
+
+               return $module;
+       }
+
        /**
         * Get the result object
         * @return ApiResult
@@ -349,70 +459,6 @@ abstract class ApiBase extends ContextSource {
                return $this->mSlaveDB;
        }
 
-       /**
-        * Get final module description, after hooks have had a chance to tweak it as
-        * needed.
-        *
-        * @return array|bool False on no parameters
-        */
-       public function getFinalDescription() {
-               $desc = $this->getDescription();
-               wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
-
-               return $desc;
-       }
-
-       /**
-        * Get final list of parameters, after hooks have had a chance to
-        * tweak it as needed.
-        *
-        * @param int $flags 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( $flags = 0 ) {
-               $params = $this->getAllowedParams( $flags );
-
-               if ( $this->needsToken() ) {
-                       $params['token'] = array(
-                               ApiBase::PARAM_TYPE => 'string',
-                               ApiBase::PARAM_REQUIRED => true,
-                       );
-               }
-
-               wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
-
-               return $params;
-       }
-
-       /**
-        * Get final parameter descriptions, after hooks have had a chance to tweak it as
-        * needed.
-        *
-        * @return array|bool False on no parameter descriptions
-        */
-       public function getFinalParamDescription() {
-               $desc = $this->getParamDescription();
-
-               $tokenType = $this->needsToken();
-               if ( $tokenType ) {
-                       if ( !isset( $desc['token'] ) ) {
-                               $desc['token'] = array();
-                       } elseif ( !is_array( $desc['token'] ) ) {
-                               // We ignore a plain-string token, because it's probably an
-                               // extension that is supplying the string for BC.
-                               $desc['token'] = array();
-                       }
-                       array_unshift( $desc['token'],
-                               "A '$tokenType' token retrieved from action=query&meta=tokens"
-                       );
-               }
-
-               wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
-
-               return $desc;
-       }
-
        /**@}*/
 
        /************************************************************************//**
@@ -1103,6 +1149,55 @@ abstract class ApiBase extends ContextSource {
                return $user;
        }
 
+       /**
+        * A subset of wfEscapeWikiText for BC texts
+        *
+        * @since 1.25
+        * @param string|array $v
+        * @return string|array
+        */
+       private static function escapeWikiText( $v ) {
+               if ( is_array( $v ) ) {
+                       return array_map( 'self::escapeWikiText', $v );
+               } else {
+                       return strtr( $v, array(
+                               '__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
+                               '[[Category:' => '[[:Category:',
+                               '[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
+                       ) );
+               }
+       }
+
+       /**
+        * Create a Message from a string or array
+        *
+        * A string is used as a message key. An array has the message key as the
+        * first value and message parameters as subsequent values.
+        *
+        * @since 1.25
+        * @param string|array|Message $msg
+        * @param IContextSource $context
+        * @param array $params
+        * @return Message|null
+        */
+       public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
+               if ( is_string( $msg ) ) {
+                       $msg = wfMessage( $msg );
+               } elseif ( is_array( $msg ) ) {
+                       $msg = call_user_func_array( 'wfMessage', $msg );
+               }
+               if ( !$msg instanceof Message ) {
+                       return null;
+               }
+
+               $msg->setContext( $context );
+               if ( $params ) {
+                       $msg->params( $params );
+               }
+
+               return $msg;
+       }
+
        /**@}*/
 
        /************************************************************************//**
@@ -1671,6 +1766,10 @@ abstract class ApiBase extends ContextSource {
                        'code' => 'undofailure',
                        'info' => 'Undo failed due to conflicting intermediate edits'
                ),
+               'content-not-allowed-here' => array(
+                       'code' => 'contentnotallowedhere',
+                       'info' => 'Content model "$1" is not allowed at title "$2"'
+               ),
 
                // Messages from WikiPage::doEit()
                'edit-hook-aborted' => array(
@@ -1825,262 +1924,190 @@ abstract class ApiBase extends ContextSource {
         */
 
        /**
-        * Generates help message for this module, or false if there is no description
-        * @return string|bool
+        * Get final module description, after hooks have had a chance to tweak it as
+        * needed.
+        *
+        * @since 1.25, returns Message[] rather than string[]
+        * @return Message[]
         */
-       public function makeHelpMsg() {
-               static $lnPrfx = "\n  ";
+       public function getFinalDescription() {
+               $desc = $this->getDescription();
+               wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+               $desc = self::escapeWikiText( $desc );
+               if ( is_array( $desc ) ) {
+                       $desc = join( "\n", $desc );
+               } else {
+                       $desc = (string)$desc;
+               }
 
-               $msg = $this->getFinalDescription();
+               $msg = $this->msg( "apihelp-{$this->getModulePath()}-description", array(
+                       $this->getModulePrefix(),
+                       $this->getModuleName(),
+                       $this->getModulePath(),
+               ) );
+               if ( !$msg->exists() ) {
+                       $msg = $this->msg( 'api-help-fallback-description', $desc );
+               }
+               $msgs = array( $msg );
 
-               if ( $msg !== false ) {
+               wfRunHooks( 'APIGetDescriptionMessages', array( $this, &$msgs ) );
 
-                       if ( !is_array( $msg ) ) {
-                               $msg = array(
-                                       $msg
-                               );
-                       }
-                       $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+               return $msgs;
+       }
 
-                       $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+       /**
+        * Get final list of parameters, after hooks have had a chance to
+        * tweak it as needed.
+        *
+        * @param int $flags 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( $flags = 0 ) {
+               $params = $this->getAllowedParams( $flags );
+               if ( !$params ) {
+                       $params = array();
+               }
 
-                       if ( $this->isReadMode() ) {
-                               $msg .= "\nThis module requires read rights";
-                       }
-                       if ( $this->isWriteMode() ) {
-                               $msg .= "\nThis module requires write rights";
-                       }
-                       if ( $this->mustBePosted() ) {
-                               $msg .= "\nThis module only accepts POST requests";
+               if ( $this->needsToken() ) {
+                       $params['token'] = array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_REQUIRED => true,
+                               ApiBase::PARAM_HELP_MSG => array(
+                                       'api-help-param-token',
+                                       $this->needsToken(),
+                               ),
+                       ) + ( isset( $params['token'] ) ? $params['token'] : array() );
+               }
+
+               wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
+
+               return $params;
+       }
+
+       /**
+        * Get final parameter descriptions, after hooks have had a chance to tweak it as
+        * needed.
+        *
+        * @since 1.25, returns array of Message[] rather than array of string[]
+        * @return array Keys are parameter names, values are arrays of Message objects
+        */
+       public function getFinalParamDescription() {
+               $desc = $this->getParamDescription();
+               wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
+
+               if ( !$desc ) {
+                       $desc = array();
+               }
+               $desc = self::escapeWikiText( $desc );
+
+               $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+               $msgs = array();
+               foreach ( $params as $param => $settings ) {
+                       if ( !is_array( $settings ) ) {
+                               $settings = array();
                        }
-                       if ( $this->isReadMode() || $this->isWriteMode() ||
-                               $this->mustBePosted()
-                       ) {
-                               $msg .= "\n";
+
+                       $d = isset( $desc[$param] ) ? $desc[$param] : '';
+                       if ( is_array( $d ) ) {
+                               // Special handling for prop parameters
+                               $d = array_map( function ( $line ) {
+                                       if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
+                                               $line = "\n;{$m[1]}:{$m[2]}";
+                                       }
+                                       return $line;
+                               }, $d );
+                               $d = join( ' ', $d );
                        }
 
-                       // Parameters
-                       $paramsMsg = $this->makeHelpMsgParameters();
-                       if ( $paramsMsg !== false ) {
-                               $msg .= "Parameters:\n$paramsMsg";
+                       if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
+                               $msg = $settings[ApiBase::PARAM_HELP_MSG];
+                       } else {
+                               $msg = $this->msg( "apihelp-{$this->getModulePath()}-param-{$param}" );
+                               if ( !$msg->exists() ) {
+                                       $msg = $this->msg( 'api-help-fallback-parameter', $d );
+                               }
                        }
+                       $msg = ApiBase::makeMessage( $msg, $this->getContext(), array(
+                               $this->getModulePrefix(),
+                               $param,
+                               $this->getModuleName(),
+                               $this->getModulePath(),
+                       ) );
+                       if ( !$msg ) {
+                               $this->dieDebug( __METHOD__,
+                                       'Value in ApiBase::PARAM_HELP_MSG is not valid' );
+                       }
+                       $msgs[$param] = array( $msg );
 
-                       $examples = $this->getExamples();
-                       if ( $examples ) {
-                               if ( !is_array( $examples ) ) {
-                                       $examples = array(
-                                               $examples
-                                       );
+                       if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
+                               if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
+                                       $this->dieDebug( __METHOD__,
+                                               'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
                                }
-                               $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
-                               foreach ( $examples as $k => $v ) {
-                                       if ( is_numeric( $k ) ) {
-                                               $msg .= "  $v\n";
+                               foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
+                                       $m = ApiBase::makeMessage( $m, $this->getContext(), array(
+                                               $this->getModulePrefix(),
+                                               $param,
+                                               $this->getModuleName(),
+                                               $this->getModulePath(),
+                                       ) );
+                                       if ( $m ) {
+                                               $msgs[$param][] = $m;
                                        } else {
-                                               if ( is_array( $v ) ) {
-                                                       $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
-                                               } else {
-                                                       $msgExample = "  $v";
-                                               }
-                                               $msgExample .= ":";
-                                               $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
+                                               $this->dieDebug( __METHOD__,
+                                                       'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
                                        }
                                }
                        }
                }
 
-               return $msg;
-       }
+               wfRunHooks( 'APIGetParamDescriptionMessages', array( $this, &$msgs ) );
 
-       /**
-        * @param string $item
-        * @return string
-        */
-       private function indentExampleText( $item ) {
-               return "  " . $item;
+               return $msgs;
        }
 
        /**
-        * @param string $prefix Text to split output items
-        * @param string $title What is being output
-        * @param string|array $input
-        * @return string
+        * Generates the list of flags for the help screen and for action=paraminfo
+        *
+        * Corresponding messages: api-help-flag-deprecated,
+        * api-help-flag-internal, api-help-flag-readrights,
+        * api-help-flag-writerights, api-help-flag-mustbeposted
+        *
+        * @return string[]
         */
-       protected function makeHelpArrayToString( $prefix, $title, $input ) {
-               if ( $input === false ) {
-                       return '';
+       protected function getHelpFlags() {
+               $flags = array();
+
+               if ( $this->isDeprecated() ) {
+                       $flags[] = 'deprecated';
                }
-               if ( !is_array( $input ) ) {
-                       $input = array( $input );
+               if ( $this->isInternal() ) {
+                       $flags[] = 'internal';
                }
-
-               if ( count( $input ) > 0 ) {
-                       if ( $title ) {
-                               $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
-                       } else {
-                               $msg = '  ';
-                       }
-                       $msg .= implode( $prefix, $input ) . "\n";
-
-                       return $msg;
+               if ( $this->isReadMode() ) {
+                       $flags[] = 'readrights';
+               }
+               if ( $this->isWriteMode() ) {
+                       $flags[] = 'writerights';
+               }
+               if ( $this->mustBePosted() ) {
+                       $flags[] = 'mustbeposted';
                }
 
-               return '';
+               return $flags;
        }
 
        /**
-        * Generates the parameter descriptions for this module, to be displayed in the
-        * module's help.
-        * @return string|bool
+        * Called from ApiHelp before the pieces are joined together and returned.
+        *
+        * This exists mainly for ApiMain to add the Permissions and Credits
+        * sections. Other modules probably don't need it.
+        *
+        * @param string[] &$help Array of help data
+        * @param array $options Options passed to ApiHelp::getHelp
         */
-       public function makeHelpMsgParameters() {
-               $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
-               if ( $params ) {
-
-                       $paramsDescription = $this->getFinalParamDescription();
-                       $msg = '';
-                       $paramPrefix = "\n" . str_repeat( ' ', 24 );
-                       $descWordwrap = "\n" . str_repeat( ' ', 28 );
-                       foreach ( $params as $paramName => $paramSettings ) {
-                               $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
-                               if ( is_array( $desc ) ) {
-                                       $desc = implode( $paramPrefix, $desc );
-                               }
-
-                               //handle shorthand
-                               if ( !is_array( $paramSettings ) ) {
-                                       $paramSettings = array(
-                                               self::PARAM_DFLT => $paramSettings,
-                                       );
-                               }
-
-                               //handle missing type
-                               if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
-                                       $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
-                                               ? $paramSettings[ApiBase::PARAM_DFLT]
-                                               : null;
-                                       if ( is_bool( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
-                                       } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'string';
-                                       } elseif ( is_int( $dflt ) ) {
-                                               $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
-                                       }
-                               }
-
-                               if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
-                                       && $paramSettings[self::PARAM_DEPRECATED]
-                               ) {
-                                       $desc = "DEPRECATED! $desc";
-                               }
-
-                               if ( isset( $paramSettings[self::PARAM_REQUIRED] )
-                                       && $paramSettings[self::PARAM_REQUIRED]
-                               ) {
-                                       $desc .= $paramPrefix . "This parameter is required";
-                               }
-
-                               $type = isset( $paramSettings[self::PARAM_TYPE] )
-                                       ? $paramSettings[self::PARAM_TYPE]
-                                       : null;
-                               if ( isset( $type ) ) {
-                                       $hintPipeSeparated = true;
-                                       $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
-                                               ? $paramSettings[self::PARAM_ISMULTI]
-                                               : false;
-                                       if ( $multi ) {
-                                               $prompt = 'Values (separate with \'|\'): ';
-                                       } else {
-                                               $prompt = 'One value: ';
-                                       }
-
-                                       if ( $type === 'submodule' ) {
-                                               $type = $this->getModuleManager()->getNames( $paramName );
-                                               sort( $type );
-                                       }
-                                       if ( is_array( $type ) ) {
-                                               $choices = array();
-                                               $nothingPrompt = '';
-                                               foreach ( $type as $t ) {
-                                                       if ( $t === '' ) {
-                                                               $nothingPrompt = 'Can be empty, or ';
-                                                       } else {
-                                                               $choices[] = $t;
-                                                       }
-                                               }
-                                               $desc .= $paramPrefix . $nothingPrompt . $prompt;
-                                               $choicesstring = implode( ', ', $choices );
-                                               $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
-                                               $hintPipeSeparated = false;
-                                       } else {
-                                               switch ( $type ) {
-                                                       case 'namespace':
-                                                               // Special handling because namespaces are
-                                                               // type-limited, yet they are not given
-                                                               $desc .= $paramPrefix . $prompt;
-                                                               $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
-                                                                       100, $descWordwrap );
-                                                               $hintPipeSeparated = false;
-                                                               break;
-                                                       case 'limit':
-                                                               $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
-                                                               if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
-                                                                       $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
-                                                               }
-                                                               $desc .= ' allowed';
-                                                               break;
-                                                       case 'integer':
-                                                               $s = $multi ? 's' : '';
-                                                               $hasMin = isset( $paramSettings[self::PARAM_MIN] );
-                                                               $hasMax = isset( $paramSettings[self::PARAM_MAX] );
-                                                               if ( $hasMin || $hasMax ) {
-                                                                       if ( !$hasMax ) {
-                                                                               $intRangeStr = "The value$s must be no less than " .
-                                                                                       "{$paramSettings[self::PARAM_MIN]}";
-                                                                       } elseif ( !$hasMin ) {
-                                                                               $intRangeStr = "The value$s must be no more than " .
-                                                                                       "{$paramSettings[self::PARAM_MAX]}";
-                                                                       } else {
-                                                                               $intRangeStr = "The value$s must be between " .
-                                                                                       "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
-                                                                       }
-
-                                                                       $desc .= $paramPrefix . $intRangeStr;
-                                                               }
-                                                               break;
-                                                       case 'upload':
-                                                               $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
-                                                               break;
-                                               }
-                                       }
-
-                                       if ( $multi ) {
-                                               if ( $hintPipeSeparated ) {
-                                                       $desc .= $paramPrefix . "Separate values with '|'";
-                                               }
-
-                                               $isArray = is_array( $type );
-                                               if ( !$isArray
-                                                       || $isArray && count( $type ) > self::LIMIT_SML1
-                                               ) {
-                                                       $desc .= $paramPrefix . "Maximum number of values " .
-                                                               self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
-                                               }
-                                       }
-                               }
-
-                               $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
-                               if ( !is_null( $default ) && $default !== false ) {
-                                       $desc .= $paramPrefix . "Default: $default";
-                               }
-
-                               $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
-                       }
-
-                       return $msg;
-               }
-
-               return false;
+       public function modifyHelp( array &$help, array $options ) {
        }
 
        /**@}*/
@@ -2241,6 +2268,15 @@ abstract class ApiBase extends ContextSource {
         * @{
         */
 
+       /// @deprecated since 1.24
+       const PROP_ROOT = 'ROOT';
+       /// @deprecated since 1.24
+       const PROP_LIST = 'LIST';
+       /// @deprecated since 1.24
+       const PROP_TYPE = 0;
+       /// @deprecated since 1.24
+       const PROP_NULLABLE = 1;
+
        /**
         * Formerly returned a string that identifies the version of the extending
         * class. Typically included the class name, the svn revision, timestamp,
@@ -2362,6 +2398,321 @@ abstract class ApiBase extends ContextSource {
                return array();
        }
 
+       /**
+        * Returns the description string for this module
+        *
+        * Ignored if an i18n message exists for
+        * "apihelp-{$this->getModulePathString()}-description".
+        *
+        * @deprecated since 1.25
+        * @return Message|string|array
+        */
+       protected function getDescription() {
+               return false;
+       }
+
+       /**
+        * Returns an array of parameter descriptions.
+        *
+        * For each parameter, ignored if an i18n message exists for the parameter.
+        * By default that message is
+        * "apihelp-{$this->getModulePathString()}-param-{$param}", but it may be
+        * overridden using ApiBase::PARAM_HELP_MSG in the data returned by
+        * self::getFinalParams().
+        *
+        * @deprecated since 1.25
+        * @return array|bool False on no parameter descriptions
+        */
+       protected function getParamDescription() {
+               return false;
+       }
+
+       /**
+        * Returns usage examples for this module.
+        *
+        * Return value as an array is either:
+        *  - numeric keys with partial URLs ("api.php?" plus a query string) as
+        *    values
+        *  - sequential numeric keys with even-numbered keys being display-text
+        *    and odd-numbered keys being partial urls
+        *  - partial URLs as keys with display-text (string or array-to-be-joined)
+        *    as values
+        * Return value as a string is the same as an array with a numeric key and
+        * that value, and boolean false means "no examples".
+        *
+        * @deprecated since 1.25, use getExamplesMessages() instead
+        * @return bool|string|array
+        */
+       protected function getExamples() {
+               return false;
+       }
+
+       /**
+        * Generates help message for this module, or false if there is no description
+        * @deprecated since 1.25
+        * @return string|bool
+        */
+       public function makeHelpMsg() {
+               wfDeprecated( __METHOD__, '1.25' );
+               static $lnPrfx = "\n  ";
+
+               $msg = $this->getFinalDescription();
+
+               if ( $msg !== false ) {
+
+                       if ( !is_array( $msg ) ) {
+                               $msg = array(
+                                       $msg
+                               );
+                       }
+                       $msg = $lnPrfx . implode( $lnPrfx, $msg ) . "\n";
+
+                       $msg .= $this->makeHelpArrayToString( $lnPrfx, false, $this->getHelpUrls() );
+
+                       if ( $this->isReadMode() ) {
+                               $msg .= "\nThis module requires read rights";
+                       }
+                       if ( $this->isWriteMode() ) {
+                               $msg .= "\nThis module requires write rights";
+                       }
+                       if ( $this->mustBePosted() ) {
+                               $msg .= "\nThis module only accepts POST requests";
+                       }
+                       if ( $this->isReadMode() || $this->isWriteMode() ||
+                               $this->mustBePosted()
+                       ) {
+                               $msg .= "\n";
+                       }
+
+                       // Parameters
+                       $paramsMsg = $this->makeHelpMsgParameters();
+                       if ( $paramsMsg !== false ) {
+                               $msg .= "Parameters:\n$paramsMsg";
+                       }
+
+                       $examples = $this->getExamples();
+                       if ( $examples ) {
+                               if ( !is_array( $examples ) ) {
+                                       $examples = array(
+                                               $examples
+                                       );
+                               }
+                               $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
+                               foreach ( $examples as $k => $v ) {
+                                       if ( is_numeric( $k ) ) {
+                                               $msg .= "  $v\n";
+                                       } else {
+                                               if ( is_array( $v ) ) {
+                                                       $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
+                                               } else {
+                                                       $msgExample = "  $v";
+                                               }
+                                               $msgExample .= ":";
+                                               $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n    $k\n";
+                                       }
+                               }
+                       }
+               }
+
+               return $msg;
+       }
+
+       /**
+        * @deprecated since 1.25
+        * @param string $item
+        * @return string
+        */
+       private function indentExampleText( $item ) {
+               return "  " . $item;
+       }
+
+       /**
+        * @deprecated since 1.25
+        * @param string $prefix Text to split output items
+        * @param string $title What is being output
+        * @param string|array $input
+        * @return string
+        */
+       protected function makeHelpArrayToString( $prefix, $title, $input ) {
+               wfDeprecated( __METHOD__, '1.25' );
+               if ( $input === false ) {
+                       return '';
+               }
+               if ( !is_array( $input ) ) {
+                       $input = array( $input );
+               }
+
+               if ( count( $input ) > 0 ) {
+                       if ( $title ) {
+                               $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n  ";
+                       } else {
+                               $msg = '  ';
+                       }
+                       $msg .= implode( $prefix, $input ) . "\n";
+
+                       return $msg;
+               }
+
+               return '';
+       }
+
+       /**
+        * Generates the parameter descriptions for this module, to be displayed in the
+        * module's help.
+        * @deprecated since 1.25
+        * @return string|bool
+        */
+       public function makeHelpMsgParameters() {
+               wfDeprecated( __METHOD__, '1.25' );
+               $params = $this->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
+               if ( $params ) {
+
+                       $paramsDescription = $this->getFinalParamDescription();
+                       $msg = '';
+                       $paramPrefix = "\n" . str_repeat( ' ', 24 );
+                       $descWordwrap = "\n" . str_repeat( ' ', 28 );
+                       foreach ( $params as $paramName => $paramSettings ) {
+                               $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
+                               if ( is_array( $desc ) ) {
+                                       $desc = implode( $paramPrefix, $desc );
+                               }
+
+                               //handle shorthand
+                               if ( !is_array( $paramSettings ) ) {
+                                       $paramSettings = array(
+                                               self::PARAM_DFLT => $paramSettings,
+                                       );
+                               }
+
+                               //handle missing type
+                               if ( !isset( $paramSettings[ApiBase::PARAM_TYPE] ) ) {
+                                       $dflt = isset( $paramSettings[ApiBase::PARAM_DFLT] )
+                                               ? $paramSettings[ApiBase::PARAM_DFLT]
+                                               : null;
+                                       if ( is_bool( $dflt ) ) {
+                                               $paramSettings[ApiBase::PARAM_TYPE] = 'boolean';
+                                       } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+                                               $paramSettings[ApiBase::PARAM_TYPE] = 'string';
+                                       } elseif ( is_int( $dflt ) ) {
+                                               $paramSettings[ApiBase::PARAM_TYPE] = 'integer';
+                                       }
+                               }
+
+                               if ( isset( $paramSettings[self::PARAM_DEPRECATED] )
+                                       && $paramSettings[self::PARAM_DEPRECATED]
+                               ) {
+                                       $desc = "DEPRECATED! $desc";
+                               }
+
+                               if ( isset( $paramSettings[self::PARAM_REQUIRED] )
+                                       && $paramSettings[self::PARAM_REQUIRED]
+                               ) {
+                                       $desc .= $paramPrefix . "This parameter is required";
+                               }
+
+                               $type = isset( $paramSettings[self::PARAM_TYPE] )
+                                       ? $paramSettings[self::PARAM_TYPE]
+                                       : null;
+                               if ( isset( $type ) ) {
+                                       $hintPipeSeparated = true;
+                                       $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
+                                               ? $paramSettings[self::PARAM_ISMULTI]
+                                               : false;
+                                       if ( $multi ) {
+                                               $prompt = 'Values (separate with \'|\'): ';
+                                       } else {
+                                               $prompt = 'One value: ';
+                                       }
+
+                                       if ( $type === 'submodule' ) {
+                                               $type = $this->getModuleManager()->getNames( $paramName );
+                                               sort( $type );
+                                       }
+                                       if ( is_array( $type ) ) {
+                                               $choices = array();
+                                               $nothingPrompt = '';
+                                               foreach ( $type as $t ) {
+                                                       if ( $t === '' ) {
+                                                               $nothingPrompt = 'Can be empty, or ';
+                                                       } else {
+                                                               $choices[] = $t;
+                                                       }
+                                               }
+                                               $desc .= $paramPrefix . $nothingPrompt . $prompt;
+                                               $choicesstring = implode( ', ', $choices );
+                                               $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
+                                               $hintPipeSeparated = false;
+                                       } else {
+                                               switch ( $type ) {
+                                                       case 'namespace':
+                                                               // Special handling because namespaces are
+                                                               // type-limited, yet they are not given
+                                                               $desc .= $paramPrefix . $prompt;
+                                                               $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
+                                                                       100, $descWordwrap );
+                                                               $hintPipeSeparated = false;
+                                                               break;
+                                                       case 'limit':
+                                                               $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}";
+                                                               if ( isset( $paramSettings[self::PARAM_MAX2] ) ) {
+                                                                       $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)";
+                                                               }
+                                                               $desc .= ' allowed';
+                                                               break;
+                                                       case 'integer':
+                                                               $s = $multi ? 's' : '';
+                                                               $hasMin = isset( $paramSettings[self::PARAM_MIN] );
+                                                               $hasMax = isset( $paramSettings[self::PARAM_MAX] );
+                                                               if ( $hasMin || $hasMax ) {
+                                                                       if ( !$hasMax ) {
+                                                                               $intRangeStr = "The value$s must be no less than " .
+                                                                                       "{$paramSettings[self::PARAM_MIN]}";
+                                                                       } elseif ( !$hasMin ) {
+                                                                               $intRangeStr = "The value$s must be no more than " .
+                                                                                       "{$paramSettings[self::PARAM_MAX]}";
+                                                                       } else {
+                                                                               $intRangeStr = "The value$s must be between " .
+                                                                                       "{$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}";
+                                                                       }
+
+                                                                       $desc .= $paramPrefix . $intRangeStr;
+                                                               }
+                                                               break;
+                                                       case 'upload':
+                                                               $desc .= $paramPrefix . "Must be posted as a file upload using multipart/form-data";
+                                                               break;
+                                               }
+                                       }
+
+                                       if ( $multi ) {
+                                               if ( $hintPipeSeparated ) {
+                                                       $desc .= $paramPrefix . "Separate values with '|'";
+                                               }
+
+                                               $isArray = is_array( $type );
+                                               if ( !$isArray
+                                                       || $isArray && count( $type ) > self::LIMIT_SML1
+                                               ) {
+                                                       $desc .= $paramPrefix . "Maximum number of values " .
+                                                               self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
+                                               }
+                                       }
+                               }
+
+                               $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null;
+                               if ( !is_null( $default ) && $default !== false ) {
+                                       $desc .= $paramPrefix . "Default: $default";
+                               }
+
+                               $msg .= sprintf( "  %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
+                       }
+
+                       return $msg;
+               }
+
+               return false;
+       }
+
        /**@}*/
 }