API: Introduce "templated parameters"
[lhc/web/wiklou.git] / includes / api / ApiBase.php
index 7fafa1f..0802e16 100644 (file)
@@ -226,6 +226,24 @@ abstract class ApiBase extends ContextSource {
         */
        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 = '*';
@@ -749,15 +767,78 @@ abstract class ApiBase extends ContextSource {
        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;
                }
 
@@ -771,9 +852,7 @@ abstract class ApiBase extends ContextSource {
         * @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];
        }
 
        /**