Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/api/ApiResult.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 */
20
21
/**
22
 * This class represents the result of the API operations.
23
 * It simply wraps a nested array structure, adding some functions to simplify
24
 * array's modifications. As various modules execute, they add different pieces
25
 * of information to this result, structuring it as it will be given to the client.
26
 *
27
 * Each subarray may either be a dictionary - key-value pairs with unique keys,
28
 * or lists, where the items are added using $data[] = $value notation.
29
 *
30
 * @since 1.25 this is no longer a subclass of ApiBase
31
 * @ingroup API
32
 */
33
class ApiResult implements ApiSerializable {
34
35
	/**
36
	 * Override existing value in addValue(), setValue(), and similar functions
37
	 * @since 1.21
38
	 */
39
	const OVERRIDE = 1;
40
41
	/**
42
	 * For addValue(), setValue() and similar functions, if the value does not
43
	 * exist, add it as the first element. In case the new value has no name
44
	 * (numerical index), all indexes will be renumbered.
45
	 * @since 1.21
46
	 */
47
	const ADD_ON_TOP = 2;
48
49
	/**
50
	 * For addValue() and similar functions, do not check size while adding a value
51
	 * Don't use this unless you REALLY know what you're doing.
52
	 * Values added while the size checking was disabled will never be counted.
53
	 * Ignored for setValue() and similar functions.
54
	 * @since 1.24
55
	 */
56
	const NO_SIZE_CHECK = 4;
57
58
	/**
59
	 * For addValue(), setValue() and similar functions, do not validate data.
60
	 * Also disables size checking. If you think you need to use this, you're
61
	 * probably wrong.
62
	 * @since 1.25
63
	 */
64
	const NO_VALIDATE = 12;
65
66
	/**
67
	 * Key for the 'indexed tag name' metadata item. Value is string.
68
	 * @since 1.25
69
	 */
70
	const META_INDEXED_TAG_NAME = '_element';
71
72
	/**
73
	 * Key for the 'subelements' metadata item. Value is string[].
74
	 * @since 1.25
75
	 */
76
	const META_SUBELEMENTS = '_subelements';
77
78
	/**
79
	 * Key for the 'preserve keys' metadata item. Value is string[].
80
	 * @since 1.25
81
	 */
82
	const META_PRESERVE_KEYS = '_preservekeys';
83
84
	/**
85
	 * Key for the 'content' metadata item. Value is string.
86
	 * @since 1.25
87
	 */
88
	const META_CONTENT = '_content';
89
90
	/**
91
	 * Key for the 'type' metadata item. Value is one of the following strings:
92
	 *  - default: Like 'array' if all (non-metadata) keys are numeric with no
93
	 *    gaps, otherwise like 'assoc'.
94
	 *  - array: Keys are used for ordering, but are not output. In a format
95
	 *    like JSON, outputs as [].
96
	 *  - assoc: In a format like JSON, outputs as {}.
97
	 *  - kvp: For a format like XML where object keys have a restricted
98
	 *    character set, use an alternative output format. For example,
99
	 *    <container><item name="key">value</item></container> rather than
100
	 *    <container key="value" />
101
	 *  - BCarray: Like 'array' normally, 'default' in backwards-compatibility mode.
102
	 *  - BCassoc: Like 'assoc' normally, 'default' in backwards-compatibility mode.
103
	 *  - BCkvp: Like 'kvp' normally. In backwards-compatibility mode, forces
104
	 *    the alternative output format for all formats, for example
105
	 *    [{"name":key,"*":value}] in JSON. META_KVP_KEY_NAME must also be set.
106
	 * @since 1.25
107
	 */
108
	const META_TYPE = '_type';
109
110
	/**
111
	 * Key for the metadata item whose value specifies the name used for the
112
	 * kvp key in the alternative output format with META_TYPE 'kvp' or
113
	 * 'BCkvp', i.e. the "name" in <container><item name="key">value</item></container>.
114
	 * Value is string.
115
	 * @since 1.25
116
	 */
117
	const META_KVP_KEY_NAME = '_kvpkeyname';
118
119
	/**
120
	 * Key for the metadata item that indicates that the KVP key should be
121
	 * added into an assoc value, i.e. {"key":{"val1":"a","val2":"b"}}
122
	 * transforms to {"name":"key","val1":"a","val2":"b"} rather than
123
	 * {"name":"key","value":{"val1":"a","val2":"b"}}.
124
	 * Value is boolean.
125
	 * @since 1.26
126
	 */
127
	const META_KVP_MERGE = '_kvpmerge';
128
129
	/**
130
	 * Key for the 'BC bools' metadata item. Value is string[].
131
	 * Note no setter is provided.
132
	 * @since 1.25
133
	 */
134
	const META_BC_BOOLS = '_BC_bools';
135
136
	/**
137
	 * Key for the 'BC subelements' metadata item. Value is string[].
138
	 * Note no setter is provided.
139
	 * @since 1.25
140
	 */
141
	const META_BC_SUBELEMENTS = '_BC_subelements';
142
143
	private $data, $size, $maxSize;
0 ignored issues
show
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
144
	private $errorFormatter;
145
146
	// Deprecated fields
147
	private $checkingSize, $mainForContinuation;
0 ignored issues
show
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
148
149
	/**
150
	 * @param int|bool $maxSize Maximum result "size", or false for no limit
151
	 * @since 1.25 Takes an integer|bool rather than an ApiMain
152
	 */
153
	public function __construct( $maxSize ) {
154
		if ( $maxSize instanceof ApiMain ) {
155
			wfDeprecated( 'ApiMain to ' . __METHOD__, '1.25' );
156
			$this->errorFormatter = $maxSize->getErrorFormatter();
157
			$this->mainForContinuation = $maxSize;
158
			$maxSize = $maxSize->getConfig()->get( 'APIMaxResultSize' );
159
		}
160
161
		$this->maxSize = $maxSize;
162
		$this->checkingSize = true;
163
		$this->reset();
164
	}
165
166
	/**
167
	 * Set the error formatter
168
	 * @since 1.25
169
	 * @param ApiErrorFormatter $formatter
170
	 */
171
	public function setErrorFormatter( ApiErrorFormatter $formatter ) {
172
		$this->errorFormatter = $formatter;
173
	}
174
175
	/**
176
	 * Allow for adding one ApiResult into another
177
	 * @since 1.25
178
	 * @return mixed
179
	 */
180
	public function serializeForApiResult() {
181
		return $this->data;
182
	}
183
184
	/************************************************************************//**
185
	 * @name   Content
186
	 * @{
187
	 */
188
189
	/**
190
	 * Clear the current result data.
191
	 */
192
	public function reset() {
193
		$this->data = [
194
			self::META_TYPE => 'assoc', // Usually what's desired
195
		];
196
		$this->size = 0;
197
	}
198
199
	/**
200
	 * Get the result data array
201
	 *
202
	 * The returned value should be considered read-only.
203
	 *
204
	 * Transformations include:
205
	 *
206
	 * Custom: (callable) Applied before other transformations. Signature is
207
	 *  function ( &$data, &$metadata ), return value is ignored. Called for
208
	 *  each nested array.
209
	 *
210
	 * BC: (array) This transformation does various adjustments to bring the
211
	 *  output in line with the pre-1.25 result format. The value array is a
212
	 *  list of flags: 'nobool', 'no*', 'nosub'.
213
	 *  - Boolean-valued items are changed to '' if true or removed if false,
214
	 *    unless listed in META_BC_BOOLS. This may be skipped by including
215
	 *    'nobool' in the value array.
216
	 *  - The tag named by META_CONTENT is renamed to '*', and META_CONTENT is
217
	 *    set to '*'. This may be skipped by including 'no*' in the value
218
	 *    array.
219
	 *  - Tags listed in META_BC_SUBELEMENTS will have their values changed to
220
	 *    [ '*' => $value ]. This may be skipped by including 'nosub' in
221
	 *    the value array.
222
	 *  - If META_TYPE is 'BCarray', set it to 'default'
223
	 *  - If META_TYPE is 'BCassoc', set it to 'default'
224
	 *  - If META_TYPE is 'BCkvp', perform the transformation (even if
225
	 *    the Types transformation is not being applied).
226
	 *
227
	 * Types: (assoc) Apply transformations based on META_TYPE. The values
228
	 * array is an associative array with the following possible keys:
229
	 *  - AssocAsObject: (bool) If true, return arrays with META_TYPE 'assoc'
230
	 *    as objects.
231
	 *  - ArmorKVP: (string) If provided, transform arrays with META_TYPE 'kvp'
232
	 *    and 'BCkvp' into arrays of two-element arrays, something like this:
233
	 *      $output = [];
234
	 *      foreach ( $input as $key => $value ) {
235
	 *          $pair = [];
236
	 *          $pair[$META_KVP_KEY_NAME ?: $ArmorKVP_value] = $key;
237
	 *          ApiResult::setContentValue( $pair, 'value', $value );
238
	 *          $output[] = $pair;
239
	 *      }
240
	 *
241
	 * Strip: (string) Strips metadata keys from the result.
242
	 *  - 'all': Strip all metadata, recursively
243
	 *  - 'base': Strip metadata at the top-level only.
244
	 *  - 'none': Do not strip metadata.
245
	 *  - 'bc': Like 'all', but leave certain pre-1.25 keys.
246
	 *
247
	 * @since 1.25
248
	 * @param array|string|null $path Path to fetch, see ApiResult::addValue
249
	 * @param array $transforms See above
250
	 * @return mixed Result data, or null if not found
251
	 */
252
	public function getResultData( $path = [], $transforms = [] ) {
253
		$path = (array)$path;
254
		if ( !$path ) {
255
			return self::applyTransformations( $this->data, $transforms );
256
		}
257
258
		$last = array_pop( $path );
259
		$ret = &$this->path( $path, 'dummy' );
260
		if ( !isset( $ret[$last] ) ) {
261
			return null;
262
		} elseif ( is_array( $ret[$last] ) ) {
263
			return self::applyTransformations( $ret[$last], $transforms );
264
		} else {
265
			return $ret[$last];
266
		}
267
	}
268
269
	/**
270
	 * Get the size of the result, i.e. the amount of bytes in it
271
	 * @return int
272
	 */
273
	public function getSize() {
274
		return $this->size;
275
	}
276
277
	/**
278
	 * Add an output value to the array by name.
279
	 *
280
	 * Verifies that value with the same name has not been added before.
281
	 *
282
	 * @since 1.25
283
	 * @param array &$arr To add $value to
284
	 * @param string|int|null $name Index of $arr to add $value at,
285
	 *   or null to use the next numeric index.
286
	 * @param mixed $value
287
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
288
	 */
289
	public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
290
		if ( ( $flags & ApiResult::NO_VALIDATE ) !== ApiResult::NO_VALIDATE ) {
291
			$value = self::validateValue( $value );
292
		}
293
294
		if ( $name === null ) {
295
			if ( $flags & ApiResult::ADD_ON_TOP ) {
296
				array_unshift( $arr, $value );
297
			} else {
298
				array_push( $arr, $value );
299
			}
300
			return;
301
		}
302
303
		$exists = isset( $arr[$name] );
304
		if ( !$exists || ( $flags & ApiResult::OVERRIDE ) ) {
305
			if ( !$exists && ( $flags & ApiResult::ADD_ON_TOP ) ) {
306
				$arr = [ $name => $value ] + $arr;
307
			} else {
308
				$arr[$name] = $value;
309
			}
310
		} elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
311
			$conflicts = array_intersect_key( $arr[$name], $value );
312
			if ( !$conflicts ) {
313
				$arr[$name] += $value;
314
			} else {
315
				$keys = implode( ', ', array_keys( $conflicts ) );
316
				throw new RuntimeException(
317
					"Conflicting keys ($keys) when attempting to merge element $name"
318
				);
319
			}
320
		} else {
321
			throw new RuntimeException(
322
				"Attempting to add element $name=$value, existing value is {$arr[$name]}"
323
			);
324
		}
