Replace uses of join() by implode()
[lhc/web/wiklou.git] / includes / api / ApiResult.php
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;
144 private $errorFormatter;
145
146 // Deprecated fields
147 private $checkingSize, $mainForContinuation;
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 * array( '*' => $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 = array();
234 * foreach ( $input as $key => $value ) {
235 * $pair = array();
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 array('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::valueSize 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::valueSize( $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::valueSize( $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 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 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 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 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 + [
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 * @note Once the deprecated public self::size is removed, we can rename
1089 * this back to a less awkward name.
1090 * @param mixed $value Validated value (see self::validateValue())
1091 * @return int
1092 */
1093 private static function valueSize( $value ) {
1094 $s = 0;
1095 if ( is_array( $value ) ) {
1096 foreach ( $value as $k => $v ) {
1097 if ( !self::isMetadataKey( $k ) ) {
1098 $s += self::valueSize( $v );
1099 }
1100 }
1101 } elseif ( is_scalar( $value ) ) {
1102 $s = strlen( $value );
1103 }
1104
1105 return $s;
1106 }
1107
1108 /**
1109 * Return a reference to the internal data at $path
1110 *
1111 * @param array|string|null $path
1112 * @param string $create
1113 * If 'append', append empty arrays.
1114 * If 'prepend', prepend empty arrays.
1115 * If 'dummy', return a dummy array.
1116 * Else, raise an error.
1117 * @return array
1118 */
1119 private function &path( $path, $create = 'append' ) {
1120 $path = (array)$path;
1121 $ret = &$this->data;
1122 foreach ( $path as $i => $k ) {
1123 if ( !isset( $ret[$k] ) ) {
1124 switch ( $create ) {
1125 case 'append':
1126 $ret[$k] = [];
1127 break;
1128 case 'prepend':
1129 $ret = [ $k => [] ] + $ret;
1130 break;
1131 case 'dummy':
1132 $tmp = [];
1133 return $tmp;
1134 default:
1135 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1136 throw new InvalidArgumentException( "Path $fail does not exist" );
1137 }
1138 }
1139 if ( !is_array( $ret[$k] ) ) {
1140 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1141 throw new InvalidArgumentException( "Path $fail is not an array" );
1142 }
1143 $ret = &$ret[$k];
1144 }
1145 return $ret;
1146 }
1147
1148 /**
1149 * Add the correct metadata to an array of vars we want to export through
1150 * the API.
1151 *
1152 * @param array $vars
1153 * @param boolean $forceHash
1154 * @return array
1155 */
1156 public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1157 // Process subarrays and determine if this is a JS [] or {}
1158 $hash = $forceHash;
1159 $maxKey = -1;
1160 $bools = [];
1161 foreach ( $vars as $k => $v ) {
1162 if ( is_array( $v ) || is_object( $v ) ) {
1163 $vars[$k] = ApiResult::addMetadataToResultVars( (array)$v, is_object( $v ) );
1164 } elseif ( is_bool( $v ) ) {
1165 // Better here to use real bools even in BC formats
1166 $bools[] = $k;
1167 }
1168 if ( is_string( $k ) ) {
1169 $hash = true;
1170 } elseif ( $k > $maxKey ) {
1171 $maxKey = $k;
1172 }
1173 }
1174 if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1175 $hash = true;
1176 }
1177
1178 // Set metadata appropriately
1179 if ( $hash ) {
1180 // Get the list of keys we actually care about. Unfortunately, we can't support
1181 // certain keys that conflict with ApiResult metadata.
1182 $keys = array_diff( array_keys( $vars ), [
1183 ApiResult::META_TYPE, ApiResult::META_PRESERVE_KEYS, ApiResult::META_KVP_KEY_NAME,
1184 ApiResult::META_INDEXED_TAG_NAME, ApiResult::META_BC_BOOLS
1185 ] );
1186
1187 return [
1188 ApiResult::META_TYPE => 'kvp',
1189 ApiResult::META_KVP_KEY_NAME => 'key',
1190 ApiResult::META_PRESERVE_KEYS => $keys,
1191 ApiResult::META_BC_BOOLS => $bools,
1192 ApiResult::META_INDEXED_TAG_NAME => 'var',
1193 ] + $vars;
1194 } else {
1195 return [
1196 ApiResult::META_TYPE => 'array',
1197 ApiResult::META_BC_BOOLS => $bools,
1198 ApiResult::META_INDEXED_TAG_NAME => 'value',
1199 ] + $vars;
1200 }
1201 }
1202
1203 /**@}*/
1204
1205 /************************************************************************//**
1206 * @name Deprecated
1207 * @{
1208 */
1209
1210 /**
1211 * Formerly used to enable/disable "raw mode".
1212 * @deprecated since 1.25, you shouldn't have been using it in the first place
1213 * @since 1.23 $flag parameter added
1214 * @param bool $flag Set the raw mode flag to this state
1215 */
1216 public function setRawMode( $flag = true ) {
1217 wfDeprecated( __METHOD__, '1.25' );
1218 }
1219
1220 /**
1221 * Returns true, the equivalent of "raw mode" is always enabled now
1222 * @deprecated since 1.25, you shouldn't have been using it in the first place
1223 * @return bool
1224 */
1225 public function getIsRawMode() {
1226 wfDeprecated( __METHOD__, '1.25' );
1227 return true;
1228 }
1229
1230 /**
1231 * Get the result's internal data array (read-only)
1232 * @deprecated since 1.25, use $this->getResultData() instead
1233 * @return array
1234 */
1235 public function getData() {
1236 wfDeprecated( __METHOD__, '1.25' );
1237 return $this->getResultData( null, [
1238 'BC' => [],
1239 'Types' => [],
1240 'Strip' => 'all',
1241 ] );
1242 }
1243
1244 /**
1245 * Disable size checking in addValue(). Don't use this unless you
1246 * REALLY know what you're doing. Values added while size checking
1247 * was disabled will not be counted (ever)
1248 * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
1249 */
1250 public function disableSizeCheck() {
1251 wfDeprecated( __METHOD__, '1.24' );
1252 $this->checkingSize = false;
1253 }
1254
1255 /**
1256 * Re-enable size checking in addValue()
1257 * @deprecated since 1.24, use ApiResult::NO_SIZE_CHECK
1258 */
1259 public function enableSizeCheck() {
1260 wfDeprecated( __METHOD__, '1.24' );
1261 $this->checkingSize = true;
1262 }
1263
1264 /**
1265 * Alias for self::setValue()
1266 *
1267 * @since 1.21 int $flags replaced boolean $override
1268 * @deprecated since 1.25, use self::setValue() instead
1269 * @param array $arr To add $value to
1270 * @param string $name Index of $arr to add $value at
1271 * @param mixed $value
1272 * @param int $flags Zero or more OR-ed flags like OVERRIDE | ADD_ON_TOP.
1273 * This parameter used to be boolean, and the value of OVERRIDE=1 was
1274 * specifically chosen so that it would be backwards compatible with the
1275 * new method signature.
1276 */
1277 public static function setElement( &$arr, $name, $value, $flags = 0 ) {
1278 wfDeprecated( __METHOD__, '1.25' );
1279 self::setValue( $arr, $name, $value, $flags );
1280 }
1281
1282 /**
1283 * Adds a content element to an array.
1284 * Use this function instead of hardcoding the '*' element.
1285 * @deprecated since 1.25, use self::setContentValue() instead
1286 * @param array $arr To add the content element to
1287 * @param mixed $value
1288 * @param string $subElemName When present, content element is created
1289 * as a sub item of $arr. Use this parameter to create elements in
1290 * format "<elem>text</elem>" without attributes.
1291 */
1292 public static function setContent( &$arr, $value, $subElemName = null ) {
1293 wfDeprecated( __METHOD__, '1.25' );
1294 if ( is_array( $value ) ) {
1295 throw new InvalidArgumentException( __METHOD__ . ': Bad parameter' );
1296 }
1297 if ( is_null( $subElemName ) ) {
1298 self::setContentValue( $arr, 'content', $value );
1299 } else {
1300 if ( !isset( $arr[$subElemName] ) ) {
1301 $arr[$subElemName] = [];
1302 }
1303 self::setContentValue( $arr[$subElemName], 'content', $value );
1304 }
1305 }
1306
1307 /**
1308 * Set indexed tag name on all subarrays of $arr
1309 *
1310 * Does not set the tag name for $arr itself.
1311 *
1312 * @deprecated since 1.25, use self::setIndexedTagNameRecursive() instead
1313 * @param array $arr
1314 * @param string $tag Tag name
1315 */
1316 public function setIndexedTagName_recursive( &$arr, $tag ) {
1317 wfDeprecated( __METHOD__, '1.25' );
1318 if ( !is_array( $arr ) ) {
1319 return;
1320 }
1321 if ( !is_string( $tag ) ) {
1322 throw new InvalidArgumentException( 'Bad tag name' );
1323 }
1324 foreach ( $arr as $k => &$v ) {
1325 if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
1326 $v[self::META_INDEXED_TAG_NAME] = $tag;
1327 $this->setIndexedTagName_recursive( $v, $tag );
1328 }
1329 }
1330 }
1331
1332 /**
1333 * Alias for self::addIndexedTagName()
1334 * @deprecated since 1.25, use $this->addIndexedTagName() instead
1335 * @param array $path Path to the array, like addValue()'s $path
1336 * @param string $tag
1337 */
1338 public function setIndexedTagName_internal( $path, $tag ) {
1339 wfDeprecated( __METHOD__, '1.25' );
1340 $this->addIndexedTagName( $path, $tag );
1341 }
1342
1343 /**
1344 * Alias for self::addParsedLimit()
1345 * @deprecated since 1.25, use $this->addParsedLimit() instead
1346 * @param string $moduleName
1347 * @param int $limit
1348 */
1349 public function setParsedLimit( $moduleName, $limit ) {
1350 wfDeprecated( __METHOD__, '1.25' );
1351 $this->addParsedLimit( $moduleName, $limit );
1352 }
1353
1354 /**
1355 * Set the ApiMain for use by $this->beginContinuation()
1356 * @since 1.25
1357 * @deprecated for backwards compatibility only, do not use
1358 * @param ApiMain $main
1359 */
1360 public function setMainForContinuation( ApiMain $main ) {
1361 $this->mainForContinuation = $main;
1362 }
1363
1364 /**
1365 * Parse a 'continue' parameter and return status information.
1366 *
1367 * This must be balanced by a call to endContinuation().
1368 *
1369 * @since 1.24
1370 * @deprecated since 1.25, use ApiContinuationManager instead
1371 * @param string|null $continue
1372 * @param ApiBase[] $allModules
1373 * @param array $generatedModules
1374 * @return array
1375 */
1376 public function beginContinuation(
1377 $continue, array $allModules = [], array $generatedModules = []
1378 ) {
1379 wfDeprecated( __METHOD__, '1.25' );
1380 if ( $this->mainForContinuation->getContinuationManager() ) {
1381 throw new UnexpectedValueException(
1382 __METHOD__ . ': Continuation already in progress from ' .
1383 $this->mainForContinuation->getContinuationManager()->getSource()
1384 );
1385 }
1386
1387 // Ugh. If $continue doesn't match that in the request, temporarily
1388 // replace the request when creating the ApiContinuationManager.
1389 if ( $continue === null ) {
1390 $continue = '';
1391 }
1392 if ( $this->mainForContinuation->getVal( 'continue', '' ) !== $continue ) {
1393 $oldCtx = $this->mainForContinuation->getContext();
1394 $newCtx = new DerivativeContext( $oldCtx );
1395 $newCtx->setRequest( new DerivativeRequest(
1396 $oldCtx->getRequest(),
1397 [ 'continue' => $continue ] + $oldCtx->getRequest()->getValues(),
1398 $oldCtx->getRequest()->wasPosted()
1399 ) );
1400 $this->mainForContinuation->setContext( $newCtx );
1401 $reset = new ScopedCallback(
1402 [ $this->mainForContinuation, 'setContext' ],
1403 [ $oldCtx ]
1404 );
1405 }
1406 $manager = new ApiContinuationManager(
1407 $this->mainForContinuation, $allModules, $generatedModules
1408 );
1409 $reset = null;
1410
1411 $this->mainForContinuation->setContinuationManager( $manager );
1412
1413 return [
1414 $manager->isGeneratorDone(),
1415 $manager->getRunModules(),
1416 ];
1417 }
1418
1419 /**
1420 * @since 1.24
1421 * @deprecated since 1.25, use ApiContinuationManager instead
1422 * @param ApiBase $module
1423 * @param string $paramName
1424 * @param string|array $paramValue
1425 */
1426 public function setContinueParam( ApiBase $module, $paramName, $paramValue ) {
1427 wfDeprecated( __METHOD__, '1.25' );
1428 if ( $this->mainForContinuation->getContinuationManager() ) {
1429 $this->mainForContinuation->getContinuationManager()->addContinueParam(
1430 $module, $paramName, $paramValue
1431 );
1432 }
1433 }
1434
1435 /**
1436 * @since 1.24
1437 * @deprecated since 1.25, use ApiContinuationManager instead
1438 * @param ApiBase $module
1439 * @param string $paramName
1440 * @param string|array $paramValue
1441 */
1442 public function setGeneratorContinueParam( ApiBase $module, $paramName, $paramValue ) {
1443 wfDeprecated( __METHOD__, '1.25' );
1444 if ( $this->mainForContinuation->getContinuationManager() ) {
1445 $this->mainForContinuation->getContinuationManager()->addGeneratorContinueParam(
1446 $module, $paramName, $paramValue
1447 );
1448 }
1449 }
1450
1451 /**
1452 * Close continuation, writing the data into the result
1453 * @since 1.24
1454 * @deprecated since 1.25, use ApiContinuationManager instead
1455 * @param string $style 'standard' for the new style since 1.21, 'raw' for
1456 * the style used in 1.20 and earlier.
1457 */
1458 public function endContinuation( $style = 'standard' ) {
1459 wfDeprecated( __METHOD__, '1.25' );
1460 if ( !$this->mainForContinuation->getContinuationManager() ) {
1461 return;
1462 }
1463
1464 if ( $style === 'raw' ) {
1465 $data = $this->mainForContinuation->getContinuationManager()->getRawContinuation();
1466 if ( $data ) {
1467 $this->addValue( null, 'query-continue', $data, self::ADD_ON_TOP | self::NO_SIZE_CHECK );
1468 }
1469 } else {
1470 $this->mainForContinuation->getContinuationManager()->setContinuationIntoResult( $this );
1471 }
1472 }
1473
1474 /**
1475 * No-op, this is now checked on insert.
1476 * @deprecated since 1.25
1477 */
1478 public function cleanUpUTF8() {
1479 wfDeprecated( __METHOD__, '1.25' );
1480 }
1481
1482 /**
1483 * Get the 'real' size of a result item. This means the strlen() of the item,
1484 * or the sum of the strlen()s of the elements if the item is an array.
1485 * @deprecated since 1.25, no external users known and there doesn't seem
1486 * to be any case for such use over just checking the return value from the
1487 * add/set methods.
1488 * @param mixed $value
1489 * @return int
1490 */
1491 public static function size( $value ) {
1492 wfDeprecated( __METHOD__, '1.25' );
1493 return self::valueSize( self::validateValue( $value ) );
1494 }
1495
1496 /**
1497 * Converts a Status object to an array suitable for addValue
1498 * @deprecated since 1.25, use ApiErrorFormatter::arrayFromStatus()
1499 * @param Status $status
1500 * @param string $errorType
1501 * @return array
1502 */
1503 public function convertStatusToArray( $status, $errorType = 'error' ) {
1504 wfDeprecated( __METHOD__, '1.25' );
1505 return $this->errorFormatter->arrayFromStatus( $status, $errorType );
1506 }
1507
1508 /**@}*/
1509 }
1510
1511 /**
1512 * For really cool vim folding this needs to be at the end:
1513 * vim: foldmarker=@{,@} foldmethod=marker
1514 */