*/
const PARAM_MAX_CHARS = 24;
+ /**
+ * (array) Indicate that this is a templated parameter, and specify replacements. Keys are the
+ * placeholders in the parameter name and values are the names of (unprefixed) parameters from
+ * which the replacement values are taken.
+ *
+ * For example, a parameter "foo-{ns}-{title}" could be defined with
+ * PARAM_TEMPLATE_VARS => [ 'ns' => 'namespaces', 'title' => 'titles' ]. Then a query for
+ * namespaces=0|1&titles=X|Y would support parameters foo-0-X, foo-0-Y, foo-1-X, and foo-1-Y.
+ *
+ * All placeholders must be present in the parameter's name. Each target parameter must have
+ * PARAM_ISMULTI true. If a target is itself a templated parameter, its PARAM_TEMPLATE_VARS must
+ * be a subset of the referring parameter's, mapping the same placeholders to the same targets.
+ * A parameter cannot target itself.
+ *
+ * @since 1.32
+ */
+ const PARAM_TEMPLATE_VARS = 25;
+
/**@}*/
const ALL_DEFAULT_STRING = '*';
* Set the continuation manager
* @param ApiContinuationManager|null $manager
*/
- public function setContinuationManager( $manager ) {
+ public function setContinuationManager( ApiContinuationManager $manager = null ) {
// Main module has setContinuationManager() method overridden
// Safety - avoid infinite loop:
if ( $this->isMain() ) {
* value - validated value from user or default. limits will not be
* parsed if $parseLimit is set to false; use this when the max
* limit is not definitive yet, e.g. when getting revisions.
- * @param bool $parseLimit True by default
+ * @param bool|array $options If a boolean, uses that as the value for 'parseLimit'
+ * - parseLimit: (bool, default true) Whether to parse the 'max' value for limit types
+ * - safeMode: (bool, default false) If true, avoid throwing for parameter validation errors.
+ * Returned parameter values might be ApiUsageException instances.
* @return array
*/
- public function extractRequestParams( $parseLimit = true ) {
+ public function extractRequestParams( $options = [] ) {
+ if ( is_bool( $options ) ) {
+ $options = [ 'parseLimit' => $options ];
+ }
+ $options += [
+ 'parseLimit' => true,
+ 'safeMode' => false,
+ ];
+
+ $parseLimit = (bool)$options['parseLimit'];
+
// Cache parameters, for performance and to avoid T26564.
if ( !isset( $this->mParamCache[$parseLimit] ) ) {
- $params = $this->getFinalParams();
+ $params = $this->getFinalParams() ?: [];
$results = [];
+ $warned = [];
+
+ // Process all non-templates and save templates for secondary
+ // processing.
+ $toProcess = [];
+ foreach ( $params as $paramName => $paramSettings ) {
+ if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
+ $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
+ } else {
+ try {
+ $results[$paramName] = $this->getParameterFromSettings(
+ $paramName, $paramSettings, $parseLimit
+ );
+ } catch ( ApiUsageException $ex ) {
+ $results[$paramName] = $ex;
+ }
+ }
+ }
+
+ // Now process all the templates by successively replacing the
+ // placeholders with all client-supplied values.
+ // This bit duplicates JavaScript logic in
+ // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
+ // If you update this, see if that needs updating too.
+ while ( $toProcess ) {
+ list( $name, $targets, $settings ) = array_shift( $toProcess );
+
+ foreach ( $targets as $placeholder => $target ) {
+ if ( !array_key_exists( $target, $results ) ) {
+ // The target wasn't processed yet, try the next one.
+ // If all hit this case, the parameter has no expansions.
+ continue;
+ }
+ if ( !is_array( $results[$target] ) || !$results[$target] ) {
+ // The target was processed but has no (valid) values.
+ // That means it has no expansions.
+ break;
+ }
+
+ // Expand this target in the name and all other targets,
+ // then requeue if there are more targets left or put in
+ // $results if all are done.
+ unset( $targets[$placeholder] );
+ $placeholder = '{' . $placeholder . '}';
+ foreach ( $results[$target] as $value ) {
+ if ( !preg_match( '/^[^{}]*$/', $value ) ) {
+ // Skip values that make invalid parameter names.
+ $encTargetName = $this->encodeParamName( $target );
+ if ( !isset( $warned[$encTargetName][$value] ) ) {
+ $warned[$encTargetName][$value] = true;
+ $this->addWarning( [
+ 'apiwarn-ignoring-invalid-templated-value',
+ wfEscapeWikiText( $encTargetName ),
+ wfEscapeWikiText( $value ),
+ ] );
+ }
+ continue;
+ }
- if ( $params ) { // getFinalParams() can return false
- foreach ( $params as $paramName => $paramSettings ) {
- $results[$paramName] = $this->getParameterFromSettings(
- $paramName, $paramSettings, $parseLimit );
+ $newName = str_replace( $placeholder, $value, $name );
+ if ( !$targets ) {
+ try {
+ $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
+ } catch ( ApiUsageException $ex ) {
+ $results[$newName] = $ex;
+ }
+ } else {
+ $newTargets = [];
+ foreach ( $targets as $k => $v ) {
+ $newTargets[$k] = str_replace( $placeholder, $value, $v );
+ }
+ $toProcess[] = [ $newName, $newTargets, $settings ];
+ }
+ }
+ break;
}
}
+
$this->mParamCache[$parseLimit] = $results;
}
+ $ret = $this->mParamCache[$parseLimit];
+ if ( !$options['safeMode'] ) {
+ foreach ( $ret as $v ) {
+ if ( $v instanceof ApiUsageException ) {
+ throw $v;
+ }
+ }
+ }
+
return $this->mParamCache[$parseLimit];
}
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
- $paramSettings = $this->getFinalParams()[$paramName];
-
- return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
+ $ret = $this->extractRequestParams( [
+ 'parseLimit' => $parseLimit,
+ 'safeMode' => true,
+ ] )[$paramName];
+ if ( $ret instanceof ApiUsageException ) {
+ throw $ret;
+ }
+ return $ret;
}
/**
];
}
- $default = isset( $paramSettings[self::PARAM_DFLT] )
- ? $paramSettings[self::PARAM_DFLT]
- : null;
- $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
- ? $paramSettings[self::PARAM_ISMULTI]
- : false;
- $multiLimit1 = isset( $paramSettings[self::PARAM_ISMULTI_LIMIT1] )
- ? $paramSettings[self::PARAM_ISMULTI_LIMIT1]
- : null;
- $multiLimit2 = isset( $paramSettings[self::PARAM_ISMULTI_LIMIT2] )
- ? $paramSettings[self::PARAM_ISMULTI_LIMIT2]
- : null;
- $type = isset( $paramSettings[self::PARAM_TYPE] )
- ? $paramSettings[self::PARAM_TYPE]
- : null;
- $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
- ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
- : false;
- $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
- ? $paramSettings[self::PARAM_DEPRECATED]
- : false;
- $deprecatedValues = isset( $paramSettings[self::PARAM_DEPRECATED_VALUES] )
- ? $paramSettings[self::PARAM_DEPRECATED_VALUES]
- : [];
- $required = isset( $paramSettings[self::PARAM_REQUIRED] )
- ? $paramSettings[self::PARAM_REQUIRED]
- : false;
- $allowAll = isset( $paramSettings[self::PARAM_ALL] )
- ? $paramSettings[self::PARAM_ALL]
- : false;
+ $default = $paramSettings[self::PARAM_DFLT] ?? null;
+ $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
+ $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
+ $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
+ $type = $paramSettings[self::PARAM_TYPE] ?? null;
+ $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
+ $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
+ $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
+ $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
+ $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
// When type is not given, and no choices, the type is the same as $default
if ( !isset( $type ) ) {
) {
$type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
}
- // By default, namespace parameters allow ALL_DEFAULT_STRING to be used to specify
- // all namespaces.
+ // Namespace parameters allow ALL_DEFAULT_STRING to be used to
+ // specify all namespaces irrespective of PARAM_ALL.
$allowAll = true;
}
if ( isset( $value ) && $type == 'submodule' ) {
}
break;
case 'integer': // Force everything using intval() and optionally validate limits
- $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
- $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
- $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
- ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
+ $min = $paramSettings[self::PARAM_MIN] ?? null;
+ $max = $paramSettings[self::PARAM_MAX] ?? null;
+ $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
if ( is_array( $value ) ) {
$value = array_map( 'intval', $value );
if ( $multi ) {
self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
}
- $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
+ $min = $paramSettings[self::PARAM_MIN] ?? 0;
if ( $value == 'max' ) {
$value = $this->getMain()->canApiHighLimits()
? $paramSettings[self::PARAM_MAX2]
return $allowedValues;
}
- if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
- $this->addDeprecation(
- [ 'apiwarn-toomanyvalues', $valueName, $sizeLimit ],
- "too-many-$valueName-for-{$this->getModulePath()}"
+ if ( count( $valuesList ) > $sizeLimit ) {
+ $this->dieWithError(
+ [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
+ "too-many-$valueName"
);
}
return $value;
}
- if ( is_array( $allowedValues ) ) {
- $values = array_map( function ( $v ) {
- return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
- }, $allowedValues );
- $this->dieWithError( [
- 'apierror-multival-only-one-of',
- $valueName,
- Message::listParam( $values ),
- count( $values ),
- ], "multival_$valueName" );
- } else {
- $this->dieWithError( [
- 'apierror-multival-only-one',
- $valueName,
- ], "multival_$valueName" );
- }
+ $values = array_map( function ( $v ) {
+ return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
+ }, $allowedValues );
+ $this->dieWithError( [
+ 'apierror-multival-only-one-of',
+ $valueName,
+ Message::listParam( $values ),
+ count( $values ),
+ ], "multival_$valueName" );
}
if ( is_array( $allowedValues ) ) {
}
/**
- * Validate and normalize of parameters of type 'timestamp'
+ * Validate and normalize parameters of type 'timestamp'
* @param string $value Parameter value
* @param string $encParamName Parameter name
* @return string Validated and normalized parameter
return wfTimestamp( TS_MW );
}
- $unixTimestamp = wfTimestamp( TS_UNIX, $value );
- if ( $unixTimestamp === false ) {
+ $timestamp = wfTimestamp( TS_MW, $value );
+ if ( $timestamp === false ) {
$this->dieWithError(
[ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
"badtimestamp_{$encParamName}"
);
}
- return wfTimestamp( TS_MW, $unixTimestamp );
+ return $timestamp;
}
/**
}
/**
- * Validate and normalize of parameters of type 'user'
+ * Validate and normalize parameters of type 'user'
* @param string $value Parameter value
* @param string $encParamName Parameter name
* @return string Validated and normalized parameter
return $value;
}
- $title = Title::makeTitleSafe( NS_USER, $value );
- if ( $title === null || $title->hasFragment() ) {
- $this->dieWithError(
- [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
- "baduser_{$encParamName}"
- );
+ $name = User::getCanonicalName( $value, 'valid' );
+ if ( $name !== false ) {
+ return $name;
+ }
+
+ if (
+ // We allow ranges as well, for blocks.
+ IP::isIPAddress( $value ) ||
+ // See comment for User::isIP. We don't just call that function
+ // here because it also returns true for things like
+ // 300.300.300.300 that are neither valid usernames nor valid IP
+ // addresses.
+ preg_match(
+ '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
+ $value
+ )
+ ) {
+ return IP::sanitizeIP( $value );
}
- return $title->getText();
+ $this->dieWithError(
+ [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
+ "baduser_{$encParamName}"
+ );
}
/**@}*/
WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
}
- /**
- * Truncate an array to a certain length.
- * @param array &$arr Array to truncate
- * @param int $limit Maximum length
- * @return bool True if the array was truncated, false otherwise
- */
- public static function truncateArray( &$arr, $limit ) {
- $modified = false;
- while ( count( $arr ) > $limit ) {
- array_pop( $arr );
- $modified = true;
- }
-
- return $modified;
- }
-
/**
* Gets the user for whom to get the watchlist
*
if ( is_string( $msg ) ) {
$msg = wfMessage( $msg );
} elseif ( is_array( $msg ) ) {
- $msg = call_user_func_array( 'wfMessage', $msg );
+ $msg = wfMessage( ...$msg );
}
if ( !$msg instanceof Message ) {
return null;
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
) );
} else {
- call_user_func_array( [ $status, 'fatal' ], (array)$error );
+ $status->fatal( ...(array)$error );
}
}
return $status;
if ( !$status->getErrorsByType( 'error' ) ) {
$newStatus = Status::newGood();
foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
- call_user_func_array(
- [ $newStatus, 'fatal' ],
- array_merge( [ $err['message'] ], $err['params'] )
- );
+ $newStatus->fatal( $err['message'], ...$err['params'] );
}
if ( !$newStatus->getErrorsByType( 'error' ) ) {
$newStatus->fatal( 'unknownerror-nocode' );
$user = $this->getUser();
}
$rights = (array)$rights;
- if ( !call_user_func_array( [ $user, 'isAllowedAny' ], $rights ) ) {
+ if ( !$user->isAllowedAny( ...$rights ) ) {
$this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
}
}
'api-help-param-token',
$this->needsToken(),
],
- ] + ( isset( $params['token'] ) ? $params['token'] : [] );
+ ] + ( $params['token'] ?? [] );
}
// Avoid PHP 7.1 warning of passing $this by reference
$settings = [];
}
- $d = isset( $desc[$param] ) ? $desc[$param] : '';
+ $d = $desc[$param] ?? '';
if ( is_array( $d ) ) {
// Special handling for prop parameters
$d = array_map( function ( $line ) {
}
$valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
- $deprecatedValues = isset( $settings[self::PARAM_DEPRECATED_VALUES] )
- ? $settings[self::PARAM_DEPRECATED_VALUES]
- : [];
+ $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
foreach ( $settings[self::PARAM_TYPE] as $value ) {
if ( isset( $valueMsgs[$value] ) ) {
return false;
}
- /**
- * @deprecated since 1.25, always returns empty string
- * @param IDatabase|bool $db
- * @return string
- */
- public function getModuleProfileName( $db = false ) {
- wfDeprecated( __METHOD__, '1.25' );
- return '';
- }
-
/**
* @deprecated since 1.25
*/
wfDeprecated( __METHOD__, '1.25' );
}
- /**
- * @deprecated since 1.25, always returns 0
- * @return float
- */
- public function getProfileTime() {
- wfDeprecated( __METHOD__, '1.25' );
- return 0;
- }
-
/**
* @deprecated since 1.25
*/
wfDeprecated( __METHOD__, '1.25' );
}
- /**
- * @deprecated since 1.25, always returns 0
- * @return float
- */
- public function getProfileDBTime() {
- wfDeprecated( __METHOD__, '1.25' );
- return 0;
- }
-
/**
* Call wfTransactionalTimeLimit() if this request was POSTed
* @since 1.26
if ( !$msg instanceof IApiMessage ) {
$key = $msg->getKey();
$params = $msg->getParams();
- array_unshift( $params, isset( self::$messageMap[$key] ) ? self::$messageMap[$key] : $key );
+ array_unshift( $params, self::$messageMap[$key] ?? $key );
$msg = ApiMessage::create( $params );
}
] ];
}
+ /**
+ * Truncate an array to a certain length.
+ * @deprecated since 1.32, no replacement
+ * @param array &$arr Array to truncate
+ * @param int $limit Maximum length
+ * @return bool True if the array was truncated, false otherwise
+ */
+ public static function truncateArray( &$arr, $limit ) {
+ wfDeprecated( __METHOD__, '1.32' );
+ $modified = false;
+ while ( count( $arr ) > $limit ) {
+ array_pop( $arr );
+ $modified = true;
+ }
+
+ return $modified;
+ }
+
/**@}*/
}