325
	}
326
327
	/**
328
	 * Validate a value for addition to the result
329
	 * @param mixed $value
330
	 * @return array|mixed|string
331
	 */
332
	private static function validateValue( $value ) {
333
		global $wgContLang;
334
335
		if ( is_object( $value ) ) {
336
			// Note we use is_callable() here instead of instanceof because
337
			// ApiSerializable is an informal protocol (see docs there for details).
338
			if ( is_callable( [ $value, 'serializeForApiResult' ] ) ) {
339
				$oldValue = $value;
340
				$value = $value->serializeForApiResult();
341
				if ( is_object( $value ) ) {
342
					throw new UnexpectedValueException(
343
						get_class( $oldValue ) . '::serializeForApiResult() returned an object of class ' .
344
							get_class( $value )
345
					);
346
				}
347
348
				// Recursive call instead of fall-through so we can throw a
349
				// better exception message.
350
				try {
351
					return self::validateValue( $value );
352
				} catch ( Exception $ex ) {
353
					throw new UnexpectedValueException(
354
						get_class( $oldValue ) . '::serializeForApiResult() returned an invalid value: ' .
355
							$ex->getMessage(),
356
						0,
357
						$ex
358
					);
359
				}
360
			} elseif ( is_callable( [ $value, '__toString' ] ) ) {
361
				$value = (string)$value;
362
			} else {
363
				$value = (array)$value + [ self::META_TYPE => 'assoc' ];
364
			}
365
		}
366
		if ( is_array( $value ) ) {
367
			// Work around PHP bug 45959 by copying to a temporary
368
			// (in this case, foreach gets $k === "1" but $tmp[$k] assigns as if $k === 1)
369
			$tmp = [];
370
			foreach ( $value as $k => $v ) {
371
				$tmp[$k] = self::validateValue( $v );
372
			}
373
			$value = $tmp;
374
		} elseif ( is_float( $value ) && !is_finite( $value ) ) {
375
			throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
376
		} elseif ( is_string( $value ) ) {
377
			$value = $wgContLang->normalize( $value );
378
		} elseif ( $value !== null && !is_scalar( $value ) ) {
379
			$type = gettype( $value );
380
			if ( is_resource( $value ) ) {
381
				$type .= '(' . get_resource_type( $value ) . ')';
382
			}
383
			throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
384
		}
385
386
		return $value;
387
	}
