*/
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 = '*';
public function extractRequestParams( $parseLimit = true ) {
// Cache parameters, for performance and to avoid T26564.
if ( !isset( $this->mParamCache[$parseLimit] ) ) {
- $params = $this->getFinalParams();
+ $params = $this->getFinalParams() ?: [];
$results = [];
-
- if ( $params ) { // getFinalParams() can return false
- foreach ( $params as $paramName => $paramSettings ) {
+ $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 {
$results[$paramName] = $this->getParameterFromSettings(
- $paramName, $paramSettings, $parseLimit );
+ $paramName, $paramSettings, $parseLimit
+ );
+ }
+ }
+
+ // 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;
+ }
+
+ $newName = str_replace( $placeholder, $value, $name );
+ if ( !$targets ) {
+ $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
+ } else {
+ $newTargets = [];
+ foreach ( $targets as $k => $v ) {
+ $newTargets[$k] = str_replace( $placeholder, $value, $v );
+ }
+ $toProcess[] = [ $newName, $newTargets, $settings ];
+ }
+ }
+ break;
}
}
+
$this->mParamCache[$parseLimit] = $results;
}
* @return mixed Parameter value
*/
protected function getParameter( $paramName, $parseLimit = true ) {
- $paramSettings = $this->getFinalParams()[$paramName];
-
- return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
+ return $this->extractRequestParams( $parseLimit )[$paramName];
}
/**