MediaWiki  master
ApiResult.php
Go to the documentation of this file.
1 <?php
22 
35 class 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 
145  private $data, $size, $maxSize;
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  } else {
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  if ( is_array( $value ) ) {
353  // Work around https://bugs.php.net/bug.php?id=45959 by copying to a temporary
354  // (in this case, foreach gets $k === "1" but $tmp[$k] assigns as if $k === 1)
355  $tmp = [];
356  foreach ( $value as $k => $v ) {
357  $tmp[$k] = self::validateValue( $v );
358  }
359  $value = $tmp;
360  } elseif ( is_float( $value ) && !is_finite( $value ) ) {
361  throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
362  } elseif ( is_string( $value ) ) {
363  $value = MediaWikiServices::getInstance()->getContentLanguage()->normalize( $value );
364  } elseif ( $value !== null && !is_scalar( $value ) ) {
365  $type = gettype( $value );
366  if ( is_resource( $value ) ) {
367  $type .= '(' . get_resource_type( $value ) . ')';
368  }
369  throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
370  }
371 
372  return $value;
373  }
374 
391  public function addValue( $path, $name, $value, $flags = 0 ) {
392  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
393 
394  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
395  // self::size needs the validated value. Then flag
396  // to not re-validate later.
397  $value = self::validateValue( $value );
398  $flags |= self::NO_VALIDATE;
399 
400  $newsize = $this->size + self::size( $value );
401  if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
402  $this->errorFormatter->addWarning(
403  'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
404  );
405  return false;
406  }
407  $this->size = $newsize;
408  }
409 
410  self::setValue( $arr, $name, $value, $flags );
411  return true;
412  }
413 
420  public static function unsetValue( array &$arr, $name ) {
421  $ret = null;
422  if ( isset( $arr[$name] ) ) {
423  $ret = $arr[$name];
424  unset( $arr[$name] );
425  }
426  return $ret;
427  }
428 
439  public function removeValue( $path, $name, $flags = 0 ) {
440  $path = (array)$path;
441  if ( $name === null ) {
442  if ( !$path ) {
443  throw new InvalidArgumentException( 'Cannot remove the data root' );
444  }
445  $name = array_pop( $path );
446  }
447  $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
448  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
449  $newsize = $this->size - self::size( $ret );
450  $this->size = max( $newsize, 0 );
451  }
452  return $ret;
453  }
454 
464  public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
465  if ( $name === null ) {
466  throw new InvalidArgumentException( 'Content value must be named' );
467  }
468  self::setContentField( $arr, $name, $flags );
469  self::setValue( $arr, $name, $value, $flags );
470  }
471 
482  public function addContentValue( $path, $name, $value, $flags = 0 ) {
483  if ( $name === null ) {
484  throw new InvalidArgumentException( 'Content value must be named' );
485  }
486  $this->addContentField( $path, $name, $flags );
487  return $this->addValue( $path, $name, $value, $flags );
488  }
489 
497  public function addParsedLimit( $moduleName, $limit ) {
498  // Add value, allowing overwriting
499  $this->addValue( 'limits', $moduleName, $limit,
500  self::OVERRIDE | self::NO_SIZE_CHECK );
501  }
502 
503  // endregion -- end of Content
504 
505  /***************************************************************************/
506  // region Metadata
517  public static function setContentField( array &$arr, $name, $flags = 0 ) {
518  if ( isset( $arr[self::META_CONTENT] ) &&
519  isset( $arr[$arr[self::META_CONTENT]] ) &&
520  !( $flags & self::OVERRIDE )
521  ) {
522  throw new RuntimeException(
523  "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
524  ' is already set as the content element'
525  );
526  }
527  $arr[self::META_CONTENT] = $name;
528  }
529 
538  public function addContentField( $path, $name, $flags = 0 ) {
539  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
540  self::setContentField( $arr, $name, $flags );
541  }
542 
550  public static function setSubelementsList( array &$arr, $names ) {
551  if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
552  $arr[self::META_SUBELEMENTS] = (array)$names;
553  } else {
554  $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
555  }
556  }
557 
565  public function addSubelementsList( $path, $names ) {
566  $arr = &$this->path( $path );
567  self::setSubelementsList( $arr, $names );
568  }
569 
577  public static function unsetSubelementsList( array &$arr, $names ) {
578  if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
579  $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
580  }
581  }
582 
590  public function removeSubelementsList( $path, $names ) {
591  $arr = &$this->path( $path );
592  self::unsetSubelementsList( $arr, $names );
593  }
594 
601  public static function setIndexedTagName( array &$arr, $tag ) {
602  if ( !is_string( $tag ) ) {
603  throw new InvalidArgumentException( 'Bad tag name' );
604  }
605  $arr[self::META_INDEXED_TAG_NAME] = $tag;
606  }
607 
614  public function addIndexedTagName( $path, $tag ) {
615  $arr = &$this->path( $path );
616  self::setIndexedTagName( $arr, $tag );
617  }
618 
626  public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
627  if ( !is_string( $tag ) ) {
628  throw new InvalidArgumentException( 'Bad tag name' );
629  }
630  $arr[self::META_INDEXED_TAG_NAME] = $tag;
631  foreach ( $arr as $k => &$v ) {
632  if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
634  }
635  }
636  }
637 
645  public function addIndexedTagNameRecursive( $path, $tag ) {
646  $arr = &$this->path( $path );
647  self::setIndexedTagNameRecursive( $arr, $tag );
648  }
649 
660  public static function setPreserveKeysList( array &$arr, $names ) {
661  if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
662  $arr[self::META_PRESERVE_KEYS] = (array)$names;
663  } else {
664  $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
665  }
666  }
667 
675  public function addPreserveKeysList( $path, $names ) {
676  $arr = &$this->path( $path );
677  self::setPreserveKeysList( $arr, $names );
678  }
679 
687  public static function unsetPreserveKeysList( array &$arr, $names ) {
688  if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
689  $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
690  }
691  }
692 
700  public function removePreserveKeysList( $path, $names ) {
701  $arr = &$this->path( $path );
702  self::unsetPreserveKeysList( $arr, $names );
703  }
704 
713  public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
714  if ( !in_array( $type, [
715  'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
716  ], true ) ) {
717  throw new InvalidArgumentException( 'Bad type' );
718  }
719  $arr[self::META_TYPE] = $type;
720  if ( is_string( $kvpKeyName ) ) {
721  $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
722  }
723  }
724 
732  public function addArrayType( $path, $tag, $kvpKeyName = null ) {
733  $arr = &$this->path( $path );
734  self::setArrayType( $arr, $tag, $kvpKeyName );
735  }
736 
744  public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
745  self::setArrayType( $arr, $type, $kvpKeyName );
746  foreach ( $arr as $k => &$v ) {
747  if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
748  self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
749  }
750  }
751  }
752 
760  public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
761  $arr = &$this->path( $path );
762  self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
763  }
764 
765  // endregion -- end of Metadata
766 
767  /***************************************************************************/
768  // region Utility
777  public static function isMetadataKey( $key ) {
778  return substr( $key, 0, 1 ) === '_';
779  }
780 
790  protected static function applyTransformations( array $dataIn, array $transforms ) {
791  $strip = $transforms['Strip'] ?? 'none';
792  if ( $strip === 'base' ) {
793  $transforms['Strip'] = 'none';
794  }
795  $transformTypes = $transforms['Types'] ?? null;
796  if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
797  throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
798  }
799 
800  $metadata = [];
801  $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
802 
803  if ( isset( $transforms['Custom'] ) ) {
804  if ( !is_callable( $transforms['Custom'] ) ) {
805  throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
806  }
807  call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
808  }
809 
810  if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
811  isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
812  !isset( $metadata[self::META_KVP_KEY_NAME] )
813  ) {
814  throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
815  'ApiResult::META_KVP_KEY_NAME metadata item' );
816  }
817 
818  // BC transformations
819  $boolKeys = null;
820  if ( isset( $transforms['BC'] ) ) {
821  if ( !is_array( $transforms['BC'] ) ) {
822  throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
823  }
824  if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
825  $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
826  ? array_flip( $metadata[self::META_BC_BOOLS] )
827  : [];
828  }
829 
830  if ( !in_array( 'no*', $transforms['BC'], true ) &&
831  isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
832  ) {
833  $k = $metadata[self::META_CONTENT];
834  $data['*'] = $data[$k];
835  unset( $data[$k] );
836  $metadata[self::META_CONTENT] = '*';
837  }
838 
839  if ( !in_array( 'nosub', $transforms['BC'], true ) &&
840  isset( $metadata[self::META_BC_SUBELEMENTS] )
841  ) {
842  foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
843  if ( isset( $data[$k] ) ) {
844  $data[$k] = [
845  '*' => $data[$k],
846  self::META_CONTENT => '*',
847  self::META_TYPE => 'assoc',
848  ];
849  }
850  }
851  }
852 
853  if ( isset( $metadata[self::META_TYPE] ) ) {
854  switch ( $metadata[self::META_TYPE] ) {
855  case 'BCarray':
856  case 'BCassoc':
857  $metadata[self::META_TYPE] = 'default';
858  break;
859  case 'BCkvp':
860  $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
861  break;
862  }
863  }
864  }
865 
866  // Figure out type, do recursive calls, and do boolean transform if necessary
867  $defaultType = 'array';
868  $maxKey = -1;
869  foreach ( $data as $k => &$v ) {
870  $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
871  if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
872  if ( !$v ) {
873  unset( $data[$k] );
874  continue;
875  }
876  $v = '';
877  }
878  if ( is_string( $k ) ) {
879  $defaultType = 'assoc';
880  } elseif ( $k > $maxKey ) {
881  $maxKey = $k;
882  }
883  }
884  unset( $v );
885 
886  // Determine which metadata to keep
887  switch ( $strip ) {
888  case 'all':
889  case 'base':
890  $keepMetadata = [];
891  break;
892  case 'none':
893  $keepMetadata = &$metadata;
894  break;
895  case 'bc':
896  $keepMetadata = array_intersect_key( $metadata, [
897  self::META_INDEXED_TAG_NAME => 1,
898  self::META_SUBELEMENTS => 1,
899  ] );
900  break;
901  default:
902  throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
903  }
904 
905  // Type transformation
906  if ( $transformTypes !== null ) {
907  if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
908  $defaultType = 'assoc';
909  }
910 
911  // Override type, if provided
912  $type = $defaultType;
913  if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
914  $type = $metadata[self::META_TYPE];
915  }
916  if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
917  empty( $transformTypes['ArmorKVP'] )
918  ) {
919  $type = 'assoc';
920  } elseif ( $type === 'BCarray' ) {
921  $type = 'array';
922  } elseif ( $type === 'BCassoc' ) {
923  $type = 'assoc';
924  }
925 
926  // Apply transformation
927  switch ( $type ) {
928  case 'assoc':
929  $metadata[self::META_TYPE] = 'assoc';
930  $data += $keepMetadata;
931  return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
932 
933  case 'array':
934  ksort( $data );
935  $data = array_values( $data );
936  $metadata[self::META_TYPE] = 'array';
937  return $data + $keepMetadata;
938 
939  case 'kvp':
940  case 'BCkvp':
941  $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
942  $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
943  $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
944  $merge = !empty( $metadata[self::META_KVP_MERGE] );
945 
946  $ret = [];
947  foreach ( $data as $k => $v ) {
948  if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
949  $vArr = (array)$v;
950  if ( isset( $vArr[self::META_TYPE] ) ) {
951  $mergeType = $vArr[self::META_TYPE];
952  } elseif ( is_object( $v ) ) {
953  $mergeType = 'assoc';
954  } else {
955  $keys = array_keys( $vArr );
956  sort( $keys, SORT_NUMERIC );
957  $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
958  }
959  } else {
960  $mergeType = 'n/a';
961  }
962  if ( $mergeType === 'assoc' ) {
963  $item = $vArr + [
964  $key => $k,
965  ];
966  if ( $strip === 'none' ) {
967  self::setPreserveKeysList( $item, [ $key ] );
968  }
969  } else {
970  $item = [
971  $key => $k,
972  $valKey => $v,
973  ];
974  if ( $strip === 'none' ) {
975  $item += [
976  self::META_PRESERVE_KEYS => [ $key ],
977  self::META_CONTENT => $valKey,
978  self::META_TYPE => 'assoc',
979  ];
980  }
981  }
982  $ret[] = $assocAsObject ? (object)$item : $item;
983  }
984  $metadata[self::META_TYPE] = 'array';
985 
986  return $ret + $keepMetadata;
987 
988  default:
989  throw new UnexpectedValueException( "Unknown type '$type'" );
990  }
991  } else {
992  return $data + $keepMetadata;
993  }
994  }
995 
1006  public static function stripMetadata( $data ) {
1007  if ( is_array( $data ) || is_object( $data ) ) {
1008  $isObj = is_object( $data );
1009  if ( $isObj ) {
1010  $data = (array)$data;
1011  }
1012  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1013  ? (array)$data[self::META_PRESERVE_KEYS]
1014  : [];
1015  foreach ( $data as $k => $v ) {
1016  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1017  unset( $data[$k] );
1018  } elseif ( is_array( $v ) || is_object( $v ) ) {
1019  $data[$k] = self::stripMetadata( $v );
1020  }
1021  }
1022  if ( $isObj ) {
1023  $data = (object)$data;
1024  }
1025  }
1026  return $data;
1027  }
1028 
1040  public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1041  if ( !is_array( $metadata ) ) {
1042  $metadata = [];
1043  }
1044  if ( is_array( $data ) || is_object( $data ) ) {
1045  $isObj = is_object( $data );
1046  if ( $isObj ) {
1047  $data = (array)$data;
1048  }
1049  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1050  ? (array)$data[self::META_PRESERVE_KEYS]
1051  : [];
1052  foreach ( $data as $k => $v ) {
1053  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1054  $metadata[$k] = $v;
1055  unset( $data[$k] );
1056  }
1057  }
1058  if ( $isObj ) {
1059  $data = (object)$data;
1060  }
1061  }
1062  return $data;
1063  }
1064 
1071  private static function size( $value ) {
1072  $s = 0;
1073  if ( is_array( $value ) ) {
1074  foreach ( $value as $k => $v ) {
1075  if ( !self::isMetadataKey( $k ) ) {
1076  $s += self::size( $v );
1077  }
1078  }
1079  } elseif ( is_scalar( $value ) ) {
1080  $s = strlen( $value );
1081  }
1082 
1083  return $s;
1084  }
1085 
1097  private function &path( $path, $create = 'append' ) {
1098  $path = (array)$path;
1099  $ret = &$this->data;
1100  foreach ( $path as $i => $k ) {
1101  if ( !isset( $ret[$k] ) ) {
1102  switch ( $create ) {
1103  case 'append':
1104  $ret[$k] = [];
1105  break;
1106  case 'prepend':
1107  $ret = [ $k => [] ] + $ret;
1108  break;
1109  case 'dummy':
1110  $tmp = [];
1111  return $tmp;
1112  default:
1113  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1114  throw new InvalidArgumentException( "Path $fail does not exist" );
1115  }
1116  }
1117  if ( !is_array( $ret[$k] ) ) {
1118  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1119  throw new InvalidArgumentException( "Path $fail is not an array" );
1120  }
1121  $ret = &$ret[$k];
1122  }
1123  return $ret;
1124  }
1125 
1134  public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1135  // Process subarrays and determine if this is a JS [] or {}
1136  $hash = $forceHash;
1137  $maxKey = -1;
1138  $bools = [];
1139  foreach ( $vars as $k => $v ) {
1140  if ( is_array( $v ) || is_object( $v ) ) {
1141  $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1142  } elseif ( is_bool( $v ) ) {
1143  // Better here to use real bools even in BC formats
1144  $bools[] = $k;
1145  }
1146  if ( is_string( $k ) ) {
1147  $hash = true;
1148  } elseif ( $k > $maxKey ) {
1149  $maxKey = $k;
1150  }
1151  }
1152  if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1153  $hash = true;
1154  }
1155 
1156  // Set metadata appropriately
1157  if ( $hash ) {
1158  // Get the list of keys we actually care about. Unfortunately, we can't support
1159  // certain keys that conflict with ApiResult metadata.
1160  $keys = array_diff( array_keys( $vars ), [
1161  self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1162  self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1163  ] );
1164 
1165  return [
1166  self::META_TYPE => 'kvp',
1167  self::META_KVP_KEY_NAME => 'key',
1168  self::META_PRESERVE_KEYS => $keys,
1169  self::META_BC_BOOLS => $bools,
1170  self::META_INDEXED_TAG_NAME => 'var',
1171  ] + $vars;
1172  } else {
1173  return [
1174  self::META_TYPE => 'array',
1175  self::META_BC_BOOLS => $bools,
1176  self::META_INDEXED_TAG_NAME => 'value',
1177  ] + $vars;
1178  }
1179  }
1180 
1189  public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1190  static $dbInfinity;
1191  if ( $dbInfinity === null ) {
1192  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
1193  }
1194 
1195  if ( $expiry === '' || $expiry === null || $expiry === false ||
1196  wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1197  ) {
1198  return $infinity;
1199  } else {
1200  return wfTimestamp( TS_ISO_8601, $expiry );
1201  }
1202  }
1203 
1204  // endregion -- end of Utility
1205 
1206 }
1207 
1208 /*
1209  * This file uses VisualStudio style region/endregion fold markers which are
1210  * recognised by PHPStorm. If modelines are enabled, the following editor
1211  * configuration will also enable folding in vim, if it is in the last 5 lines
1212  * of the file. We also use "@name" which creates sections in Doxygen.
1213  *
1214  * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
1215  */
ApiResult\addPreserveKeysList
addPreserveKeysList( $path, $names)
Preserve specified keys.
Definition: ApiResult.php:675
ApiResult\__construct
__construct( $maxSize)
Definition: ApiResult.php:151
Message\numParam
static numParam( $num)
Definition: Message.php:1038
ApiResult\addIndexedTagName
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:614
ApiSerializable
This interface allows for overriding the default conversion applied by ApiResult::validateValue().
Definition: ApiSerializable.php:37
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:166
ApiResult\setContentField
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:517
ApiResult\META_TYPE
const META_TYPE
Key for the 'type' metadata item.
Definition: ApiResult.php:110
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1831
ApiResult\META_BC_SUBELEMENTS
const META_BC_SUBELEMENTS
Key for the 'BC subelements' metadata item.
Definition: ApiResult.php:143
ApiResult\setArrayTypeRecursive
static setArrayTypeRecursive(array &$arr, $type, $kvpKeyName=null)
Set the array data type recursively.
Definition: ApiResult.php:744
ApiResult\META_PRESERVE_KEYS
const META_PRESERVE_KEYS
Key for the 'preserve keys' metadata item.
Definition: ApiResult.php:84
ApiResult\$size
$size
Definition: ApiResult.php:145
ApiResult\addContentField
addContentField( $path, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:538
ApiResult\applyTransformations
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
Definition: ApiResult.php:790
ApiResult\setValue
static setValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name.
Definition: ApiResult.php:277
$s
$s
Definition: mergeMessageFileList.php:186
ApiResult\NO_SIZE_CHECK
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
ApiResult\META_KVP_KEY_NAME
const META_KVP_KEY_NAME
Key for the metadata item whose value specifies the name used for the kvp key in the alternative outp...
Definition: ApiResult.php:119
ApiResult\removeSubelementsList
removeSubelementsList( $path, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
Definition: ApiResult.php:590
ApiResult\addArrayTypeRecursive
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
Definition: ApiResult.php:760
ApiResult\NO_VALIDATE
const NO_VALIDATE
For addValue(), setValue() and similar functions, do not validate data.
Definition: ApiResult.php:66
ApiResult\serializeForApiResult
serializeForApiResult()
Allow for adding one ApiResult into another.
Definition: ApiResult.php:169
ApiResult\addContentValue
addContentValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path and mark as META_CONTENT.
Definition: ApiResult.php:482
ApiResult\path
& path( $path, $create='append')
Return a reference to the internal data at $path.
Definition: ApiResult.php:1097
ApiResult\getSize
getSize()
Get the size of the result, i.e.
Definition: ApiResult.php:261
ApiResult\addArrayType
addArrayType( $path, $tag, $kvpKeyName=null)
Set the array data type for a path.
Definition: ApiResult.php:732
ApiResult\setContentValue
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
Definition: ApiResult.php:464
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:713
ApiResult\META_INDEXED_TAG_NAME
const META_INDEXED_TAG_NAME
Key for the 'indexed tag name' metadata item.
Definition: ApiResult.php:72
ApiResult\addParsedLimit
addParsedLimit( $moduleName, $limit)
Add the numeric limit for a limit=max to the result.
Definition: ApiResult.php:497
ApiResult\addMetadataToResultVars
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
Definition: ApiResult.php:1134
ApiResult
This class represents the result of the API operations.
Definition: ApiResult.php:35
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2466
ApiResult\META_SUBELEMENTS
const META_SUBELEMENTS
Key for the 'subelements' metadata item.
Definition: ApiResult.php:78
ApiResult\unsetValue
static unsetValue(array &$arr, $name)
Remove an output value to the array by name.
Definition: ApiResult.php:420
ApiResult\validateValue
static validateValue( $value)
Validate a value for addition to the result.
Definition: ApiResult.php:320
ApiResult\reset
reset()
Clear the current result data.
Definition: ApiResult.php:180
ApiResult\META_BC_BOOLS
const META_BC_BOOLS
Key for the 'BC bools' metadata item.
Definition: ApiResult.php:136
ApiResult\stripMetadataNonRecursive
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
Definition: ApiResult.php:1040
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ApiResult\$errorFormatter
$errorFormatter
Definition: ApiResult.php:146
ApiResult\removeValue
removeValue( $path, $name, $flags=0)
Remove value from the output data at the given path.
Definition: ApiResult.php:439
ApiResult\addValue
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
Definition: ApiResult.php:391
ApiResult\setErrorFormatter
setErrorFormatter(ApiErrorFormatter $formatter)
Definition: ApiResult.php:160
ApiResult\unsetPreserveKeysList
static unsetPreserveKeysList(array &$arr, $names)
Don't preserve specified keys.
Definition: ApiResult.php:687
ApiResult\addSubelementsList
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
Definition: ApiResult.php:565
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:601
ApiResult\size
static size( $value)
Get the 'real' size of a result item.
Definition: ApiResult.php:1071
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:2763
ApiResult\removePreserveKeysList
removePreserveKeysList( $path, $names)
Don't preserve specified keys.
Definition: ApiResult.php:700
ApiResult\isMetadataKey
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Definition: ApiResult.php:777
ApiErrorFormatter
Formats errors and warnings for the API, and add them to the associated ApiResult.
Definition: ApiErrorFormatter.php:31
ApiResult\$maxSize
$maxSize
Definition: ApiResult.php:145
ApiResult\setPreserveKeysList
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
Definition: ApiResult.php:660
ApiResult\ADD_ON_TOP
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
ApiResult\getResultData
getResultData( $path=[], $transforms=[])
Get the result data array.
Definition: ApiResult.php:240
ApiResult\unsetSubelementsList
static unsetSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
Definition: ApiResult.php:577
$path
$path
Definition: NoLocalSettings.php:25
$keys
$keys
Definition: testCompression.php:72
ApiResult\OVERRIDE
const OVERRIDE
Override existing value in addValue(), setValue(), and similar functions.
Definition: ApiResult.php:41
ApiResult\formatExpiry
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1189
ApiResult\META_KVP_MERGE
const META_KVP_MERGE
Key for the metadata item that indicates that the KVP key should be added into an assoc value,...
Definition: ApiResult.php:129
ApiResult\$data
$data
Definition: ApiResult.php:145
ApiResult\setIndexedTagNameRecursive
static setIndexedTagNameRecursive(array &$arr, $tag)
Set indexed tag name on $arr and all subarrays.
Definition: ApiResult.php:626
ApiResult\stripMetadata
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
Definition: ApiResult.php:1006
ApiResult\META_CONTENT
const META_CONTENT
Key for the 'content' metadata item.
Definition: ApiResult.php:90
ApiResult\setSubelementsList
static setSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
Definition: ApiResult.php:550
ApiResult\addIndexedTagNameRecursive
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
Definition: ApiResult.php:645
$type
$type
Definition: testCompression.php:52