388
389
	/**
390
	 * Add value to the output data at the given path.
391
	 *
392
	 * Path can be an indexed array, each element specifying the branch at which to add the new
393
	 * value. Setting $path to [ 'a', 'b', 'c' ] is equivalent to data['a']['b']['c'] = $value.
394
	 * If $path is null, the value will be inserted at the data root.
395
	 *
396
	 * @param array|string|int|null $path
397
	 * @param string|int|null $name See ApiResult::setValue()
398
	 * @param mixed $value
399
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
400
	 *   This parameter used to be boolean, and the value of OVERRIDE=1 was specifically
401
	 *   chosen so that it would be backwards compatible with the new method signature.
402
	 * @return bool True if $value fits in the result, false if not
403
	 * @since 1.21 int $flags replaced boolean $override
404
	 */
405
	public function addValue( $path, $name, $value, $flags = 0 ) {
406
		$arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
407
408
		if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
409
			// self::size needs the validated value. Then flag
410
			// to not re-validate later.
411
			$value = self::validateValue( $value );
412
			$flags |= ApiResult::NO_VALIDATE;
413
414
			$newsize = $this->size + self::size( $value );
415
			if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
416
				/// @todo Add i18n message when replacing calls to ->setWarning()
417
				$msg = new ApiRawMessage( 'This result was truncated because it would otherwise ' .
418
					'be larger than the limit of $1 bytes', 'truncatedresult' );
419
				$msg->numParams( $this->maxSize );
420
				$this->errorFormatter->addWarning( 'result', $msg );
421
				return false;
422
			}
