MediaWiki REL1_37
ApiResult.php
Go to the documentation of this file.
1<?php
22
35class ApiResult implements ApiSerializable {
36
41 public const OVERRIDE = 1;
42
49 public const ADD_ON_TOP = 2;
50
58 public const NO_SIZE_CHECK = 4;
59
66 public const NO_VALIDATE = self::NO_SIZE_CHECK | 8;
67
72 public const META_INDEXED_TAG_NAME = '_element';
73
78 public const META_SUBELEMENTS = '_subelements';
79
84 public const META_PRESERVE_KEYS = '_preservekeys';
85
90 public const META_CONTENT = '_content';
91
110 public const META_TYPE = '_type';
111
119 public const META_KVP_KEY_NAME = '_kvpkeyname';
120
129 public const META_KVP_MERGE = '_kvpmerge';
130
136 public const META_BC_BOOLS = '_BC_bools';
137
143 public const META_BC_SUBELEMENTS = '_BC_subelements';
144
147
151 public function __construct( $maxSize ) {
152 $this->maxSize = $maxSize;
153 $this->reset();
154 }
155
160 public function setErrorFormatter( ApiErrorFormatter $formatter ) {
161 $this->errorFormatter = $formatter;
162 }
163
169 public function serializeForApiResult() {
170 return $this->data;
171 }
172
173 /***************************************************************************/
174 // region Content
180 public function reset() {
181 $this->data = [
182 self::META_TYPE => 'assoc', // Usually what's desired
183 ];
184 $this->size = 0;
185 }
186
240 public function getResultData( $path = [], $transforms = [] ) {
241 $path = (array)$path;
242 if ( !$path ) {
243 return self::applyTransformations( $this->data, $transforms );
244 }
245
246 $last = array_pop( $path );
247 $ret = &$this->path( $path, 'dummy' );
248 if ( !isset( $ret[$last] ) ) {
249 return null;
250 } elseif ( is_array( $ret[$last] ) ) {
251 return self::applyTransformations( $ret[$last], $transforms );
252 } else {
253 return $ret[$last];
254 }
255 }
256
261 public function getSize() {
262 return $this->size;
263 }
264
277 public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
278 if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
279 $value = self::validateValue( $value );
280 }
281
282 if ( $name === null ) {
283 if ( $flags & self::ADD_ON_TOP ) {
284 array_unshift( $arr, $value );
285 } else {
286 array_push( $arr, $value );
287 }
288 return;
289 }
290
291 $exists = isset( $arr[$name] );
292 if ( !$exists || ( $flags & self::OVERRIDE ) ) {
293 if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
294 $arr = [ $name => $value ] + $arr;
295 } else {
296 $arr[$name] = $value;
297 }
298 } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
299 $conflicts = array_intersect_key( $arr[$name], $value );
300 if ( !$conflicts ) {
301 $arr[$name] += $value;
302 } else {
303 $keys = implode( ', ', array_keys( $conflicts ) );
304 throw new RuntimeException(
305 "Conflicting keys ($keys) when attempting to merge element $name"
306 );
307 }
308 } elseif ( $value !== $arr[$name] ) {
309 throw new RuntimeException(
310 "Attempting to add element $name=$value, existing value is {$arr[$name]}"
311 );
312 }
313 }
314
320 private static function validateValue( $value ) {
321 if ( is_object( $value ) ) {
322 // Note we use is_callable() here instead of instanceof because
323 // ApiSerializable is an informal protocol (see docs there for details).
324 if ( is_callable( [ $value, 'serializeForApiResult' ] ) ) {
325 $oldValue = $value;
326 $value = $value->serializeForApiResult();
327 if ( is_object( $value ) ) {
328 throw new UnexpectedValueException(
329 get_class( $oldValue ) . '::serializeForApiResult() returned an object of class ' .
330 get_class( $value )
331 );
332 }
333
334 // Recursive call instead of fall-through so we can throw a
335 // better exception message.
336 try {
337 return self::validateValue( $value );
338 } catch ( Exception $ex ) {
339 throw new UnexpectedValueException(
340 get_class( $oldValue ) . '::serializeForApiResult() returned an invalid value: ' .
341 $ex->getMessage(),
342 0,
343 $ex
344 );
345 }
346 } elseif ( is_callable( [ $value, '__toString' ] ) ) {
347 $value = (string)$value;
348 } else {
349 $value = (array)$value + [ self::META_TYPE => 'assoc' ];
350 }
351 }
352
353 if ( is_string( $value ) ) {
354 // Optimization: avoid querying the service locator for each value.
355 static $contentLanguage = null;
356 if ( !$contentLanguage ) {
357 $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
358 }
359 $value = $contentLanguage->normalize( $value );
360 } elseif ( is_array( $value ) ) {
361 foreach ( $value as $k => $v ) {
362 $value[$k] = self::validateValue( $v );
363 }
364 } elseif ( $value !== null && !is_scalar( $value ) ) {
365 $type = gettype( $value );
366 // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.is_resource
367 if ( is_resource( $value ) ) {
368 $type .= '(' . get_resource_type( $value ) . ')';
369 }
370 throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
371 } elseif ( is_float( $value ) && !is_finite( $value ) ) {
372 throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
373 }
374
375 return $value;
376 }
377
394 public function addValue( $path, $name, $value, $flags = 0 ) {
395 $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
396
397 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
398 // self::size needs the validated value. Then flag
399 // to not re-validate later.
400 $value = self::validateValue( $value );
401 $flags |= self::NO_VALIDATE;
402
403 $newsize = $this->size + self::size( $value );
404 if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
405 $this->errorFormatter->addWarning(
406 'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
407 );
408 return false;
409 }
410 $this->size = $newsize;
411 }
412
413 self::setValue( $arr, $name, $value, $flags );
414 return true;
415 }
416
423 public static function unsetValue( array &$arr, $name ) {
424 $ret = null;
425 if ( isset( $arr[$name] ) ) {
426 $ret = $arr[$name];
427 unset( $arr[$name] );
428 }
429 return $ret;
430 }
431
442 public function removeValue( $path, $name, $flags = 0 ) {
443 $path = (array)$path;
444 if ( $name === null ) {
445 if ( !$path ) {
446 throw new InvalidArgumentException( 'Cannot remove the data root' );
447 }
448 $name = array_pop( $path );
449 }
450 $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
451 if ( !( $flags & self::NO_SIZE_CHECK ) ) {
452 $newsize = $this->size - self::size( $ret );
453 $this->size = max( $newsize, 0 );
454 }
455 return $ret;
456 }
457
467 public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
468 if ( $name === null ) {
469 throw new InvalidArgumentException( 'Content value must be named' );
470 }
471 self::setContentField( $arr, $name, $flags );
472 self::setValue( $arr, $name, $value, $flags );
473 }
474
485 public function addContentValue( $path, $name, $value, $flags = 0 ) {
486 if ( $name === null ) {
487 throw new InvalidArgumentException( 'Content value must be named' );
488 }
489 $this->addContentField( $path, $name, $flags );
490 return $this->addValue( $path, $name, $value, $flags );
491 }
492
500 public function addParsedLimit( $moduleName, $limit ) {
501 // Add value, allowing overwriting
502 $this->addValue( 'limits', $moduleName, $limit,
503 self::OVERRIDE | self::NO_SIZE_CHECK );
504 }
505
506 // endregion -- end of Content
507
508 /***************************************************************************/
509 // region Metadata
520 public static function setContentField( array &$arr, $name, $flags = 0 ) {
521 if ( isset( $arr[self::META_CONTENT] ) &&
522 isset( $arr[$arr[self::META_CONTENT]] ) &&
523 !( $flags & self::OVERRIDE )
524 ) {
525 throw new RuntimeException(
526 "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
527 ' is already set as the content element'
528 );
529 }
530 $arr[self::META_CONTENT] = $name;
531 }
532
541 public function addContentField( $path, $name, $flags = 0 ) {
542 $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
543 self::setContentField( $arr, $name, $flags );
544 }
545
553 public static function setSubelementsList( array &$arr, $names ) {
554 if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
555 $arr[self::META_SUBELEMENTS] = (array)$names;
556 } else {
557 $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
558 }
559 }
560
568 public function addSubelementsList( $path, $names ) {
569 $arr = &$this->path( $path );
570 self::setSubelementsList( $arr, $names );
571 }
572
580 public static function unsetSubelementsList( array &$arr, $names ) {
581 if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
582 $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
583 }
584 }
585
593 public function removeSubelementsList( $path, $names ) {
594 $arr = &$this->path( $path );
595 self::unsetSubelementsList( $arr, $names );
596 }
597
604 public static function setIndexedTagName( array &$arr, $tag ) {
605 if ( !is_string( $tag ) ) {
606 throw new InvalidArgumentException( 'Bad tag name' );
607 }
608 $arr[self::META_INDEXED_TAG_NAME] = $tag;
609 }
610
617 public function addIndexedTagName( $path, $tag ) {
618 $arr = &$this->path( $path );
619 self::setIndexedTagName( $arr, $tag );
620 }
621
629 public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
630 if ( !is_string( $tag ) ) {
631 throw new InvalidArgumentException( 'Bad tag name' );
632 }
633 $arr[self::META_INDEXED_TAG_NAME] = $tag;
634 foreach ( $arr as $k => &$v ) {
635 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
636 self::setIndexedTagNameRecursive( $v, $tag );
637 }
638 }
639 }
640
648 public function addIndexedTagNameRecursive( $path, $tag ) {
649 $arr = &$this->path( $path );
650 self::setIndexedTagNameRecursive( $arr, $tag );
651 }
652
663 public static function setPreserveKeysList( array &$arr, $names ) {
664 if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
665 $arr[self::META_PRESERVE_KEYS] = (array)$names;
666 } else {
667 $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
668 }
669 }
670
678 public function addPreserveKeysList( $path, $names ) {
679 $arr = &$this->path( $path );
680 self::setPreserveKeysList( $arr, $names );
681 }
682
690 public static function unsetPreserveKeysList( array &$arr, $names ) {
691 if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
692 $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
693 }
694 }
695
703 public function removePreserveKeysList( $path, $names ) {
704 $arr = &$this->path( $path );
705 self::unsetPreserveKeysList( $arr, $names );
706 }
707
716 public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
717 if ( !in_array( $type, [
718 'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
719 ], true ) ) {
720 throw new InvalidArgumentException( 'Bad type' );
721 }
722 $arr[self::META_TYPE] = $type;
723 if ( is_string( $kvpKeyName ) ) {
724 $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
725 }
726 }
727
735 public function addArrayType( $path, $tag, $kvpKeyName = null ) {
736 $arr = &$this->path( $path );
737 self::setArrayType( $arr, $tag, $kvpKeyName );
738 }
739
747 public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
748 self::setArrayType( $arr, $type, $kvpKeyName );
749 foreach ( $arr as $k => &$v ) {
750 if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
751 self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
752 }
753 }
754 }
755
763 public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
764 $arr = &$this->path( $path );
765 self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
766 }
767
768 // endregion -- end of Metadata
769
770 /***************************************************************************/
771 // region Utility
780 public static function isMetadataKey( $key ) {
781 // Optimization: This is a very hot and highly optimized code path. Note that ord() only
782 // considers the first character and also works with empty strings and integers.
783 // 95 corresponds to the '_' character.
784 return ord( $key ) === 95;
785 }
786
796 protected static function applyTransformations( array $dataIn, array $transforms ) {
797 $strip = $transforms['Strip'] ?? 'none';
798 if ( $strip === 'base' ) {
799 $transforms['Strip'] = 'none';
800 }
801 $transformTypes = $transforms['Types'] ?? null;
802 if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
803 throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
804 }
805
806 $metadata = [];
807 $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
808
809 if ( isset( $transforms['Custom'] ) ) {
810 if ( !is_callable( $transforms['Custom'] ) ) {
811 throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
812 }
813 call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
814 }
815
816 if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
817 isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
818 !isset( $metadata[self::META_KVP_KEY_NAME] )
819 ) {
820 throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
821 'ApiResult::META_KVP_KEY_NAME metadata item' );
822 }
823
824 // BC transformations
825 $boolKeys = null;
826 if ( isset( $transforms['BC'] ) ) {
827 if ( !is_array( $transforms['BC'] ) ) {
828 throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
829 }
830 if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
831 $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
832 ? array_fill_keys( $metadata[self::META_BC_BOOLS], true )
833 : [];
834 }
835
836 if ( !in_array( 'no*', $transforms['BC'], true ) &&
837 isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
838 ) {
839 $k = $metadata[self::META_CONTENT];
840 $data['*'] = $data[$k];
841 unset( $data[$k] );
842 $metadata[self::META_CONTENT] = '*';
843 }
844
845 if ( !in_array( 'nosub', $transforms['BC'], true ) &&
846 isset( $metadata[self::META_BC_SUBELEMENTS] )
847 ) {
848 foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
849 if ( isset( $data[$k] ) ) {
850 $data[$k] = [
851 '*' => $data[$k],
852 self::META_CONTENT => '*',
853 self::META_TYPE => 'assoc',
854 ];
855 }
856 }
857 }
858
859 if ( isset( $metadata[self::META_TYPE] ) ) {
860 switch ( $metadata[self::META_TYPE] ) {
861 case 'BCarray':
862 case 'BCassoc':
863 $metadata[self::META_TYPE] = 'default';
864 break;
865 case 'BCkvp':
866 $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
867 break;
868 }
869 }
870 }
871
872 // Figure out type, do recursive calls, and do boolean transform if necessary
873 $defaultType = 'array';
874 $maxKey = -1;
875 foreach ( $data as $k => &$v ) {
876 $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
877 if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
878 if ( !$v ) {
879 unset( $data[$k] );
880 continue;
881 }
882 $v = '';
883 }
884 if ( is_string( $k ) ) {
885 $defaultType = 'assoc';
886 } elseif ( $k > $maxKey ) {
887 $maxKey = $k;
888 }
889 }
890 unset( $v );
891
892 // Determine which metadata to keep
893 switch ( $strip ) {
894 case 'all':
895 case 'base':
896 $keepMetadata = [];
897 break;
898 case 'none':
899 $keepMetadata = &$metadata;
900 break;
901 case 'bc':
902 $keepMetadata = array_intersect_key( $metadata, [
903 self::META_INDEXED_TAG_NAME => 1,
904 self::META_SUBELEMENTS => 1,
905 ] );
906 break;
907 default:
908 throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
909 }
910
911 // No type transformation
912 if ( $transformTypes === null ) {
913 return $data + $keepMetadata;
914 }
915
916 if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
917 $defaultType = 'assoc';
918 }
919
920 // Override type, if provided
921 $type = $defaultType;
922 if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
923 $type = $metadata[self::META_TYPE];
924 }
925 if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
926 empty( $transformTypes['ArmorKVP'] )
927 ) {
928 $type = 'assoc';
929 } elseif ( $type === 'BCarray' ) {
930 $type = 'array';
931 } elseif ( $type === 'BCassoc' ) {
932 $type = 'assoc';
933 }
934
935 // Apply transformation
936 switch ( $type ) {
937 case 'assoc':
938 $metadata[self::META_TYPE] = 'assoc';
939 $data += $keepMetadata;
940 return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
941
942 case 'array':
943 ksort( $data );
944 $data = array_values( $data );
945 $metadata[self::META_TYPE] = 'array';
946 return $data + $keepMetadata;
947
948 case 'kvp':
949 case 'BCkvp':
950 $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
951 $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
952 $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
953 $merge = !empty( $metadata[self::META_KVP_MERGE] );
954
955 $ret = [];
956 foreach ( $data as $k => $v ) {
957 if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
958 $vArr = (array)$v;
959 if ( isset( $vArr[self::META_TYPE] ) ) {
960 $mergeType = $vArr[self::META_TYPE];
961 } elseif ( is_object( $v ) ) {
962 $mergeType = 'assoc';
963 } else {
964 $keys = array_keys( $vArr );
965 sort( $keys, SORT_NUMERIC );
966 $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
967 }
968 } else {
969 $mergeType = 'n/a';
970 }
971 if ( $mergeType === 'assoc' ) {
972 $item = $vArr + [
973 $key => $k,
974 ];
975 if ( $strip === 'none' ) {
976 self::setPreserveKeysList( $item, [ $key ] );
977 }
978 } else {
979 $item = [
980 $key => $k,
981 $valKey => $v,
982 ];
983 if ( $strip === 'none' ) {
984 $item += [
985 self::META_PRESERVE_KEYS => [ $key ],
986 self::META_CONTENT => $valKey,
987 self::META_TYPE => 'assoc',
988 ];
989 }
990 }
991 $ret[] = $assocAsObject ? (object)$item : $item;
992 }
993 $metadata[self::META_TYPE] = 'array';
994
995 return $ret + $keepMetadata;
996
997 default:
998 throw new UnexpectedValueException( "Unknown type '$type'" );
999 }
1000 }
1001
1012 public static function stripMetadata( $data ) {
1013 if ( is_array( $data ) || is_object( $data ) ) {
1014 $isObj = is_object( $data );
1015 if ( $isObj ) {
1016 $data = (array)$data;
1017 }
1018 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1019 ? (array)$data[self::META_PRESERVE_KEYS]
1020 : [];
1021 foreach ( $data as $k => $v ) {
1022 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1023 unset( $data[$k] );
1024 } elseif ( is_array( $v ) || is_object( $v ) ) {
1025 $data[$k] = self::stripMetadata( $v );
1026 }
1027 }
1028 if ( $isObj ) {
1029 $data = (object)$data;
1030 }
1031 }
1032 return $data;
1033 }
1034
1046 public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1047 if ( !is_array( $metadata ) ) {
1048 $metadata = [];
1049 }
1050 if ( is_array( $data ) || is_object( $data ) ) {
1051 $isObj = is_object( $data );
1052 if ( $isObj ) {
1053 $data = (array)$data;
1054 }
1055 $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1056 ? (array)$data[self::META_PRESERVE_KEYS]
1057 : [];
1058 foreach ( $data as $k => $v ) {
1059 if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1060 $metadata[$k] = $v;
1061 unset( $data[$k] );
1062 }
1063 }
1064 if ( $isObj ) {
1065 $data = (object)$data;
1066 }
1067 }
1068 return $data;
1069 }
1070
1077 private static function size( $value ) {
1078 $s = 0;
1079 if ( is_array( $value ) ) {
1080 foreach ( $value as $k => $v ) {
1081 if ( !self::isMetadataKey( $k ) ) {
1082 $s += self::size( $v );
1083 }
1084 }
1085 } elseif ( is_scalar( $value ) ) {
1086 $s = strlen( $value );
1087 }
1088
1089 return $s;
1090 }
1091
1103 private function &path( $path, $create = 'append' ) {
1104 $path = (array)$path;
1105 $ret = &$this->data;
1106 foreach ( $path as $i => $k ) {
1107 if ( !isset( $ret[$k] ) ) {
1108 switch ( $create ) {
1109 case 'append':
1110 $ret[$k] = [];
1111 break;
1112 case 'prepend':
1113 $ret = [ $k => [] ] + $ret;
1114 break;
1115 case 'dummy':
1116 $tmp = [];
1117 return $tmp;
1118 default:
1119 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1120 throw new InvalidArgumentException( "Path $fail does not exist" );
1121 }
1122 }
1123 if ( !is_array( $ret[$k] ) ) {
1124 $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1125 throw new InvalidArgumentException( "Path $fail is not an array" );
1126 }
1127 $ret = &$ret[$k];
1128 }
1129 return $ret;
1130 }
1131
1140 public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1141 // Process subarrays and determine if this is a JS [] or {}
1142 $hash = $forceHash;
1143 $maxKey = -1;
1144 $bools = [];
1145 foreach ( $vars as $k => $v ) {
1146 if ( is_array( $v ) || is_object( $v ) ) {
1147 $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1148 } elseif ( is_bool( $v ) ) {
1149 // Better here to use real bools even in BC formats
1150 $bools[] = $k;
1151 }
1152 if ( is_string( $k ) ) {
1153 $hash = true;
1154 } elseif ( $k > $maxKey ) {
1155 $maxKey = $k;
1156 }
1157 }
1158 if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1159 $hash = true;
1160 }
1161
1162 // Set metadata appropriately
1163 if ( $hash ) {
1164 // Get the list of keys we actually care about. Unfortunately, we can't support
1165 // certain keys that conflict with ApiResult metadata.
1166 $keys = array_diff( array_keys( $vars ), [
1167 self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1168 self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1169 ] );
1170
1171 return [
1172 self::META_TYPE => 'kvp',
1173 self::META_KVP_KEY_NAME => 'key',
1174 self::META_PRESERVE_KEYS => $keys,
1175 self::META_BC_BOOLS => $bools,
1176 self::META_INDEXED_TAG_NAME => 'var',
1177 ] + $vars;
1178 } else {
1179 return [
1180 self::META_TYPE => 'array',
1181 self::META_BC_BOOLS => $bools,
1182 self::META_INDEXED_TAG_NAME => 'value',
1183 ] + $vars;
1184 }
1185 }
1186
1195 public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1196 static $dbInfinity;
1197 if ( $dbInfinity === null ) {
1198 $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
1199 }
1200
1201 if ( $expiry === '' || $expiry === null || $expiry === false ||
1202 wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1203 ) {
1204 return $infinity;
1205 } else {
1206 return wfTimestamp( TS_ISO_8601, $expiry );
1207 }
1208 }
1209
1210 // endregion -- end of Utility
1211
1212}
1213
1214/*
1215 * This file uses VisualStudio style region/endregion fold markers which are
1216 * recognised by PHPStorm. If modelines are enabled, the following editor
1217 * configuration will also enable folding in vim, if it is in the last 5 lines
1218 * of the file. We also use "@name" which creates sections in Doxygen.
1219 *
1220 * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
1221 */
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfIsInfinity( $str)
Determine input string is represents as infinity.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Formats errors and warnings for the API, and add them to the associated ApiResult.
This class represents the result of the API operations.
Definition ApiResult.php:35
addArrayType( $path, $tag, $kvpKeyName=null)
Set the array data type for a path.
static unsetSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
static unsetPreserveKeysList(array &$arr, $names)
Don't preserve specified keys.
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
const META_TYPE
Key for the 'type' metadata item.
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
serializeForApiResult()
Allow for adding one ApiResult into another.
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
static setValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name.
static validateValue( $value)
Validate a value for addition to the result.
const META_SUBELEMENTS
Key for the 'subelements' metadata item.
Definition ApiResult.php:78
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
const META_PRESERVE_KEYS
Key for the 'preserve keys' metadata item.
Definition ApiResult.php:84
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
Definition ApiResult.php:58
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
getSize()
Get the size of the result, i.e.
__construct( $maxSize)
static unsetValue(array &$arr, $name)
Remove an output value to the array by name.
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
addPreserveKeysList( $path, $names)
Preserve specified keys.
addParsedLimit( $moduleName, $limit)
Add the numeric limit for a limit=max to the result.
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
const META_CONTENT
Key for the 'content' metadata item.
Definition ApiResult.php:90
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
removeSubelementsList( $path, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
setErrorFormatter(ApiErrorFormatter $formatter)
const OVERRIDE
Override existing value in addValue(), setValue(), and similar functions.
Definition ApiResult.php:41
& path( $path, $create='append')
Return a reference to the internal data at $path.
static setSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
static setArrayTypeRecursive(array &$arr, $type, $kvpKeyName=null)
Set the array data type recursively.
const META_KVP_KEY_NAME
Key for the metadata item whose value specifies the name used for the kvp key in the alternative outp...
removeValue( $path, $name, $flags=0)
Remove value from the output data at the given path.
const ADD_ON_TOP
For addValue(), setValue() and similar functions, if the value does not exist, add it as the first el...
Definition ApiResult.php:49
getResultData( $path=[], $transforms=[])
Get the result data array.
const META_BC_SUBELEMENTS
Key for the 'BC subelements' metadata item.
removePreserveKeysList( $path, $names)
Don't preserve specified keys.
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
const META_INDEXED_TAG_NAME
Key for the 'indexed tag name' metadata item.
Definition ApiResult.php:72
const META_KVP_MERGE
Key for the metadata item that indicates that the KVP key should be added into an assoc value,...
reset()
Clear the current result data.
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
static setIndexedTagNameRecursive(array &$arr, $tag)
Set indexed tag name on $arr and all subarrays.
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
static size( $value)
Get the 'real' size of a result item.
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
const NO_VALIDATE
For addValue(), setValue() and similar functions, do not validate data.
Definition ApiResult.php:66
addContentValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path and mark as META_CONTENT.
addContentField( $path, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
static isMetadataKey( $key)
Test whether a key should be considered metadata.
MediaWikiServices is the service locator for the application scope of MediaWiki.
static numParam( $num)
Definition Message.php:1101
This interface allows for overriding the default conversion applied by ApiResult::validateValue().
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
const DB_REPLICA
Definition defines.php:25