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  foreach ( $value as $k => $v ) {
362  $value[$k] = self::validateValue( $v );
363  }
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  } elseif ( is_float( $value ) && !is_finite( $value ) ) {
371  throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
372  }
373 
374  return $value;
375  }
376 
393  public function addValue( $path, $name, $value, $flags = 0 ) {
394  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
395 
396  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
397  // self::size needs the validated value. Then flag
398  // to not re-validate later.
399  $value = self::validateValue( $value );
400  $flags |= self::NO_VALIDATE;
401 
402  $newsize = $this->size + self::size( $value );
403  if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
404  $this->errorFormatter->addWarning(
405  'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
406  );
407  return false;
408  }
409  $this->size = $newsize;
410  }
411 
412  self::setValue( $arr, $name, $value, $flags );
413  return true;
414  }
415 
422  public static function unsetValue( array &$arr, $name ) {
423  $ret = null;
424  if ( isset( $arr[$name] ) ) {
425  $ret = $arr[$name];
426  unset( $arr[$name] );
427  }
428  return $ret;
429  }
430 
441  public function removeValue( $path, $name, $flags = 0 ) {
442  $path = (array)$path;
443  if ( $name === null ) {
444  if ( !$path ) {
445  throw new InvalidArgumentException( 'Cannot remove the data root' );
446  }
447  $name = array_pop( $path );
448  }
449  $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
450  if ( !( $flags & self::NO_SIZE_CHECK ) ) {
451  $newsize = $this->size - self::size( $ret );
452  $this->size = max( $newsize, 0 );
453  }
454  return $ret;
455  }
456 
466  public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
467  if ( $name === null ) {
468  throw new InvalidArgumentException( 'Content value must be named' );
469  }
470  self::setContentField( $arr, $name, $flags );
471  self::setValue( $arr, $name, $value, $flags );
472  }
473 
484  public function addContentValue( $path, $name, $value, $flags = 0 ) {
485  if ( $name === null ) {
486  throw new InvalidArgumentException( 'Content value must be named' );
487  }
488  $this->addContentField( $path, $name, $flags );
489  return $this->addValue( $path, $name, $value, $flags );
490  }
491 
499  public function addParsedLimit( $moduleName, $limit ) {
500  // Add value, allowing overwriting
501  $this->addValue( 'limits', $moduleName, $limit,
502  self::OVERRIDE | self::NO_SIZE_CHECK );
503  }
504 
505  // endregion -- end of Content
506 
507  /***************************************************************************/
508  // region Metadata
519  public static function setContentField( array &$arr, $name, $flags = 0 ) {
520  if ( isset( $arr[self::META_CONTENT] ) &&
521  isset( $arr[$arr[self::META_CONTENT]] ) &&
522  !( $flags & self::OVERRIDE )
523  ) {
524  throw new RuntimeException(
525  "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
526  ' is already set as the content element'
527  );
528  }
529  $arr[self::META_CONTENT] = $name;
530  }
531 
540  public function addContentField( $path, $name, $flags = 0 ) {
541  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
542  self::setContentField( $arr, $name, $flags );
543  }
544 
552  public static function setSubelementsList( array &$arr, $names ) {
553  if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
554  $arr[self::META_SUBELEMENTS] = (array)$names;
555  } else {
556  $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
557  }
558  }
559 
567  public function addSubelementsList( $path, $names ) {
568  $arr = &$this->path( $path );
569  self::setSubelementsList( $arr, $names );
570  }
571 
579  public static function unsetSubelementsList( array &$arr, $names ) {
580  if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
581  $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
582  }
583  }
584 
592  public function removeSubelementsList( $path, $names ) {
593  $arr = &$this->path( $path );
594  self::unsetSubelementsList( $arr, $names );
595  }
596 
603  public static function setIndexedTagName( array &$arr, $tag ) {
604  if ( !is_string( $tag ) ) {
605  throw new InvalidArgumentException( 'Bad tag name' );
606  }
607  $arr[self::META_INDEXED_TAG_NAME] = $tag;
608  }
609 
616  public function addIndexedTagName( $path, $tag ) {
617  $arr = &$this->path( $path );
618  self::setIndexedTagName( $arr, $tag );
619  }
620 
628  public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
629  if ( !is_string( $tag ) ) {
630  throw new InvalidArgumentException( 'Bad tag name' );
631  }
632  $arr[self::META_INDEXED_TAG_NAME] = $tag;
633  foreach ( $arr as $k => &$v ) {
634  if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
636  }
637  }
638  }
639 
647  public function addIndexedTagNameRecursive( $path, $tag ) {
648  $arr = &$this->path( $path );
649  self::setIndexedTagNameRecursive( $arr, $tag );
650  }
651 
662  public static function setPreserveKeysList( array &$arr, $names ) {
663  if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
664  $arr[self::META_PRESERVE_KEYS] = (array)$names;
665  } else {
666  $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
667  }
668  }
669 
677  public function addPreserveKeysList( $path, $names ) {
678  $arr = &$this->path( $path );
679  self::setPreserveKeysList( $arr, $names );
680  }
681 
689  public static function unsetPreserveKeysList( array &$arr, $names ) {
690  if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
691  $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
692  }
693  }
694 
702  public function removePreserveKeysList( $path, $names ) {
703  $arr = &$this->path( $path );
704  self::unsetPreserveKeysList( $arr, $names );
705  }
706 
715  public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
716  if ( !in_array( $type, [
717  'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
718  ], true ) ) {
719  throw new InvalidArgumentException( 'Bad type' );
720  }
721  $arr[self::META_TYPE] = $type;
722  if ( is_string( $kvpKeyName ) ) {
723  $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
724  }
725  }
726 
734  public function addArrayType( $path, $tag, $kvpKeyName = null ) {
735  $arr = &$this->path( $path );
736  self::setArrayType( $arr, $tag, $kvpKeyName );
737  }
738 
746  public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
747  self::setArrayType( $arr, $type, $kvpKeyName );
748  foreach ( $arr as $k => &$v ) {
749  if ( is_array( $v ) && !self::isMetadataKey( $k ) ) {
750  self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
751  }
752  }
753  }
754 
762  public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
763  $arr = &$this->path( $path );
764  self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
765  }
766 
767  // endregion -- end of Metadata
768 
769  /***************************************************************************/
770  // region Utility
779  public static function isMetadataKey( $key ) {
780  // Optimization: This is a very hot and highly optimized code path. Note that ord() only
781  // considers the first character and also works with empty strings and integers.
782  // 95 corresponds to the '_' character.
783  return ord( $key ) === 95;
784  }
785 
795  protected static function applyTransformations( array $dataIn, array $transforms ) {
796  $strip = $transforms['Strip'] ?? 'none';
797  if ( $strip === 'base' ) {
798  $transforms['Strip'] = 'none';
799  }
800  $transformTypes = $transforms['Types'] ?? null;
801  if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
802  throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
803  }
804 
805  $metadata = [];
806  $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
807 
808  if ( isset( $transforms['Custom'] ) ) {
809  if ( !is_callable( $transforms['Custom'] ) ) {
810  throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
811  }
812  call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
813  }
814 
815  if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
816  isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
817  !isset( $metadata[self::META_KVP_KEY_NAME] )
818  ) {
819  throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
820  'ApiResult::META_KVP_KEY_NAME metadata item' );
821  }
822 
823  // BC transformations
824  $boolKeys = null;
825  if ( isset( $transforms['BC'] ) ) {
826  if ( !is_array( $transforms['BC'] ) ) {
827  throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
828  }
829  if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
830  $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
831  ? array_fill_keys( $metadata[self::META_BC_BOOLS], true )
832  : [];
833  }
834 
835  if ( !in_array( 'no*', $transforms['BC'], true ) &&
836  isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
837  ) {
838  $k = $metadata[self::META_CONTENT];
839  $data['*'] = $data[$k];
840  unset( $data[$k] );
841  $metadata[self::META_CONTENT] = '*';
842  }
843 
844  if ( !in_array( 'nosub', $transforms['BC'], true ) &&
845  isset( $metadata[self::META_BC_SUBELEMENTS] )
846  ) {
847  foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
848  if ( isset( $data[$k] ) ) {
849  $data[$k] = [
850  '*' => $data[$k],
851  self::META_CONTENT => '*',
852  self::META_TYPE => 'assoc',
853  ];
854  }
855  }
856  }
857 
858  if ( isset( $metadata[self::META_TYPE] ) ) {
859  switch ( $metadata[self::META_TYPE] ) {
860  case 'BCarray':
861  case 'BCassoc':
862  $metadata[self::META_TYPE] = 'default';
863  break;
864  case 'BCkvp':
865  $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
866  break;
867  }
868  }
869  }
870 
871  // Figure out type, do recursive calls, and do boolean transform if necessary
872  $defaultType = 'array';
873  $maxKey = -1;
874  foreach ( $data as $k => &$v ) {
875  $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
876  if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
877  if ( !$v ) {
878  unset( $data[$k] );
879  continue;
880  }
881  $v = '';
882  }
883  if ( is_string( $k ) ) {
884  $defaultType = 'assoc';
885  } elseif ( $k > $maxKey ) {
886  $maxKey = $k;
887  }
888  }
889  unset( $v );
890 
891  // Determine which metadata to keep
892  switch ( $strip ) {
893  case 'all':
894  case 'base':
895  $keepMetadata = [];
896  break;
897  case 'none':
898  $keepMetadata = &$metadata;
899  break;
900  case 'bc':
901  $keepMetadata = array_intersect_key( $metadata, [
902  self::META_INDEXED_TAG_NAME => 1,
903  self::META_SUBELEMENTS => 1,
904  ] );
905  break;
906  default:
907  throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
908  }
909 
910  // No type transformation
911  if ( $transformTypes === null ) {
912  return $data + $keepMetadata;
913  }
914 
915  if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
916  $defaultType = 'assoc';
917  }
918 
919  // Override type, if provided
920  $type = $defaultType;
921  if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
922  $type = $metadata[self::META_TYPE];
923  }
924  if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
925  empty( $transformTypes['ArmorKVP'] )
926  ) {
927  $type = 'assoc';
928  } elseif ( $type === 'BCarray' ) {
929  $type = 'array';
930  } elseif ( $type === 'BCassoc' ) {
931  $type = 'assoc';
932  }
933 
934  // Apply transformation
935  switch ( $type ) {
936  case 'assoc':
937  $metadata[self::META_TYPE] = 'assoc';
938  $data += $keepMetadata;
939  return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
940 
941  case 'array':
942  ksort( $data );
943  $data = array_values( $data );
944  $metadata[self::META_TYPE] = 'array';
945  return $data + $keepMetadata;
946 
947  case 'kvp':
948  case 'BCkvp':
949  $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
950  $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
951  $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
952  $merge = !empty( $metadata[self::META_KVP_MERGE] );
953 
954  $ret = [];
955  foreach ( $data as $k => $v ) {
956  if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
957  $vArr = (array)$v;
958  if ( isset( $vArr[self::META_TYPE] ) ) {
959  $mergeType = $vArr[self::META_TYPE];
960  } elseif ( is_object( $v ) ) {
961  $mergeType = 'assoc';
962  } else {
963  $keys = array_keys( $vArr );
964  sort( $keys, SORT_NUMERIC );
965  $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
966  }
967  } else {
968  $mergeType = 'n/a';
969  }
970  if ( $mergeType === 'assoc' ) {
971  $item = $vArr + [
972  $key => $k,
973  ];
974  if ( $strip === 'none' ) {
975  self::setPreserveKeysList( $item, [ $key ] );
976  }
977  } else {
978  $item = [
979  $key => $k,
980  $valKey => $v,
981  ];
982  if ( $strip === 'none' ) {
983  $item += [
984  self::META_PRESERVE_KEYS => [ $key ],
985  self::META_CONTENT => $valKey,
986  self::META_TYPE => 'assoc',
987  ];
988  }
989  }
990  $ret[] = $assocAsObject ? (object)$item : $item;
991  }
992  $metadata[self::META_TYPE] = 'array';
993 
994  return $ret + $keepMetadata;
995 
996  default:
997  throw new UnexpectedValueException( "Unknown type '$type'" );
998  }
999  }
1000 
1011  public static function stripMetadata( $data ) {
1012  if ( is_array( $data ) || is_object( $data ) ) {
1013  $isObj = is_object( $data );
1014  if ( $isObj ) {
1015  $data = (array)$data;
1016  }
1017  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1018  ? (array)$data[self::META_PRESERVE_KEYS]
1019  : [];
1020  foreach ( $data as $k => $v ) {
1021  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1022  unset( $data[$k] );
1023  } elseif ( is_array( $v ) || is_object( $v ) ) {
1024  $data[$k] = self::stripMetadata( $v );
1025  }
1026  }
1027  if ( $isObj ) {
1028  $data = (object)$data;
1029  }
1030  }
1031  return $data;
1032  }
1033 
1045  public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1046  if ( !is_array( $metadata ) ) {
1047  $metadata = [];
1048  }
1049  if ( is_array( $data ) || is_object( $data ) ) {
1050  $isObj = is_object( $data );
1051  if ( $isObj ) {
1052  $data = (array)$data;
1053  }
1054  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1055  ? (array)$data[self::META_PRESERVE_KEYS]
1056  : [];
1057  foreach ( $data as $k => $v ) {
1058  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1059  $metadata[$k] = $v;
1060  unset( $data[$k] );
1061  }
1062  }
1063  if ( $isObj ) {
1064  $data = (object)$data;
1065  }
1066  }
1067  return $data;
1068  }
1069 
1076  private static function size( $value ) {
1077  $s = 0;
1078  if ( is_array( $value ) ) {
1079  foreach ( $value as $k => $v ) {
1080  if ( !self::isMetadataKey( $k ) ) {
1081  $s += self::size( $v );
1082  }
1083  }
1084  } elseif ( is_scalar( $value ) ) {
1085  $s = strlen( $value );
1086  }
1087 
1088  return $s;
1089  }
1090 
1102  private function &path( $path, $create = 'append' ) {
1103  $path = (array)$path;
1104  $ret = &$this->data;
1105  foreach ( $path as $i => $k ) {
1106  if ( !isset( $ret[$k] ) ) {
1107  switch ( $create ) {
1108  case 'append':
1109  $ret[$k] = [];
1110  break;
1111  case 'prepend':
1112  $ret = [ $k => [] ] + $ret;
1113  break;
1114  case 'dummy':
1115  $tmp = [];
1116  return $tmp;
1117  default:
1118  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1119  throw new InvalidArgumentException( "Path $fail does not exist" );
1120  }
1121  }
1122  if ( !is_array( $ret[$k] ) ) {
1123  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1124  throw new InvalidArgumentException( "Path $fail is not an array" );
1125  }
1126  $ret = &$ret[$k];
1127  }
1128  return $ret;
1129  }
1130 
1139  public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1140  // Process subarrays and determine if this is a JS [] or {}
1141  $hash = $forceHash;
1142  $maxKey = -1;
1143  $bools = [];
1144  foreach ( $vars as $k => $v ) {
1145  if ( is_array( $v ) || is_object( $v ) ) {
1146  $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1147  } elseif ( is_bool( $v ) ) {
1148  // Better here to use real bools even in BC formats
1149  $bools[] = $k;
1150  }
1151  if ( is_string( $k ) ) {
1152  $hash = true;
1153  } elseif ( $k > $maxKey ) {
1154  $maxKey = $k;
1155  }
1156  }
1157  if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1158  $hash = true;
1159  }
1160 
1161  // Set metadata appropriately
1162  if ( $hash ) {
1163  // Get the list of keys we actually care about. Unfortunately, we can't support
1164  // certain keys that conflict with ApiResult metadata.
1165  $keys = array_diff( array_keys( $vars ), [
1166  self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1167  self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1168  ] );
1169 
1170  return [
1171  self::META_TYPE => 'kvp',
1172  self::META_KVP_KEY_NAME => 'key',
1173  self::META_PRESERVE_KEYS => $keys,
1174  self::META_BC_BOOLS => $bools,
1175  self::META_INDEXED_TAG_NAME => 'var',
1176  ] + $vars;
1177  } else {
1178  return [
1179  self::META_TYPE => 'array',
1180  self::META_BC_BOOLS => $bools,
1181  self::META_INDEXED_TAG_NAME => 'value',
1182  ] + $vars;
1183  }
1184  }
1185 
1194  public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1195  static $dbInfinity;
1196  if ( $dbInfinity === null ) {
1197  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
1198  }
1199 
1200  if ( $expiry === '' || $expiry === null || $expiry === false ||
1201  wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1202  ) {
1203  return $infinity;
1204  } else {
1205  return wfTimestamp( TS_ISO_8601, $expiry );
1206  }
1207  }
1208 
1209  // endregion -- end of Utility
1210 
1211 }
1212 
1213 /*
1214  * This file uses VisualStudio style region/endregion fold markers which are
1215  * recognised by PHPStorm. If modelines are enabled, the following editor
1216  * configuration will also enable folding in vim, if it is in the last 5 lines
1217  * of the file. We also use "@name" which creates sections in Doxygen.
1218  *
1219  * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
1220  */
ApiResult\addPreserveKeysList
addPreserveKeysList( $path, $names)
Preserve specified keys.
Definition: ApiResult.php:677
ApiResult\__construct
__construct( $maxSize)
Definition: ApiResult.php:151
Message\numParam
static numParam( $num)
Definition: Message.php:1127
ApiResult\addIndexedTagName
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
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:202
ApiResult\setContentField
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:519
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:1665
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:746
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:540
ApiResult\applyTransformations
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
Definition: ApiResult.php:795
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:592
ApiResult\addArrayTypeRecursive
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
Definition: ApiResult.php:762
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:484
ApiResult\path
& path( $path, $create='append')
Return a reference to the internal data at $path.
Definition: ApiResult.php:1102
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:734
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:466
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:715
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:499
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:1139
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:2200
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:422
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:1045
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:441
ApiResult\addValue
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
Definition: ApiResult.php:393
ApiResult\setErrorFormatter
setErrorFormatter(ApiErrorFormatter $formatter)
Definition: ApiResult.php:160
ApiResult\unsetPreserveKeysList
static unsetPreserveKeysList(array &$arr, $names)
Don't preserve specified keys.
Definition: ApiResult.php:689
ApiResult\addSubelementsList
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes.
Definition: ApiResult.php:567
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:603
$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:1076
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:2522
ApiResult\removePreserveKeysList
removePreserveKeysList( $path, $names)
Don't preserve specified keys.
Definition: ApiResult.php:702
ApiResult\isMetadataKey
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Definition: ApiResult.php:779
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:662
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:579
$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:1194
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:628
ApiResult\stripMetadata
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
Definition: ApiResult.php:1011
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:552
ApiResult\addIndexedTagNameRecursive
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
Definition: ApiResult.php:647
$type
$type
Definition: testCompression.php:52