423
			$this->size = $newsize;
424
		}
425
426
		self::setValue( $arr, $name, $value, $flags );
427
		return true;
428
	}
429
430
	/**
431
	 * Remove an output value to the array by name.
432
	 * @param array &$arr To remove $value from
433
	 * @param string|int $name Index of $arr to remove
434
	 * @return mixed Old value, or null
435
	 */
436
	public static function unsetValue( array &$arr, $name ) {
437
		$ret = null;
438
		if ( isset( $arr[$name] ) ) {
439
			$ret = $arr[$name];
440
			unset( $arr[$name] );
441
		}
442
		return $ret;
443
	}
444
445
	/**
446
	 * Remove value from the output data at the given path.
447
	 *
448
	 * @since 1.25
449
	 * @param array|string|null $path See ApiResult::addValue()
450
	 * @param string|int|null $name Index to remove at $path.
451
	 *   If null, $path itself is removed.
452
	 * @param int $flags Flags used when adding the value
453
	 * @return mixed Old value, or null
454
	 */
455
	public function removeValue( $path, $name, $flags = 0 ) {
456
		$path = (array)$path;
457
		if ( $name === null ) {
458
			if ( !$path ) {
459
				throw new InvalidArgumentException( 'Cannot remove the data root' );
460
			}
461
			$name = array_pop( $path );
462
		}
463
		$ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
464
		if ( $this->checkingSize && !( $flags & ApiResult::NO_SIZE_CHECK ) ) {
465
			$newsize = $this->size - self::size( $ret );
466
			$this->size = max( $newsize, 0 );
467
		}
468
		return $ret;
469
	}
470
471
	/**
472
	 * Add an output value to the array by name and mark as META_CONTENT.
473
	 *
474
	 * @since 1.25
475
	 * @param array &$arr To add $value to
476
	 * @param string|int $name Index of $arr to add $value at.
477
	 * @param mixed $value
478
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
479
	 */
480
	public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
481
		if ( $name === null ) {
482
			throw new InvalidArgumentException( 'Content value must be named' );
483
		}
484
		self::setContentField( $arr, $name, $flags );
485
		self::setValue( $arr, $name, $value, $flags );
486
	}
487
488
	/**
489
	 * Add value to the output data at the given path and mark as META_CONTENT
490
	 *
491
	 * @since 1.25
492
	 * @param array|string|null $path See ApiResult::addValue()
493
	 * @param string|int $name See ApiResult::setValue()
494
	 * @param mixed $value
495
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
496
	 * @return bool True if $value fits in the result, false if not
497
	 */
498
	public function addContentValue( $path, $name, $value, $flags = 0 ) {
499
		if ( $name === null ) {
500
			throw new InvalidArgumentException( 'Content value must be named' );
501
		}
502
		$this->addContentField( $path, $name, $flags );
503
		$this->addValue( $path, $name, $value, $flags );
504
	}
505
506
	/**
507
	 * Add the numeric limit for a limit=max to the result.
508
	 *
509
	 * @since 1.25
510
	 * @param string $moduleName
511
	 * @param int $limit
512
	 */
513
	public function addParsedLimit( $moduleName, $limit ) {
514
		// Add value, allowing overwriting
515
		$this->addValue( 'limits', $moduleName, $limit,
516
			ApiResult::OVERRIDE | ApiResult::NO_SIZE_CHECK );
517
	}
518
519
	/**@}*/
520
521
	/************************************************************************//**
522
	 * @name   Metadata
523
	 * @{
524
	 */
525
526
	/**
527
	 * Set the name of the content field name (META_CONTENT)
528
	 *
529
	 * @since 1.25
530
	 * @param array &$arr
531
	 * @param string|int $name Name of the field
532
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
533
	 */
534
	public static function setContentField( array &$arr, $name, $flags = 0 ) {
535
		if ( isset( $arr[self::META_CONTENT] ) &&
536
			isset( $arr[$arr[self::META_CONTENT]] ) &&
537
			!( $flags & self::OVERRIDE )
538
		) {
539
			throw new RuntimeException(
540
				"Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
541
					' is already set as the content element'
542
			);
543
		}
544
		$arr[self::META_CONTENT] = $name;
545
	}
546
547
	/**
548
	 * Set the name of the content field name (META_CONTENT)
549
	 *
550
	 * @since 1.25
551
	 * @param array|string|null $path See ApiResult::addValue()
552
	 * @param string|int $name Name of the field
553
	 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
554
	 */
555
	public function addContentField( $path, $name, $flags = 0 ) {
556
		$arr = &$this->path( $path, ( $flags & ApiResult::ADD_ON_TOP ) ? 'prepend' : 'append' );
557
		self::setContentField( $arr, $name, $flags );
558
	}
559
560
	/**
561
	 * Causes the elements with the specified names to be output as
562
	 * subelements rather than attributes.
563
	 * @since 1.25 is static
564
	 * @param array &$arr
565
	 * @param array|string|int $names The element name(s) to be output as subelements
566
	 */
567 View Code Duplication
	public static function setSubelementsList( array &$arr, $names ) {
568
		if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
569
			$arr[self::META_SUBELEMENTS] = (array)$names;
570
		} else {
571
			$arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
572
		}
573
	}
