From 63601400e476c6cf43d985f3e7b9864681695ed4 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 18 Jan 2013 16:46:04 +0100 Subject: Update to MediaWiki 1.20.2 this update includes: * adjusted Arch Linux skin * updated FluxBBAuthPlugin * patch for https://bugzilla.wikimedia.org/show_bug.cgi?id=44024 --- includes/api/ApiBase.php | 224 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 175 insertions(+), 49 deletions(-) (limited to 'includes/api/ApiBase.php') diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index a586f688..875a3814 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -4,7 +4,7 @@ * * Created on Sep 5, 2006 * - * Copyright © 2006, 2010 Yuri Astrakhan @gmail.com + * Copyright © 2006, 2010 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 @@ -51,9 +51,16 @@ abstract class ApiBase extends ContextSource { const PARAM_MIN = 5; // Lowest value allowed for a parameter. Only applies if TYPE='integer' const PARAM_ALLOW_DUPLICATES = 6; // Boolean, do we allow the same value to be set more than once when ISMULTI=true const PARAM_DEPRECATED = 7; // Boolean, is the parameter deprecated (will show a warning) + /// @since 1.17 const PARAM_REQUIRED = 8; // Boolean, is the parameter required? + /// @since 1.17 const PARAM_RANGE_ENFORCE = 9; // Boolean, if MIN/MAX are set, enforce (die) these? Only applies if TYPE='integer' Use with extreme caution + const PROP_ROOT = 'ROOT'; // Name of property group that is on the root element of the result, i.e. not part of a list + const PROP_LIST = 'LIST'; // Boolean, is the result multiple items? Defaults to true for query modules, to false for other modules + const PROP_TYPE = 0; // Type of the property, uses same format as PARAM_TYPE + const PROP_NULLABLE = 1; // Boolean, can the property be not included in the result? Defaults to false + const LIMIT_BIG1 = 500; // Fast query, std user limit const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit const LIMIT_SML1 = 50; // Slow query, std user limit @@ -127,7 +134,7 @@ abstract class ApiBase extends ContextSource { /** * Get the name of the module as shown in the profiler log * - * @param $db DatabaseBase + * @param $db DatabaseBase|bool * * @return string */ @@ -280,12 +287,12 @@ abstract class ApiBase extends ContextSource { if ( is_numeric( $k ) ) { $msg .= " $v\n"; } else { - $v .= ":"; 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"; } } @@ -365,27 +372,38 @@ abstract class ApiBase extends ContextSource { $desc = implode( $paramPrefix, $desc ); } + //handle shorthand if ( !is_array( $paramSettings ) ) { $paramSettings = array( self::PARAM_DFLT => $paramSettings, ); } - $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ? - $paramSettings[self::PARAM_DEPRECATED] : false; - if ( $deprecated ) { + //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"; } - $required = isset( $paramSettings[self::PARAM_REQUIRED] ) ? - $paramSettings[self::PARAM_REQUIRED] : false; - if ( $required ) { + 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 ) ) { - if ( isset( $paramSettings[self::PARAM_ISMULTI] ) && $paramSettings[self::PARAM_ISMULTI] ) { + $hintPipeSeparated = true; + $multi = isset( $paramSettings[self::PARAM_ISMULTI] ) ? $paramSettings[self::PARAM_ISMULTI] : false; + if ( $multi ) { $prompt = 'Values (separate with \'|\'): '; } else { $prompt = 'One value: '; @@ -393,7 +411,7 @@ abstract class ApiBase extends ContextSource { if ( is_array( $type ) ) { $choices = array(); - $nothingPrompt = false; + $nothingPrompt = ''; foreach ( $type as $t ) { if ( $t === '' ) { $nothingPrompt = 'Can be empty, or '; @@ -404,6 +422,7 @@ abstract class ApiBase extends ContextSource { $desc .= $paramPrefix . $nothingPrompt . $prompt; $choicesstring = implode( ', ', $choices ); $desc .= wordwrap( $choicesstring, 100, $descWordwrap ); + $hintPipeSeparated = false; } else { switch ( $type ) { case 'namespace': @@ -411,6 +430,7 @@ abstract class ApiBase extends ContextSource { $desc .= $paramPrefix . $prompt; $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ), 100, $descWordwrap ); + $hintPipeSeparated = false; break; case 'limit': $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}"; @@ -420,37 +440,39 @@ abstract class ApiBase extends ContextSource { $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 must be no less than {$paramSettings[self::PARAM_MIN]}"; + $intRangeStr = "The value$s must be no less than {$paramSettings[self::PARAM_MIN]}"; } elseif ( !$hasMin ) { - $intRangeStr = "The value must be no more than {$paramSettings[self::PARAM_MAX]}"; + $intRangeStr = "The value$s must be no more than {$paramSettings[self::PARAM_MAX]}"; } else { - $intRangeStr = "The value must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}"; + $intRangeStr = "The value$s must be between {$paramSettings[self::PARAM_MIN]} and {$paramSettings[self::PARAM_MAX]}"; } $desc .= $paramPrefix . $intRangeStr; } break; } + } - if ( isset( $paramSettings[self::PARAM_ISMULTI] ) ) { - $isArray = is_array( $paramSettings[self::PARAM_TYPE] ); + if ( $multi ) { + if ( $hintPipeSeparated ) { + $desc .= $paramPrefix . "Separate values with '|'"; + } - if ( !$isArray - || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1 ) { - $desc .= $paramPrefix . "Maximum number of values " . - self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)"; - } + $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 = is_array( $paramSettings ) - ? ( isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null ) - : $paramSettings; + $default = isset( $paramSettings[self::PARAM_DFLT] ) ? $paramSettings[self::PARAM_DFLT] : null; if ( !is_null( $default ) && $default !== false ) { $desc .= $paramPrefix . "Default: $default"; } @@ -512,7 +534,7 @@ abstract class ApiBase extends ContextSource { /** * Returns usage examples for this module. Return false if no examples are available. - * @return false|string|array + * @return bool|string|array */ protected function getExamples() { return false; @@ -523,7 +545,7 @@ 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. - * @return array or false + * @return array|bool */ protected function getAllowedParams() { return false; @@ -533,7 +555,7 @@ abstract class ApiBase extends ContextSource { * Returns an array of parameter descriptions. * Don't call this functon directly: use getFinalParamDescription() to * allow hooks to modify descriptions as needed. - * @return array or false + * @return array|bool False on no parameter descriptions */ protected function getParamDescription() { return false; @@ -543,7 +565,7 @@ abstract class ApiBase extends ContextSource { * Get final list of parameters, after hooks have had a chance to * tweak it as needed. * - * @return array or false + * @return array|Bool False on no parameters */ public function getFinalParams() { $params = $this->getAllowedParams(); @@ -555,7 +577,7 @@ abstract class ApiBase extends ContextSource { * Get final parameter descriptions, after hooks have had a chance to tweak it as * needed. * - * @return array + * @return array|bool False on no parameter descriptions */ public function getFinalParamDescription() { $desc = $this->getParamDescription(); @@ -564,11 +586,56 @@ abstract class ApiBase extends ContextSource { } /** - * Get final module description, after hooks have had a chance to tweak it as + * Returns possible properties in the result, grouped by the value of the prop parameter + * that shows them. + * + * Properties that are shown always are in a group with empty string as a key. + * Properties that can be shown by several values of prop are included multiple times. + * If some properties are part of a list and some are on the root object (see ApiQueryQueryPage), + * those on the root object are under the key PROP_ROOT. + * The array can also contain a boolean under the key PROP_LIST, + * indicating whether the result is a list. + * + * Don't call this functon directly: use getFinalResultProperties() to + * allow hooks to modify descriptions as needed. + * + * @return array|bool False on no properties + */ + protected function getResultProperties() { + return false; + } + + /** + * Get final possible result properties, after hooks have had a chance to tweak it as * needed. * * @return array */ + public function getFinalResultProperties() { + $properties = $this->getResultProperties(); + wfRunHooks( 'APIGetResultProperties', array( $this, &$properties ) ); + return $properties; + } + + /** + * Add token properties to the array used by getResultProperties, + * based on a token functions mapping. + */ + protected static function addTokenProperties( &$props, $tokenFunctions ) { + foreach ( array_keys( $tokenFunctions ) as $token ) { + $props[''][$token . 'token'] = array( + ApiBase::PROP_TYPE => 'string', + ApiBase::PROP_NULLABLE => true + ); + } + } + + /** + * 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 ) ); @@ -630,14 +697,15 @@ abstract class ApiBase extends ContextSource { public function requireOnlyOneParameter( $params ) { $required = func_get_args(); array_shift( $required ); + $p = $this->getModulePrefix(); $intersection = array_intersect( array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ), $required ); if ( count( $intersection ) > 1 ) { - $this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' ); + $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" ); } elseif ( count( $intersection ) == 0 ) { - $this->dieUsage( 'One of the parameters ' . implode( ', ', $required ) . ' is required', 'missingparam' ); + $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" ); } } @@ -665,12 +733,13 @@ abstract class ApiBase extends ContextSource { public function requireMaxOneParameter( $params ) { $required = func_get_args(); array_shift( $required ); + $p = $this->getModulePrefix(); $intersection = array_intersect( array_keys( array_filter( $params, array( $this, "parameterNotEmpty" ) ) ), $required ); if ( count( $intersection ) > 1 ) { - $this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' ); + $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" ); } } @@ -689,6 +758,53 @@ abstract class ApiBase extends ContextSource { ); } + /** + * @param $params array + * @param $load bool|string Whether load the object's state from the database: + * - false: don't load (if the pageid is given, it will still be loaded) + * - 'fromdb': load from a slave database + * - 'fromdbmaster': load from the master database + * @return WikiPage + */ + public function getTitleOrPageId( $params, $load = false ) { + $this->requireOnlyOneParameter( $params, 'title', 'pageid' ); + + $pageObj = null; + if ( isset( $params['title'] ) ) { + $titleObj = Title::newFromText( $params['title'] ); + if ( !$titleObj ) { + $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); + } + $pageObj = WikiPage::factory( $titleObj ); + if ( $load !== false ) { + $pageObj->loadPageData( $load ); + } + } elseif ( isset( $params['pageid'] ) ) { + if ( $load === false ) { + $load = 'fromdb'; + } + $pageObj = WikiPage::newFromID( $params['pageid'], $load ); + if ( !$pageObj ) { + $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) ); + } + } + + return $pageObj; + } + + /** + * @return array + */ + public function getTitleOrPageIdErrorMessage() { + return array_merge( + $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ), + array( + array( 'invalidtitle', 'title' ), + array( 'nosuchpageid', 'pageid' ), + ) + ); + } + /** * Callback function used in requireOnlyOneParameter to check whether reequired parameters are set * @@ -719,7 +835,7 @@ abstract class ApiBase extends ContextSource { */ protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) { - $userWatching = $titleObj->userIsWatching(); + $userWatching = $this->getUser()->isWatched( $titleObj ); switch ( $watchlist ) { case 'watch': @@ -773,7 +889,7 @@ abstract class ApiBase extends ContextSource { * Using the settings determine the value for the given parameter * * @param $paramName String: parameter name - * @param $paramSettings Mixed: default value or an array of settings + * @param $paramSettings array|mixed default value or an array of settings * using PARAM_* constants. * @param $parseLimit Boolean: parse limit? * @return mixed Parameter value @@ -809,8 +925,8 @@ abstract class ApiBase extends ContextSource { if ( $type == 'boolean' ) { if ( isset( $default ) && $default !== false ) { - // Having a default value of anything other than 'false' is pointless - ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'" ); + // Having a default value of anything other than 'false' is not allowed + ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'. Boolean parameters must default to false." ); } $value = $this->getRequest()->getCheck( $encParamName ); @@ -1078,7 +1194,8 @@ abstract class ApiBase extends ContextSource { * @param $errorCode string Brief, arbitrary, stable string to allow easy * automated identification of the error, e.g., 'unknown_action' * @param $httpRespCode int HTTP response code - * @param $extradata array Data to add to the element; array in ApiResult format + * @param $extradata array Data to add to the "" element; array in ApiResult format + * @throws UsageException */ public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) { Profiler::instance()->close(); @@ -1155,6 +1272,8 @@ abstract class ApiBase extends ContextSource { 'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ), 'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ), 'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ), + 'import-rootpage-invalid' => array( 'code' => 'import-rootpage-invalid', 'info' => 'Root page is an invalid title' ), + 'import-rootpage-nosubpage' => array( 'code' => 'import-rootpage-nosubpage', 'info' => 'Namespace "$1" of the root page does not allow subpages' ), // API-specific messages 'readrequired' => array( 'code' => 'readapidenied', 'info' => "You need read permission to use this module" ), @@ -1186,7 +1305,6 @@ abstract class ApiBase extends ContextSource { 'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ), 'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ), 'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ), - 'nouploadmodule' => array( 'code' => 'nomodule', 'info' => 'No upload module set' ), 'importnofile' => array( 'code' => 'nofile', 'info' => "You didn't upload a file" ), 'importuploaderrorsize' => array( 'code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size' ), 'importuploaderrorpartial' => array( 'code' => 'partialupload', 'info' => 'The file was only partially uploaded' ), @@ -1202,12 +1320,14 @@ abstract class ApiBase extends ContextSource { 'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ), 'invalidoldimage' => array( 'code' => 'invalidoldimage', 'info' => 'The oldimage parameter has invalid format' ), 'nodeleteablefile' => array( 'code' => 'nodeleteablefile', 'info' => 'No such old version of the file' ), + 'fileexists-forbidden' => array( 'code' => 'fileexists-forbidden', 'info' => 'A file with name "$1" already exists, and cannot be overwritten.' ), + 'fileexists-shared-forbidden' => array( 'code' => 'fileexists-shared-forbidden', 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.' ), + 'filerevert-badversion' => array( 'code' => 'filerevert-badversion', 'info' => 'There is no previous local version of this file with the provided timestamp.' ), // ApiEditPage messages 'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ), 'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ), 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ), - 'filtered' => array( 'code' => 'filtered', 'info' => "The filter callback function refused your edit" ), 'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ), 'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ), 'noedit' => array( 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ), @@ -1227,10 +1347,11 @@ abstract class ApiBase extends ContextSource { 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ), // uploadMsgs - 'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ), + 'invalid-file-key' => array( 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ), 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ), 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ), 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ), + 'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ), 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ), 'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ), @@ -1280,10 +1401,9 @@ abstract class ApiBase extends ContextSource { } if ( isset( self::$messageMap[$key] ) ) { - return array( 'code' => - wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ), - 'info' => - wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error ) + return array( + 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ), + 'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error ) ); } @@ -1332,7 +1452,9 @@ abstract class ApiBase extends ContextSource { } /** - * Returns whether this module requires a Token to execute + * Returns whether this module requires a token to execute + * It is used to show possible errors in action=paraminfo + * see bug 25248 * @return bool */ public function needsToken() { @@ -1340,8 +1462,12 @@ abstract class ApiBase extends ContextSource { } /** - * Returns the token salt if there is one, '' if the module doesn't require a salt, else false if the module doesn't need a token - * @return bool|string + * Returns the token salt if there is one, + * '' if the module doesn't require a salt, + * else false if the module doesn't need a token + * You have also to override needsToken() + * Value is passed to User::getEditToken + * @return bool|string|array */ public function getTokenSalt() { return false; @@ -1373,7 +1499,7 @@ abstract class ApiBase extends ContextSource { } /** - * @return false|string|array Returns a false if the module has no help url, else returns a (array of) string + * @return bool|string|array Returns a false if the module has no help url, else returns a (array of) string */ public function getHelpUrls() { return false; -- cgit v1.2.3-54-g00ecf