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  } 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  // Work around https://bugs.php.net/bug.php?id=45959 by copying to a temporary
362  // (in this case, foreach gets $k === "1" but $tmp[$k] assigns as if $k === 1)
363  $tmp = [];
364  foreach ( $value as $k => $v ) {
365  $tmp[$k] = self::validateValue( $v );
366  }
367  $value = $tmp;
368  } elseif ( $value !== null && !is_scalar( $value ) ) {
369  $type = gettype( $value );
370  if ( is_resource( $value ) ) {
371  $type .= '(' . get_resource_type( $value ) . ')';
372  }
373  throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
374  } elseif ( is_float( $value ) && !is_finite( $value ) ) {
375  throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
376  }
377 
378  return $value;
379  }
380 
397  public function addValue( $path, $name, $value, $flags = 0 ) {
398  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
399 
400  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
401  // self::size needs the validated value. Then flag
402  // to not re-validate later.
403  $value = self::validateValue( $value );
404  $flags |= self::NO_VALIDATE;
405 
406  $newsize = $this->size + self::size( $value );
407  if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
408  $this->errorFormatter->addWarning(
409  'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
410  );
411  return false;
412  }
413  $this->size = $newsize;
414  }
415 
416  self::setValue( $arr, $name, $value, $flags );
417  return true;
418  }
419 
426  public static function unsetValue( array &$arr, $name ) {
427  $ret = null;
428  if ( isset( $arr[$name] ) ) {
429  $ret = $arr[$name];
430  unset( $arr[$name] );
431  }
432  return $ret;
433  }
434 
445  public function removeValue( $path, $name, $flags = 0 ) {
446  $path = (array)$path;
447  if ( $name === null ) {
448  if ( !$path ) {
449  throw new InvalidArgumentException( 'Cannot remove the data root' );
450  }
451  $name = array_pop( $path );
452  }
453  $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
454  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
455  $newsize = $this->size - self::size( $ret );
456  $this->size = max( $newsize, 0 );
457  }
458  return $ret;
459  }
460 
470  public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
471  if ( $name === null ) {
472  throw new InvalidArgumentException( 'Content value must be named' );
473  }
474  self::setContentField( $arr, $name, $flags );
475  self::setValue( $arr, $name, $value, $flags );
476  }
477 
488  public function addContentValue( $path, $name, $value, $flags = 0 ) {
489  if ( $name === null ) {
490  throw new InvalidArgumentException( 'Content value must be named' );
491  }
492  $this->addContentField( $path, $name, $flags );
493  return $this->addValue( $path, $name, $value, $flags );
494  }
495 
503  public function addParsedLimit( $moduleName, $limit ) {
504  // Add value, allowing overwriting
505  $this->addValue( 'limits', $moduleName, $limit,
506  self::OVERRIDE | self::NO_SIZE_CHECK );
507  }
508 
509  // endregion -- end of Content
510 
511  /***************************************************************************/
512  // region Metadata
523  public static function setContentField( array &$arr, $name, $flags = 0 ) {
524  if ( isset( $arr[self::META_CONTENT] ) &&
525  isset( $arr[$arr[self::META_CONTENT]] ) &&
526  !( $flags & self::OVERRIDE )
527  ) {
528  throw new RuntimeException(
529  "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
530  ' is already set as the content element'
531  );
532  }
533  $arr[self::META_CONTENT] = $name;
534  }
535 
544  public function addContentField( $path, $name, $flags = 0 ) {
545  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
546  self::setContentField( $arr, $name, $flags );
547  }
548 
556  public static function setSubelementsList( array &$arr, $names ) {
557  if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
558  $arr[self::META_SUBELEMENTS] = (array)$names;
559  } else {
560  $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
561  }
562  }
563 
571  public function addSubelementsList( $path, $names ) {
572  $arr = &$this->path( $path );
573  self::setSubelementsList( $arr, $names );
574  }
575 
583  public static function unsetSubelementsList( array &$arr, $names ) {
584  if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
585  $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
586  }
587  }
588 
596  public function removeSubelementsList( $path, $names ) {
597  $arr = &$this->path( $path );
598  self::unsetSubelementsList( $arr, $names );
599  }
600 
607  public static function setIndexedTagName( array &$arr, $tag ) {
608  if ( !is_string( $tag ) ) {
609  throw new InvalidArgumentException( 'Bad tag name' );
610  }
611  $arr[self::META_INDEXED_TAG_NAME] = $tag;
612  }
613 
620  public function addIndexedTagName( $path, $tag ) {
621  $arr = &$this->path( $path );
622  self::setIndexedTagName( $arr, $tag );
623  }
624 
632  public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
633  if ( !is_string( $tag ) ) {
634  throw new InvalidArgumentException( 'Bad tag name' );
635  }
636  $arr[self::META_INDEXED_TAG_NAME] = $tag;
637  foreach ( $arr as $k => &$v ) {
638  if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
640  }
641  }
642  }
643 
651  public function addIndexedTagNameRecursive( $path, $tag ) {
652  $arr = &$this->path( $path );
653  self::setIndexedTagNameRecursive( $arr, $tag );
654  }
655 
666  public static function setPreserveKeysList( array &$arr, $names ) {
667  if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
668  $arr[self::META_PRESERVE_KEYS] = (array)$names;
669  } else {
670  $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
671  }
672  }
673 
681  public function addPreserveKeysList( $path, $names ) {
682  $arr = &$this->path( $path );
683  self::setPreserveKeysList( $arr, $names );
684  }
685 
693  public static function unsetPreserveKeysList( array &$arr, $names ) {
694  if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
695  $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
696  }
697  }
698 
706  public function removePreserveKeysList( $path, $names ) {
707  $arr = &$this->path( $path );
708  self::unsetPreserveKeysList( $arr, $names );
709  }
710 
719  public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
720  if ( !in_array( $type, [
721  'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
722  ], true ) ) {
723  throw new InvalidArgumentException( 'Bad type' );
724  }
725  $arr[self::META_TYPE] = $type;
726  if ( is_string( $kvpKeyName ) ) {
727  $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
728  }
729  }
730 
738  public function addArrayType( $path, $tag, $kvpKeyName = null ) {
739  $arr = &$this->path( $path );
740  self::setArrayType( $arr, $tag, $kvpKeyName );
741  }
742 
750  public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
751  self::setArrayType( $arr, $type, $kvpKeyName );
752  foreach ( $arr as $k => &$v ) {
753  if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
754  self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
755  }
756  }
757  }
758 
766  public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
767  $arr = &$this->path( $path );
768  self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
769  }
770 
771  // endregion -- end of Metadata
772 
773  /***************************************************************************/
774  // region Utility
783  public static function isMetadataKey( $key ) {
784  // Optimization: This is a very hot and highly optimized code path. Note that ord() only
785  // considers the first character and also works with empty strings and integers.
786  // 95 corresponds to the '_' character.
787  return ord( $key ) === 95;
788  }
789 
799  protected static function applyTransformations( array $dataIn, array $transforms ) {
800  $strip = $transforms['Strip'] ?? 'none';
801  if ( $strip === 'base' ) {
802  $transforms['Strip'] = 'none';
803  }
804  $transformTypes = $transforms['Types'] ?? null;
805  if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
806  throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
807  }
808 
809  $metadata = [];
810  $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
811 
812  if ( isset( $transforms['Custom'] ) ) {
813  if ( !is_callable( $transforms['Custom'] ) ) {
814  throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
815  }
816  call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
817  }
818 
819  if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
820  isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
821  !isset( $metadata[self::META_KVP_KEY_NAME] )
822  ) {
823  throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
824  'ApiResult::META_KVP_KEY_NAME metadata item' );
825  }
826 
827  // BC transformations
828  $boolKeys = null;
829  if ( isset( $transforms['BC'] ) ) {
830  if ( !is_array( $transforms['BC'] ) ) {
831  throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
832  }
833  if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
834  $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
835  ? array_fill_keys( $metadata[self::META_BC_BOOLS], true )
836  : [];
837  }
838 
839  if ( !in_array( 'no*', $transforms['BC'], true ) &&
840  isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
841  ) {
842  $k = $metadata[self::META_CONTENT];
843  $data['*'] = $data[$k];
844  unset( $data[$k] );
845  $metadata[self::META_CONTENT] = '*';
846  }
847 
848  if ( !in_array( 'nosub', $transforms['BC'], true ) &&
849  isset( $metadata[self::META_BC_SUBELEMENTS] )
850  ) {
851  foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
852  if ( isset( $data[$k] ) ) {
853  $data[$k] = [
854  '*' => $data[$k],
855  self::META_CONTENT => '*',
856  self::META_TYPE => 'assoc',
857  ];
858  }
859  }
860  }
861 
862  if ( isset( $metadata[self::META_TYPE] ) ) {
863  switch ( $metadata[self::META_TYPE] ) {
864  case 'BCarray':
865  case 'BCassoc':
866  $metadata[self::META_TYPE] = 'default';
867  break;
868  case 'BCkvp':
869  $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
870  break;
871  }
872  }
873  }
874 
875  // Figure out type, do recursive calls, and do boolean transform if necessary
876  $defaultType = 'array';
877  $maxKey = -1;
878  foreach ( $data as $k => &$v ) {
879  $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
880  if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
881  if ( !$v ) {
882  unset( $data[$k] );
883  continue;
884  }
885  $v = '';
886  }
887  if ( is_string( $k ) ) {
888  $defaultType = 'assoc';
889  } elseif ( $k > $maxKey ) {
890  $maxKey = $k;
891  }
892  }
893  unset( $v );
894 
895  // Determine which metadata to keep
896  switch ( $strip ) {
897  case 'all':
898  case 'base':
899  $keepMetadata = [];
900  break;
901  case 'none':
902  $keepMetadata = &$metadata;
903  break;
904  case 'bc':
905  $keepMetadata = array_intersect_key( $metadata, [
906  self::META_INDEXED_TAG_NAME => 1,
907  self::META_SUBELEMENTS => 1,
908  ] );
909  break;
910  default:
911  throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
912  }
913 
914  // No type transformation
915  if ( $transformTypes === null ) {
916  return $data + $keepMetadata;
917  }
918 
919  if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
920  $defaultType = 'assoc';
921  }
922 
923  // Override type, if provided
924  $type = $defaultType;
925  if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
926  $type = $metadata[self::META_TYPE];
927  }
928  if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
929  empty( $transformTypes['ArmorKVP'] )
930  ) {
931  $type = 'assoc';
932  } elseif ( $type === 'BCarray' ) {
933  $type = 'array';
934  } elseif ( $type === 'BCassoc' ) {
935  $type = 'assoc';
936  }
937 
938  // Apply transformation
939  switch ( $type ) {
940  case 'assoc':
941  $metadata[self::META_TYPE] = 'assoc';
942  $data += $keepMetadata;
943  return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
944 
945  case 'array':
946  ksort( $data );
947  $data = array_values( $data );
948  $metadata[self::META_TYPE] = 'array';
949  return $data + $keepMetadata;
950 
951  case 'kvp':
952  case 'BCkvp':
953  $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
954  $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
955  $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
956  $merge = !empty( $metadata[self::META_KVP_MERGE] );
957 
958  $ret = [];
959  foreach ( $data as $k => $v ) {
960  if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
961  $vArr = (array)$v;
962  if ( isset( $vArr[self::META_TYPE] ) ) {
963  $mergeType = $vArr[self::META_TYPE];
964  } elseif ( is_object( $v ) ) {
965  $mergeType = 'assoc';
966  } else {
967  $keys = array_keys( $vArr );
968  sort( $keys, SORT_NUMERIC );
969  $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
970  }
971  } else {
972  $mergeType = 'n/a';
973  }
974  if ( $mergeType === 'assoc' ) {
975  $item = $vArr + [
976  $key => $k,
977  ];
978  if ( $strip === 'none' ) {
979  self::setPreserveKeysList( $item, [ $key ] );
980  }
981  } else {
982  $item = [
983  $key => $k,
984  $valKey => $v,
985  ];
986  if ( $strip === 'none' ) {
987  $item += [
988  self::META_PRESERVE_KEYS => [ $key ],
989  self::META_CONTENT => $valKey,
990  self::META_TYPE => 'assoc',
991  ];
992  }
993  }
994  $ret[] = $assocAsObject ? (object)$item : $item;
995  }
996  $metadata[self::META_TYPE] = 'array';
997 
998  return $ret + $keepMetadata;
999 
1000  default:
1001  throw new UnexpectedValueException( "Unknown type '$type'" );
1002  }
1003  }
1004 
1015  public static function stripMetadata( $data ) {
1016  if ( is_array( $data ) || is_object( $data ) ) {
1017  $isObj = is_object( $data );
1018  if ( $isObj ) {
1019  $data = (array)$data;
1020  }
1021  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1022  ? (array)$data[self::META_PRESERVE_KEYS]
1023  : [];
1024  foreach ( $data as $k => $v ) {
1025  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1026  unset( $data[$k] );
1027  } elseif ( is_array( $v ) || is_object( $v ) ) {
1028  $data[$k] = self::stripMetadata( $v );
1029  }
1030  }
1031  if ( $isObj ) {
1032  $data = (object)$data;
1033  }
1034  }
1035  return $data;
1036  }
1037 
1049  public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1050  if ( !is_array( $metadata ) ) {
1051  $metadata = [];
1052  }
1053  if ( is_array( $data ) || is_object( $data ) ) {
1054  $isObj = is_object( $data );
1055  if ( $isObj ) {
1056  $data = (array)$data;
1057  }
1058  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1059  ? (array)$data[self::META_PRESERVE_KEYS]
1060  : [];
1061  foreach ( $data as $k => $v ) {
1062  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1063  $metadata[$k] = $v;
1064  unset( $data[$k] );
1065  }
1066  }
1067  if ( $isObj ) {
1068  $data = (object)$data;
1069  }
1070  }
1071  return $data;
1072  }
1073 
1080  private static function size( $value ) {
1081  $s = 0;
1082  if ( is_array( $value ) ) {
1083  foreach ( $value as $k => $v ) {
1084  if ( !self::isMetadataKey( $k ) ) {
1085  $s += self::size( $v );
1086  }
1087  }
1088  } elseif ( is_scalar( $value ) ) {
1089  $s = strlen( $value );
1090  }
1091 
1092  return $s;
1093  }
1094 
1106  private function &path( $path, $create = 'append' ) {
1107  $path = (array)$path;
1108  $ret = &$this->data;
1109  foreach ( $path as $i => $k ) {
1110  if ( !isset( $ret[$k] ) ) {
1111  switch ( $create ) {
1112  case 'append':
1113  $ret[$k] = [];
1114  break;
1115  case 'prepend':
1116  $ret = [ $k => [] ] + $ret;
1117  break;
1118  case 'dummy':
1119  $tmp = [];
1120  return $tmp;
1121  default:
1122  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1123  throw new InvalidArgumentException( "Path $fail does not exist" );
1124  }
1125  }
1126  if ( !is_array( $ret[$k] ) ) {
1127  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1128  throw new InvalidArgumentException( "Path $fail is not an array" );
1129  }
1130  $ret = &$ret[$k];
1131  }
1132  return $ret;
1133  }
1134 
1143  public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1144  // Process subarrays and determine if this is a JS [] or {}
1145  $hash = $forceHash;
1146  $maxKey = -1;
1147  $bools = [];
1148  foreach ( $vars as $k => $v ) {
1149  if ( is_array( $v ) || is_object( $v ) ) {
1150  $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1151  } elseif ( is_bool( $v ) ) {
1152  // Better here to use real bools even in BC formats
1153  $bools[] = $k;
1154  }
1155  if ( is_string( $k ) ) {
1156  $hash = true;
1157  } elseif ( $k > $maxKey ) {
1158  $maxKey = $k;
1159  }
1160  }
1161  if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1162  $hash = true;
1163  }
1164 
1165  // Set metadata appropriately
1166  if ( $hash ) {
1167  // Get the list of keys we actually care about. Unfortunately, we can't support
1168  // certain keys that conflict with ApiResult metadata.
1169  $keys = array_diff( array_keys( $vars ), [
1170  self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1171  self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1172  ] );
1173 
1174  return [
1175  self::META_TYPE => 'kvp',
1176  self::META_KVP_KEY_NAME => 'key',
1177  self::META_PRESERVE_KEYS => $keys,
1178  self::META_BC_BOOLS => $bools,
1179  self::META_INDEXED_TAG_NAME => 'var',
1180  ] + $vars;
1181  } else {
1182  return [
1183  self::META_TYPE => 'array',
1184  self::META_BC_BOOLS => $bools,
1185  self::META_INDEXED_TAG_NAME => 'value',
1186  ] + $vars;
1187  }
1188  }
1189 
1198  public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1199  static $dbInfinity;
1200  if ( $dbInfinity === null ) {
1201  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
1202  }
1203 
1204  if ( $expiry === '' || $expiry === null || $expiry === false ||
1205  wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1206  ) {
1207  return $infinity;
1208  } else {
1209  return wfTimestamp( TS_ISO_8601, $expiry );
1210  }
1211  }
1212 
1213  // endregion -- end of Utility
1214 
1215 }
1216 
1217 /*
1218  * This file uses VisualStudio style region/endregion fold markers which are
1219  * recognised by PHPStorm. If modelines are enabled, the following editor
1220  * configuration will also enable folding in vim, if it is in the last 5 lines
1221  * of the file. We also use "@name" which creates sections in Doxygen.
1222  *
1223  * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
1224  */
ApiResult\addPreserveKeysList
addPreserveKeysList( $path, $names)
Preserve specified keys.
Definition: ApiResult.php:681
ApiResult\__construct
__construct( $maxSize)
Definition: ApiResult.php:151
Message\numParam
static numParam( $num)
Definition: Message.php:1103
ApiResult\addIndexedTagName
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:620
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:180
ApiResult\setContentField
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:523
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:1692
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:750
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:544
ApiResult\applyTransformations
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
Definition: ApiResult.php:799
ApiResult\setValue
static setValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name.
Definition: ApiResult.php:277
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:596
ApiResult\addArrayTypeRecursive
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
Definition: ApiResult.php:766
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:488
ApiResult\path
& path( $path, $create='append')
Return a reference to the internal data at $path.
Definition: ApiResult.php:1106
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:738
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:470
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:719
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:503
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:1143
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:2306
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:426
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:1049
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:445
ApiResult\addValue
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
Definition: ApiResult.php:397
ApiResult\setErrorFormatter
setErrorFormatter(ApiErrorFormatter $formatter)
Definition: ApiResult.php:160
ApiResult\unsetPreserveKeysList
static unsetPreserveKeysList(array &$arr, $names)
Don't preserve specified keys.
Definition: ApiResult.php:693
ApiResult\addSubelementsList
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
Definition: ApiResult.php:571
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:607
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
ApiResult\size
static size( $value)
Get the 'real' size of a result item.
Definition: ApiResult.php:1080
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:2615
ApiResult\removePreserveKeysList
removePreserveKeysList( $path, $names)
Don't preserve specified keys.
Definition: ApiResult.php:706
ApiResult\isMetadataKey
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Definition: ApiResult.php:783
ApiErrorFormatter
Formats errors and warnings for the API, and add them to the associated ApiResult.
Definition: ApiErrorFormatter.php:34
ApiResult\$maxSize
$maxSize
Definition: ApiResult.php:145
ApiResult\setPreserveKeysList
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
Definition: ApiResult.php:666
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:583
$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:1198
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:632
ApiResult\stripMetadata
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
Definition: ApiResult.php:1015
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:556
ApiResult\addIndexedTagNameRecursive
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
Definition: ApiResult.php:651
$type
$type
Definition: testCompression.php:52