574
575
	/**
576
	 * Causes the elements with the specified names to be output as
577
	 * subelements rather than attributes.
578
	 * @since 1.25
579
	 * @param array|string|null $path See ApiResult::addValue()
580
	 * @param array|string|int $names The element name(s) to be output as subelements
581
	 */
582
	public function addSubelementsList( $path, $names ) {
583
		$arr = &$this->path( $path );
584
		self::setSubelementsList( $arr, $names );
585
	}
586
587
	/**
588
	 * Causes the elements with the specified names to be output as
589
	 * attributes (when possible) rather than as subelements.
590
	 * @since 1.25
591
	 * @param array &$arr
592
	 * @param array|string|int $names The element name(s) to not be output as subelements
593
	 */
594
	public static function unsetSubelementsList( array &$arr, $names ) {
595 View Code Duplication
		if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
596
			$arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
597
		}
598
	}
599
600
	/**
601
	 * Causes the elements with the specified names to be output as
602
	 * attributes (when possible) rather than as subelements.
603
	 * @since 1.25
604
	 * @param array|string|null $path See ApiResult::addValue()
605
	 * @param array|string|int $names The element name(s) to not be output as subelements
606
	 */
607
	public function removeSubelementsList( $path, $names ) {
608
		$arr = &$this->path( $path );
609
		self::unsetSubelementsList( $arr, $names );
610
	}
611
612
	/**
613
	 * Set the tag name for numeric-keyed values in XML format
614
	 * @since 1.25 is static
615
	 * @param array &$arr
616
	 * @param string $tag Tag name
617
	 */
618
	public static function setIndexedTagName( array &$arr, $tag ) {
619
		if ( !is_string( $tag ) ) {
620
			throw new InvalidArgumentException( 'Bad tag name' );
621
		}
622
		$arr[self::META_INDEXED_TAG_NAME] = $tag;
623
	}
624
625
	/**
626
	 * Set the tag name for numeric-keyed values in XML format
627
	 * @since 1.25
628
	 * @param array|string|null $path See ApiResult::addValue()
629
	 * @param string $tag Tag name
630
	 */
631
	public function addIndexedTagName( $path, $tag ) {
632
		$arr = &$this->path( $path );
633
		self::setIndexedTagName( $arr, $tag );
634
	}
635
636
	/**
637
	 * Set indexed tag name on $arr and all subarrays
638
	 *
639
	 * @since 1.25
640
	 * @param array &$arr
641
	 * @param string $tag Tag name
642
	 */
643
	public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
644
		if ( !is_string( $tag ) ) {
645
			throw new InvalidArgumentException( 'Bad tag name' );
646
		}
647
		$arr[self::META_INDEXED_TAG_NAME] = $tag;
648
		foreach ( $arr as $k => &$v ) {
649
			if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
650
				self::setIndexedTagNameRecursive( $v, $tag );
651
			}
652
		}
653
	}
654
655
	/**
656
	 * Set indexed tag name on $path and all subarrays
657
	 *
658
	 * @since 1.25
659
	 * @param array|string|null $path See ApiResult::addValue()
660
	 * @param string $tag Tag name
661
	 */
662
	public function addIndexedTagNameRecursive( $path, $tag ) {
663
		$arr = &$this->path( $path );
664
		self::setIndexedTagNameRecursive( $arr, $tag );
665
	}
666
667
	/**
668
	 * Preserve specified keys.
669
	 *
670
	 * This prevents XML name mangling and preventing keys from being removed
671
	 * by self::stripMetadata().
672
	 *
673
	 * @since 1.25
674
	 * @param array &$arr
675
	 * @param array|string $names The element name(s) to preserve
676
	 */
677 View Code Duplication
	public static function setPreserveKeysList( array &$arr, $names ) {
678
		if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
679
			$arr[self::META_PRESERVE_KEYS] = (array)$names;
680
		} else {
681
			$arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
682
		}
683
	}
684
685
	/**
686
	 * Preserve specified keys.
687
	 * @since 1.25
688
	 * @see self::setPreserveKeysList()
689
	 * @param array|string|null $path See ApiResult::addValue()
690
	 * @param array|string $names The element name(s) to preserve
691
	 */
692
	public function addPreserveKeysList( $path, $names ) {
693
		$arr = &$this->path( $path );
694
		self::setPreserveKeysList( $arr, $names );
695
	}
696
697
	/**
698
	 * Don't preserve specified keys.
699
	 * @since 1.25
700
	 * @see self::setPreserveKeysList()
701
	 * @param array &$arr
702
	 * @param array|string $names The element name(s) to not preserve
703
	 */
704
	public static function unsetPreserveKeysList( array &$arr, $names ) {
705 View Code Duplication
		if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
706
			$arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
707
		}
708
	}
709
710
	/**
711
	 * Don't preserve specified keys.
712
	 * @since 1.25
713
	 * @see self::setPreserveKeysList()
714
	 * @param array|string|null $path See ApiResult::addValue()
715
	 * @param array|string $names The element name(s) to not preserve
716
	 */
717
	public function removePreserveKeysList( $path, $names ) {
718
		$arr = &$this->path( $path );
719
		self::unsetPreserveKeysList( $arr, $names );
720
	}
721
722
	/**
723
	 * Set the array data type
724
	 *
725
	 * @since 1.25
726
	 * @param array &$arr
727
	 * @param string $type See ApiResult::META_TYPE
728
	 * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
729
	 */
