API: Add ApiResult::META_KVP_MERGE
[lhc/web/wiklou.git] / includes / api / ApiResult.php
index 044b6e0..2675185 100644 (file)
@@ -108,12 +108,24 @@ class ApiResult implements ApiSerializable {
        const META_TYPE = '_type';
 
        /**
-        * Key (rather than "name" or other default) for when META_TYPE is 'kvp' or
-        * 'BCkvp'. Value is string.
+        * Key for the metatata item whose value specifies the name used for the
+        * kvp key in the alternative output format with META_TYPE 'kvp' or
+        * 'BCkvp', i.e. the "name" in <container><item name="key">value</item></container>.
+        * Value is string.
         * @since 1.25
         */
        const META_KVP_KEY_NAME = '_kvpkeyname';
 
+       /**
+        * Key for the metadata item that indicates that the KVP key should be
+        * added into an assoc value, i.e. {"key":{"val1":"a","val2":"b"}}
+        * transforms to {"name":"key","val1":"a","val2":"b"} rather than
+        * {"name":"key","value":{"val1":"a","val2":"b"}}.
+        * Value is boolean.
+        * @since 1.26
+        */
+       const META_KVP_MERGE = '_kvpmerge';
+
        /**
         * Key for the 'BC bools' metadata item. Value is string[].
         * Note no setter is provided.
@@ -302,10 +314,14 @@ class ApiResult implements ApiSerializable {
                                $arr[$name] += $value;
                        } else {
                                $keys = join( ', ', array_keys( $conflicts ) );
-                               throw new RuntimeException( "Conflicting keys ($keys) when attempting to merge element $name" );
+                               throw new RuntimeException(
+                                       "Conflicting keys ($keys) when attempting to merge element $name"
+                               );
                        }
                } else {
-                       throw new RuntimeException( "Attempting to add element $name=$value, existing value is {$arr[$name]}" );
+                       throw new RuntimeException(
+                               "Attempting to add element $name=$value, existing value is {$arr[$name]}"
+                       );
                }
        }
 
@@ -703,7 +719,9 @@ class ApiResult implements ApiSerializable {
         * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
         */
        public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
-               if ( !in_array( $type, array( 'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp' ), true ) ) {
+               if ( !in_array( $type, array(
+                               'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
+                               ), true ) ) {
                        throw new InvalidArgumentException( 'Bad type' );
                }
                $arr[self::META_TYPE] = $type;
@@ -832,11 +850,13 @@ class ApiResult implements ApiSerializable {
                                isset( $metadata[self::META_BC_SUBELEMENTS] )
                        ) {
                                foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
-                                       $data[$k] = array(
-                                               '*' => $data[$k],
-                                               self::META_CONTENT => '*',
-                                               self::META_TYPE => 'assoc',
-                                       );
+                                       if ( isset( $data[$k] ) ) {
+                                               $data[$k] = array(
+                                                       '*' => $data[$k],
+                                                       self::META_CONTENT => '*',
+                                                       self::META_TYPE => 'assoc',
+                                               );
+                                       }
                                }
                        }
 
@@ -933,19 +953,43 @@ class ApiResult implements ApiSerializable {
                                                : $transformTypes['ArmorKVP'];
                                        $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
                                        $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
+                                       $merge = !empty( $metadata[self::META_KVP_MERGE] );
 
                                        $ret = array();
                                        foreach ( $data as $k => $v ) {
-                                               $item = array(
-                                                       $key => $k,
-                                                       $valKey => $v,
-                                               );
-                                               if ( $strip === 'none' ) {
-                                                       $item += array(
-                                                               self::META_PRESERVE_KEYS => array( $key ),
-                                                               self::META_CONTENT => $valKey,
-                                                               self::META_TYPE => 'assoc',
+                                               if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
+                                                       $vArr = (array)$v;
+                                                       if ( isset( $vArr[self::META_TYPE] ) ) {
+                                                               $mergeType = $vArr[self::META_TYPE];
+                                                       } elseif ( is_object( $v ) ) {
+                                                               $mergeType = 'assoc';
+                                                       } else {
+                                                               $keys = array_keys( $vArr );
+                                                               sort( $keys, SORT_NUMERIC );
+                                                               $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
+                                                       }
+                                               } else {
+                                                       $mergeType = 'n/a';
+                                               }
+                                               if ( $mergeType === 'assoc' ) {
+                                                       $item = $vArr + array(
+                                                               $key => $k,
                                                        );
+                                                       if ( $strip === 'none' ) {
+                                                               self::setPreserveKeysList( $item, array( $key ) );
+                                                       }
+                                               } else {
+                                                       $item = array(
+                                                               $key => $k,
+                                                               $valKey => $v,
+                                                       );
+                                                       if ( $strip === 'none' ) {
+                                                               $item += array(
+                                                                       self::META_PRESERVE_KEYS => array( $key ),
+                                                                       self::META_CONTENT => $valKey,
+                                                                       self::META_TYPE => 'assoc',
+                                                               );
+                                                       }
                                                }
                                                $ret[] = $assocAsObject ? (object)$item : $item;
                                        }
@@ -1033,7 +1077,8 @@ class ApiResult implements ApiSerializable {
        /**
         * Get the 'real' size of a result item. This means the strlen() of the item,
         * or the sum of the strlen()s of the elements if the item is an array.
-        * @note Once the deprecated public self::size is removed, we can rename this back to a less awkward name.
+        * @note Once the deprecated public self::size is removed, we can rename
+        *       this back to a less awkward name.
         * @param mixed $value
         * @return int
         */
@@ -1094,6 +1139,61 @@ class ApiResult implements ApiSerializable {
                return $ret;
        }
 
+       /**
+        * Add the correct metadata to an array of vars we want to export through
+        * the API.
+        *
+        * @param array $vars
+        * @param boolean $forceHash
+        * @return array
+        */
+       public static function addMetadataToResultVars( $vars, $forceHash = true ) {
+               // Process subarrays and determine if this is a JS [] or {}
+               $hash = $forceHash;
+               $maxKey = -1;
+               $bools = array();
+               foreach ( $vars as $k => $v ) {
+                       if ( is_array( $v ) || is_object( $v ) ) {
+                               $vars[$k] = ApiResult::addMetadataToResultVars( (array)$v, is_object( $v ) );
+                       } elseif ( is_bool( $v ) ) {
+                               // Better here to use real bools even in BC formats
+                               $bools[] = $k;
+                       }
+                       if ( is_string( $k ) ) {
+                               $hash = true;
+                       } elseif ( $k > $maxKey ) {
+                               $maxKey = $k;
+                       }
+               }
+               if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
+                       $hash = true;
+               }
+
+               // Set metadata appropriately
+               if ( $hash ) {
+                       // Get the list of keys we actually care about. Unfortunately, we can't support
+                       // certain keys that conflict with ApiResult metadata.
+                       $keys = array_diff( array_keys( $vars ), array(
+                               ApiResult::META_TYPE, ApiResult::META_PRESERVE_KEYS, ApiResult::META_KVP_KEY_NAME,
+                               ApiResult::META_INDEXED_TAG_NAME, ApiResult::META_BC_BOOLS
+                       ) );
+
+                       return array(
+                               ApiResult::META_TYPE => 'kvp',
+                               ApiResult::META_KVP_KEY_NAME => 'key',
+                               ApiResult::META_PRESERVE_KEYS => $keys,
+                               ApiResult::META_BC_BOOLS => $bools,
+                               ApiResult::META_INDEXED_TAG_NAME => 'var',
+                       ) + $vars;
+               } else {
+                       return array(
+                               ApiResult::META_TYPE => 'array',
+                               ApiResult::META_BC_BOOLS => $bools,
+                               ApiResult::META_INDEXED_TAG_NAME => 'value',
+                       ) + $vars;
+               }
+       }
+
        /**@}*/
 
        /************************************************************************//**