730
	public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
731
		if ( !in_array( $type, [
732
				'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
733
				], true ) ) {
734
			throw new InvalidArgumentException( 'Bad type' );
735
		}
736
		$arr[self::META_TYPE] = $type;
737
		if ( is_string( $kvpKeyName ) ) {
738
			$arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
739
		}
740
	}
741
742
	/**
743
	 * Set the array data type for a path
744
	 * @since 1.25
745
	 * @param array|string|null $path See ApiResult::addValue()
746
	 * @param string $tag See ApiResult::META_TYPE
747
	 * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
748
	 */
749
	public function addArrayType( $path, $tag, $kvpKeyName = null ) {
750
		$arr = &$this->path( $path );
751
		self::setArrayType( $arr, $tag, $kvpKeyName );
752
	}
753
754
	/**
755
	 * Set the array data type recursively
756
	 * @since 1.25
757
	 * @param array &$arr
758
	 * @param string $type See ApiResult::META_TYPE
759
	 * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
760
	 */
761
	public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
762
		self::setArrayType( $arr, $type, $kvpKeyName );
763
		foreach ( $arr as $k => &$v ) {
764
			if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
765
				self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
766
			}
767
		}
768
	}
769
770
	/**
771
	 * Set the array data type for a path recursively
772
	 * @since 1.25
773
	 * @param array|string|null $path See ApiResult::addValue()
774
	 * @param string $tag See ApiResult::META_TYPE
775
	 * @param string $kvpKeyName See ApiResult::META_KVP_KEY_NAME
776
	 */
777
	public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
778
		$arr = &$this->path( $path );
779
		self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
780
	}
781
782
	/**@}*/
783
784
	/************************************************************************//**
785
	 * @name   Utility
786
	 * @{
787
	 */
788
789
	/**
790
	 * Test whether a key should be considered metadata
791
	 *
792
	 * @param string $key
793
	 * @return bool
794
	 */
795
	public static function isMetadataKey( $key ) {
796
		return substr( $key, 0, 1 ) === '_';
797
	}
798
799
	/**
800
	 * Apply transformations to an array, returning the transformed array.
801
	 *
802
	 * @see ApiResult::getResultData()
803
	 * @since 1.25
804
	 * @param array $dataIn
805
	 * @param array $transforms
806
	 * @return array|object
807
	 */
808
	protected static function applyTransformations( array $dataIn, array $transforms ) {
809
		$strip = isset( $transforms['Strip'] ) ? $transforms['Strip'] : 'none';
810
		if ( $strip === 'base' ) {
811
			$transforms['Strip'] = 'none';
812
		}
813
		$transformTypes = isset( $transforms['Types'] ) ? $transforms['Types'] : null;
814
		if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
815
			throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
816
		}
817
818
		$metadata = [];
819
		$data = self::stripMetadataNonRecursive( $dataIn, $metadata );
820
821
		if ( isset( $transforms['Custom'] ) ) {
822
			if ( !is_callable( $transforms['Custom'] ) ) {
823
				throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
824
			}
825
			call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
826
		}
827
828
		if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
829
			isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
830
			!isset( $metadata[self::META_KVP_KEY_NAME] )
831
		) {
832
			throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
833
				'ApiResult::META_KVP_KEY_NAME metadata item' );
834
		}
835
836
		// BC transformations
837
		$boolKeys = null;
838
		if ( isset( $transforms['BC'] ) ) {
839
			if ( !is_array( $transforms['BC'] ) ) {
840
				throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
841
			}
842
			if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
843
				$boolKeys = isset( $metadata[self::META_BC_BOOLS] )
844
					? array_flip( $metadata[self::META_BC_BOOLS] )
845
					: [];
846
			}
847
848
			if ( !in_array( 'no*', $transforms['BC'], true ) &&
849
				isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
850
			) {
851
				$k = $metadata[self::META_CONTENT];
852
				$data['*'] = $data[$k];
853
				unset( $data[$k] );
854
				$metadata[self::META_CONTENT] = '*';
855
			}
856
857
			if ( !in_array( 'nosub', $transforms['BC'], true ) &&
858
				isset( $metadata[self::META_BC_SUBELEMENTS] )
859
			) {
860
				foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
861
					if ( isset( $data[$k] ) ) {
862
						$data[$k] = [
863
							'*' => $data[$k],
864
							self::META_CONTENT => '*',
865
							self::META_TYPE => 'assoc',
866
						];
867
					}
868
				}
869
			}
870
871
			if ( isset( $metadata[self::META_TYPE] ) ) {
872
				switch ( $metadata[self::META_TYPE] ) {
873
					case 'BCarray':
874
					case 'BCassoc':
875
						$metadata[self::META_TYPE] = 'default';
876
						break;
877
					case 'BCkvp':
878
						$transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
879
						break;
880
				}
881
			}
882
		}
883
884
		// Figure out type, do recursive calls, and do boolean transform if necessary
885
		$defaultType = 'array';
886
		$maxKey = -1;
887
		foreach ( $data as $k => &$v ) {
888
			$v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
889
			if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
890
				if ( !$v ) {
891
					unset( $data[$k] );
892
					continue;
893
				}
894
				$v = '';
895
			}
896
			if ( is_string( $k ) ) {
897
				$defaultType = 'assoc';
898
			} elseif ( $k > $maxKey ) {
899
				$maxKey = $k;
900
			}
901
		}
902
		unset( $v );
903
904
		// Determine which metadata to keep
905
		switch ( $strip ) {
906
			case 'all':
907
			case 'base':
908
				$keepMetadata = [];
909
				break;
910
			case 'none':
911
				$keepMetadata = &$metadata;
912
				break;
913
			case 'bc':
914
				$keepMetadata = array_intersect_key( $metadata, [
915
					self::META_INDEXED_TAG_NAME => 1,
916
					self::META_SUBELEMENTS => 1,
917
				] );
918
				break;
919
			default:
920
				throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
921
		}
922
923
		// Type transformation
924
		if ( $transformTypes !== null ) {
925
			if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
926
				$defaultType = 'assoc';
927
			}
928
929
			// Override type, if provided
930
			$type = $defaultType;
931
			if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
932
				$type = $metadata[self::META_TYPE];
933
			}
934
			if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
935
				empty( $transformTypes['ArmorKVP'] )
936
			) {
937
				$type = 'assoc';
938
			} elseif ( $type === 'BCarray' ) {
939
				$type = 'array';
940
			} elseif ( $type === 'BCassoc' ) {
941
				$type = 'assoc';
942
			}
943
944
			// Apply transformation
945
			switch ( $type ) {
946
				case 'assoc':
947
					$metadata[self::META_TYPE] = 'assoc';
948
					$data += $keepMetadata;
949
					return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
950
951
				case 'array':
952
					ksort( $data );
953
					$data = array_values( $data );
954
					$metadata[self::META_TYPE] = 'array';
955
					return $data + $keepMetadata;
956
957
				case 'kvp':
958
				case 'BCkvp':
959
					$key = isset( $metadata[self::META_KVP_KEY_NAME] )
960
						? $metadata[self::META_KVP_KEY_NAME]
961
						: $transformTypes['ArmorKVP'];
962
					$valKey = isset( $transforms['BC'] ) ? '*' : 'value';
963
					$assocAsObject = !empty( $transformTypes['AssocAsObject'] );
964
					$merge = !empty( $metadata[self::META_KVP_MERGE] );
965
966
					$ret = [];
967
					foreach ( $data as $k => $v ) {
968
						if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
969
							$vArr = (array)$v;
970
							if ( isset( $vArr[self::META_TYPE] ) ) {
971
								$mergeType = $vArr[self::META_TYPE];
972
							} elseif ( is_object( $v ) ) {
973
								$mergeType = 'assoc';
974
							} else {
975
								$keys = array_keys( $vArr );
976
								sort( $keys, SORT_NUMERIC );
977
								$mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
978
							}
979
						} else {
980
							$mergeType = 'n/a';
981
						}
982
						if ( $mergeType === 'assoc' ) {
983
							$item = $vArr + [
0 ignored issues
show
The variable $vArr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
984
								$key => $k,
985
							];
986
							if ( $strip === 'none' ) {
987
								self::setPreserveKeysList( $item, [ $key ] );
988
							}
989
						} else {
990
							$item = [
991
								$key => $k,
992
								$valKey => $v,
993
							];
994
							if ( $strip === 'none' ) {
995
								$item += [
996
									self::META_PRESERVE_KEYS => [ $key ],
997
									self::META_CONTENT => $valKey,
998
									self::META_TYPE => 'assoc',
999
								];
1000
							}
1001
						}
1002
						$ret[] = $assocAsObject ? (object)$item : $item;
1003
					}
1004
					$metadata[self::META_TYPE] = 'array';
1005
1006
					return $ret + $keepMetadata;
1007
1008
				default:
1009
					throw new UnexpectedValueException( "Unknown type '$type'" );
1010
			}
1011
		} else {
1012
			return $data + $keepMetadata;
1013
		}
1014
	}
1015
1016
	/**
1017
	 * Recursively remove metadata keys from a data array or object
1018
	 *
1019
	 * Note this removes all potential metadata keys, not just the defined
1020
	 * ones.
1021
	 *
1022
	 * @since 1.25
1023
	 * @param array|object $data
1024
	 * @return array|object
1025
	 */
1026
	public static function stripMetadata( $data ) {
1027
		if ( is_array( $data ) || is_object( $data ) ) {
1028
			$isObj = is_object( $data );
1029
			if ( $isObj ) {
1030
				$data = (array)$data;
1031
			}
1032
			$preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1033
				? (array)$data[self::META_PRESERVE_KEYS]
1034
				: [];
1035
			foreach ( $data as $k => $v ) {
1036
				if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1037
					unset( $data[$k] );
1038
				} elseif ( is_array( $v ) || is_object( $v ) ) {
1039
					$data[$k] = self::stripMetadata( $v );
1040
				}
1041
			}
1042
			if ( $isObj ) {
1043
				$data = (object)$data;
1044
			}
1045
		}
1046
		return $data;
1047
	}
1048
1049
	/**
1050
	 * Remove metadata keys from a data array or object, non-recursive
1051
	 *
1052
	 * Note this removes all potential metadata keys, not just the defined
1053
	 * ones.
1054
	 *
1055
	 * @since 1.25
1056
	 * @param array|object $data
1057
	 * @param array &$metadata Store metadata here, if provided
1058
	 * @return array|object
1059
	 */
1060
	public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1061
		if ( !is_array( $metadata ) ) {
1062
			$metadata = [];
1063
		}
1064
		if ( is_array( $data ) || is_object( $data ) ) {
1065
			$isObj = is_object( $data );
1066
			if ( $isObj ) {
1067
				$data = (array)$data;
1068
			}
1069
			$preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1070
				? (array)$data[self::META_PRESERVE_KEYS]
1071
				: [];
1072
			foreach ( $data as $k => $v ) {
1073
				if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1074
					$metadata[$k] = $v;
1075
					unset( $data[$k] );
1076
				}
1077
			}
1078
			if ( $isObj ) {
1079
				$data = (object)$data;
1080
			}
1081
		}
1082
		return $data;
1083
	}
1084
1085
	/**
1086
	 * Get the 'real' size of a result item. This means the strlen() of the item,
1087
	 * or the sum of the strlen()s of the elements if the item is an array.
1088
	 * @param mixed $value Validated value (see self::validateValue())
1089
	 * @return int
1090
	 */
1091
	private static function size( $value ) {
1092
		$s = 0;
1093
		if ( is_array( $value ) ) {
1094
			foreach ( $value as $k => $v ) {
1095
				if ( !self::isMetadataKey( $k ) ) {
1096
					$s += self::size( $v );
1097
				}
1098
			}
1099
		} elseif ( is_scalar( $value ) ) {
1100
			$s = strlen( $value );
1101
		}
1102
1103
		return $s;
1104
	}
1105
1106
	/**
1107
	 * Return a reference to the internal data at $path
1108
	 *
1109
	 * @param array|string|null $path
1110
	 * @param string $create
1111
	 *   If 'append', append empty arrays.
1112
	 *   If 'prepend', prepend empty arrays.
1113
	 *   If 'dummy', return a dummy array.
1114
	 *   Else, raise an error.
1115
	 * @return array
1116
	 */
1117
	private function &path( $path, $create = 'append' ) {
1118
		$path = (array)$path;
1119
		$ret = &$this->data;
1120
		foreach ( $path as $i => $k ) {
1121
			if ( !isset( $ret[$k] ) ) {
1122
				switch ( $create ) {
1123
					case 'append':
1124
						$ret[$k] = [];
1125
						break;
1126
					case 'prepend':
1127
						$ret = [ $k => [] ] + $ret;
1128
						break;
1129
					case 'dummy':
1130
						$tmp = [];
1131
						return $tmp;
1132
					default:
1133
						$fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1134
						throw new InvalidArgumentException( "Path $fail does not exist" );
1135
				}
1136
			}
1137
			if ( !is_array( $ret[$k] ) ) {
1138
				$fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1139
				throw new InvalidArgumentException( "Path $fail is not an array" );
1140
			}
1141
			$ret = &$ret[$k];
1142
		}
1143
		return $ret;
1144
	}
1145
1146
	/**
1147
	 * Add the correct metadata to an array of vars we want to export through
1148
	 * the API.
1149
	 *
1150
	 * @param array $vars
1151
	 * @param bool $forceHash
1152
	 * @return array
1153
	 */
1154
	public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1155
		// Process subarrays and determine if this is a JS [] or {}
1156
		$hash = $forceHash;
1157
		$maxKey = -1;
1158
		$bools = [];
1159
		foreach ( $vars as $k => $v ) {
1160
			if ( is_array( $v ) || is_object( $v ) ) {
1161
				$vars[$k] = ApiResult::addMetadataToResultVars( (array)$v, is_object( $v ) );
1162
			} elseif ( is_bool( $v ) ) {
1163
				// Better here to use real bools even in BC formats
1164
				$bools[] = $k;
1165
			}
1166
			if ( is_string( $k ) ) {
1167
				$hash = true;
1168
			} elseif ( $k > $maxKey ) {
1169
				$maxKey = $k;
1170
			}
1171
		}
1172
		if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1173
			$hash = true;
1174
		}
1175
1176
		// Set metadata appropriately
1177
		if ( $hash ) {
1178
			// Get the list of keys we actually care about. Unfortunately, we can't support
1179
			// certain keys that conflict with ApiResult metadata.
1180
			$keys = array_diff( array_keys( $vars ), [
1181
				ApiResult::META_TYPE, ApiResult::META_PRESERVE_KEYS, ApiResult::META_KVP_KEY_NAME,
1182
				ApiResult::META_INDEXED_TAG_NAME, ApiResult::META_BC_BOOLS
1183
			] );
1184
1185
			return [
1186
				ApiResult::META_TYPE => 'kvp',
1187
				ApiResult::META_KVP_KEY_NAME => 'key',
1188
				ApiResult::META_PRESERVE_KEYS => $keys,
1189
				ApiResult::META_BC_BOOLS => $bools,
1190
				ApiResult::META_INDEXED_TAG_NAME => 'var',
1191
			] + $vars;
1192
		} else {
1193
			return [
1194
				ApiResult::META_TYPE => 'array',
1195
				ApiResult::META_BC_BOOLS => $bools,
1196
				ApiResult::META_INDEXED_TAG_NAME => 'value',
1197
			] + $vars;
1198
		}
1199
	}
1200
1201
	/**@}*/
1202
1203
}
1204
1205
/**
1206
 * For really cool vim folding this needs to be at the end:
1207
 * vim: foldmarker=@{,@} foldmethod=marker
1208
 */
1209