MediaWiki  master
ApiResult.php
Go to the documentation of this file.
1 <?php
22 
35 class ApiResult implements ApiSerializable {
36 
41  const OVERRIDE = 1;
42 
49  const ADD_ON_TOP = 2;
50 
58  const NO_SIZE_CHECK = 4;
59 
66  const NO_VALIDATE = self::NO_SIZE_CHECK | 8;
67 
72  const META_INDEXED_TAG_NAME = '_element';
73 
78  const META_SUBELEMENTS = '_subelements';
79 
84  const META_PRESERVE_KEYS = '_preservekeys';
85 
90  const META_CONTENT = '_content';
91 
110  const META_TYPE = '_type';
111 
119  const META_KVP_KEY_NAME = '_kvpkeyname';
120 
129  const META_KVP_MERGE = '_kvpmerge';
130 
136  const META_BC_BOOLS = '_BC_bools';
137 
143  const META_BC_SUBELEMENTS = '_BC_subelements';
144 
145  private $data, $size, $maxSize;
147 
148  // Deprecated fields
150 
155  public function __construct( $maxSize ) {
156  if ( $maxSize instanceof ApiMain ) {
157  wfDeprecated( 'ApiMain to ' . __METHOD__, '1.25' );
158  $this->errorFormatter = $maxSize->getErrorFormatter();
159  $this->mainForContinuation = $maxSize;
160  $maxSize = $maxSize->getConfig()->get( 'APIMaxResultSize' );
161  }
162 
163  $this->maxSize = $maxSize;
164  $this->checkingSize = true;
165  $this->reset();
166  }
167 
173  public function setErrorFormatter( ApiErrorFormatter $formatter ) {
174  $this->errorFormatter = $formatter;
175  }
176 
182  public function serializeForApiResult() {
183  return $this->data;
184  }
185 
186  /************************************************************************/
194  public function reset() {
195  $this->data = [
196  self::META_TYPE => 'assoc', // Usually what's desired
197  ];
198  $this->size = 0;
199  }
200 
254  public function getResultData( $path = [], $transforms = [] ) {
255  $path = (array)$path;
256  if ( !$path ) {
257  return self::applyTransformations( $this->data, $transforms );
258  }
259 
260  $last = array_pop( $path );
261  $ret = &$this->path( $path, 'dummy' );
262  if ( !isset( $ret[$last] ) ) {
263  return null;
264  } elseif ( is_array( $ret[$last] ) ) {
265  return self::applyTransformations( $ret[$last], $transforms );
266  } else {
267  return $ret[$last];
268  }
269  }
270 
275  public function getSize() {
276  return $this->size;
277  }
278 
291  public static function setValue( array &$arr, $name, $value, $flags = 0 ) {
292  if ( ( $flags & self::NO_VALIDATE ) !== self::NO_VALIDATE ) {
293  $value = self::validateValue( $value );
294  }
295 
296  if ( $name === null ) {
297  if ( $flags & self::ADD_ON_TOP ) {
298  array_unshift( $arr, $value );
299  } else {
300  array_push( $arr, $value );
301  }
302  return;
303  }
304 
305  $exists = isset( $arr[$name] );
306  if ( !$exists || ( $flags & self::OVERRIDE ) ) {
307  if ( !$exists && ( $flags & self::ADD_ON_TOP ) ) {
308  $arr = [ $name => $value ] + $arr;
309  } else {
310  $arr[$name] = $value;
311  }
312  } elseif ( is_array( $arr[$name] ) && is_array( $value ) ) {
313  $conflicts = array_intersect_key( $arr[$name], $value );
314  if ( !$conflicts ) {
315  $arr[$name] += $value;
316  } else {
317  $keys = implode( ', ', array_keys( $conflicts ) );
318  throw new RuntimeException(
319  "Conflicting keys ($keys) when attempting to merge element $name"
320  );
321  }
322  } else {
323  throw new RuntimeException(
324  "Attempting to add element $name=$value, existing value is {$arr[$name]}"
325  );
326  }
327  }
328 
334  private static function validateValue( $value ) {
335  if ( is_object( $value ) ) {
336  // Note we use is_callable() here instead of instanceof because
337  // ApiSerializable is an informal protocol (see docs there for details).
338  if ( is_callable( [ $value, 'serializeForApiResult' ] ) ) {
339  $oldValue = $value;
340  $value = $value->serializeForApiResult();
341  if ( is_object( $value ) ) {
342  throw new UnexpectedValueException(
343  get_class( $oldValue ) . '::serializeForApiResult() returned an object of class ' .
344  get_class( $value )
345  );
346  }
347 
348  // Recursive call instead of fall-through so we can throw a
349  // better exception message.
350  try {
351  return self::validateValue( $value );
352  } catch ( Exception $ex ) {
353  throw new UnexpectedValueException(
354  get_class( $oldValue ) . '::serializeForApiResult() returned an invalid value: ' .
355  $ex->getMessage(),
356  0,
357  $ex
358  );
359  }
360  } elseif ( is_callable( [ $value, '__toString' ] ) ) {
361  $value = (string)$value;
362  } else {
363  $value = (array)$value + [ self::META_TYPE => 'assoc' ];
364  }
365  }
366  if ( is_array( $value ) ) {
367  // Work around https://bugs.php.net/bug.php?id=45959 by copying to a temporary
368  // (in this case, foreach gets $k === "1" but $tmp[$k] assigns as if $k === 1)
369  $tmp = [];
370  foreach ( $value as $k => $v ) {
371  $tmp[$k] = self::validateValue( $v );
372  }
373  $value = $tmp;
374  } elseif ( is_float( $value ) && !is_finite( $value ) ) {
375  throw new InvalidArgumentException( 'Cannot add non-finite floats to ApiResult' );
376  } elseif ( is_string( $value ) ) {
377  $value = MediaWikiServices::getInstance()->getContentLanguage()->normalize( $value );
378  } elseif ( $value !== null && !is_scalar( $value ) ) {
379  $type = gettype( $value );
380  if ( is_resource( $value ) ) {
381  $type .= '(' . get_resource_type( $value ) . ')';
382  }
383  throw new InvalidArgumentException( "Cannot add $type to ApiResult" );
384  }
385 
386  return $value;
387  }
388 
405  public function addValue( $path, $name, $value, $flags = 0 ) {
406  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
407 
408  if ( $this->checkingSize && !( $flags & self::NO_SIZE_CHECK ) ) {
409  // self::size needs the validated value. Then flag
410  // to not re-validate later.
411  $value = self::validateValue( $value );
412  $flags |= self::NO_VALIDATE;
413 
414  $newsize = $this->size + self::size( $value );
415  if ( $this->maxSize !== false && $newsize > $this->maxSize ) {
416  $this->errorFormatter->addWarning(
417  'result', [ 'apiwarn-truncatedresult', Message::numParam( $this->maxSize ) ]
418  );
419  return false;
420  }
421  $this->size = $newsize;
422  }
423 
424  self::setValue( $arr, $name, $value, $flags );
425  return true;
426  }
427 
434  public static function unsetValue( array &$arr, $name ) {
435  $ret = null;
436  if ( isset( $arr[$name] ) ) {
437  $ret = $arr[$name];
438  unset( $arr[$name] );
439  }
440  return $ret;
441  }
442 
453  public function removeValue( $path, $name, $flags = 0 ) {
454  $path = (array)$path;
455  if ( $name === null ) {
456  if ( !$path ) {
457  throw new InvalidArgumentException( 'Cannot remove the data root' );
458  }
459  $name = array_pop( $path );
460  }
461  $ret = self::unsetValue( $this->path( $path, 'dummy' ), $name );
462  if ( $this->checkingSize && !( $flags & self::NO_SIZE_CHECK ) ) {
463  $newsize = $this->size - self::size( $ret );
464  $this->size = max( $newsize, 0 );
465  }
466  return $ret;
467  }
468 
478  public static function setContentValue( array &$arr, $name, $value, $flags = 0 ) {
479  if ( $name === null ) {
480  throw new InvalidArgumentException( 'Content value must be named' );
481  }
482  self::setContentField( $arr, $name, $flags );
483  self::setValue( $arr, $name, $value, $flags );
484  }
485 
496  public function addContentValue( $path, $name, $value, $flags = 0 ) {
497  if ( $name === null ) {
498  throw new InvalidArgumentException( 'Content value must be named' );
499  }
500  $this->addContentField( $path, $name, $flags );
501  return $this->addValue( $path, $name, $value, $flags );
502  }
503 
511  public function addParsedLimit( $moduleName, $limit ) {
512  // Add value, allowing overwriting
513  $this->addValue( 'limits', $moduleName, $limit,
514  self::OVERRIDE | self::NO_SIZE_CHECK );
515  }
516 
519  /************************************************************************/
532  public static function setContentField( array &$arr, $name, $flags = 0 ) {
533  if ( isset( $arr[self::META_CONTENT] ) &&
534  isset( $arr[$arr[self::META_CONTENT]] ) &&
535  !( $flags & self::OVERRIDE )
536  ) {
537  throw new RuntimeException(
538  "Attempting to set content element as $name when " . $arr[self::META_CONTENT] .
539  ' is already set as the content element'
540  );
541  }
542  $arr[self::META_CONTENT] = $name;
543  }
544 
553  public function addContentField( $path, $name, $flags = 0 ) {
554  $arr = &$this->path( $path, ( $flags & self::ADD_ON_TOP ) ? 'prepend' : 'append' );
555  self::setContentField( $arr, $name, $flags );
556  }
557 
565  public static function setSubelementsList( array &$arr, $names ) {
566  if ( !isset( $arr[self::META_SUBELEMENTS] ) ) {
567  $arr[self::META_SUBELEMENTS] = (array)$names;
568  } else {
569  $arr[self::META_SUBELEMENTS] = array_merge( $arr[self::META_SUBELEMENTS], (array)$names );
570  }
571  }
572 
580  public function addSubelementsList( $path, $names ) {
581  $arr = &$this->path( $path );
582  self::setSubelementsList( $arr, $names );
583  }
584 
592  public static function unsetSubelementsList( array &$arr, $names ) {
593  if ( isset( $arr[self::META_SUBELEMENTS] ) ) {
594  $arr[self::META_SUBELEMENTS] = array_diff( $arr[self::META_SUBELEMENTS], (array)$names );
595  }
596  }
597 
605  public function removeSubelementsList( $path, $names ) {
606  $arr = &$this->path( $path );
607  self::unsetSubelementsList( $arr, $names );
608  }
609 
616  public static function setIndexedTagName( array &$arr, $tag ) {
617  if ( !is_string( $tag ) ) {
618  throw new InvalidArgumentException( 'Bad tag name' );
619  }
620  $arr[self::META_INDEXED_TAG_NAME] = $tag;
621  }
622 
629  public function addIndexedTagName( $path, $tag ) {
630  $arr = &$this->path( $path );
631  self::setIndexedTagName( $arr, $tag );
632  }
633 
641  public static function setIndexedTagNameRecursive( array &$arr, $tag ) {
642  if ( !is_string( $tag ) ) {
643  throw new InvalidArgumentException( 'Bad tag name' );
644  }
645  $arr[self::META_INDEXED_TAG_NAME] = $tag;
646  foreach ( $arr as $k => &$v ) {
647  if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
648  self::setIndexedTagNameRecursive( $v, $tag );
649  }
650  }
651  }
652 
660  public function addIndexedTagNameRecursive( $path, $tag ) {
661  $arr = &$this->path( $path );
662  self::setIndexedTagNameRecursive( $arr, $tag );
663  }
664 
675  public static function setPreserveKeysList( array &$arr, $names ) {
676  if ( !isset( $arr[self::META_PRESERVE_KEYS] ) ) {
677  $arr[self::META_PRESERVE_KEYS] = (array)$names;
678  } else {
679  $arr[self::META_PRESERVE_KEYS] = array_merge( $arr[self::META_PRESERVE_KEYS], (array)$names );
680  }
681  }
682 
690  public function addPreserveKeysList( $path, $names ) {
691  $arr = &$this->path( $path );
692  self::setPreserveKeysList( $arr, $names );
693  }
694 
702  public static function unsetPreserveKeysList( array &$arr, $names ) {
703  if ( isset( $arr[self::META_PRESERVE_KEYS] ) ) {
704  $arr[self::META_PRESERVE_KEYS] = array_diff( $arr[self::META_PRESERVE_KEYS], (array)$names );
705  }
706  }
707 
715  public function removePreserveKeysList( $path, $names ) {
716  $arr = &$this->path( $path );
717  self::unsetPreserveKeysList( $arr, $names );
718  }
719 
728  public static function setArrayType( array &$arr, $type, $kvpKeyName = null ) {
729  if ( !in_array( $type, [
730  'default', 'array', 'assoc', 'kvp', 'BCarray', 'BCassoc', 'BCkvp'
731  ], true ) ) {
732  throw new InvalidArgumentException( 'Bad type' );
733  }
734  $arr[self::META_TYPE] = $type;
735  if ( is_string( $kvpKeyName ) ) {
736  $arr[self::META_KVP_KEY_NAME] = $kvpKeyName;
737  }
738  }
739 
747  public function addArrayType( $path, $tag, $kvpKeyName = null ) {
748  $arr = &$this->path( $path );
749  self::setArrayType( $arr, $tag, $kvpKeyName );
750  }
751 
759  public static function setArrayTypeRecursive( array &$arr, $type, $kvpKeyName = null ) {
760  self::setArrayType( $arr, $type, $kvpKeyName );
761  foreach ( $arr as $k => &$v ) {
762  if ( !self::isMetadataKey( $k ) && is_array( $v ) ) {
763  self::setArrayTypeRecursive( $v, $type, $kvpKeyName );
764  }
765  }
766  }
767 
775  public function addArrayTypeRecursive( $path, $tag, $kvpKeyName = null ) {
776  $arr = &$this->path( $path );
777  self::setArrayTypeRecursive( $arr, $tag, $kvpKeyName );
778  }
779 
782  /************************************************************************/
793  public static function isMetadataKey( $key ) {
794  return substr( $key, 0, 1 ) === '_';
795  }
796 
806  protected static function applyTransformations( array $dataIn, array $transforms ) {
807  $strip = $transforms['Strip'] ?? 'none';
808  if ( $strip === 'base' ) {
809  $transforms['Strip'] = 'none';
810  }
811  $transformTypes = $transforms['Types'] ?? null;
812  if ( $transformTypes !== null && !is_array( $transformTypes ) ) {
813  throw new InvalidArgumentException( __METHOD__ . ':Value for "Types" must be an array' );
814  }
815 
816  $metadata = [];
817  $data = self::stripMetadataNonRecursive( $dataIn, $metadata );
818 
819  if ( isset( $transforms['Custom'] ) ) {
820  if ( !is_callable( $transforms['Custom'] ) ) {
821  throw new InvalidArgumentException( __METHOD__ . ': Value for "Custom" must be callable' );
822  }
823  call_user_func_array( $transforms['Custom'], [ &$data, &$metadata ] );
824  }
825 
826  if ( ( isset( $transforms['BC'] ) || $transformTypes !== null ) &&
827  isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] === 'BCkvp' &&
828  !isset( $metadata[self::META_KVP_KEY_NAME] )
829  ) {
830  throw new UnexpectedValueException( 'Type "BCkvp" used without setting ' .
831  'ApiResult::META_KVP_KEY_NAME metadata item' );
832  }
833 
834  // BC transformations
835  $boolKeys = null;
836  if ( isset( $transforms['BC'] ) ) {
837  if ( !is_array( $transforms['BC'] ) ) {
838  throw new InvalidArgumentException( __METHOD__ . ':Value for "BC" must be an array' );
839  }
840  if ( !in_array( 'nobool', $transforms['BC'], true ) ) {
841  $boolKeys = isset( $metadata[self::META_BC_BOOLS] )
842  ? array_flip( $metadata[self::META_BC_BOOLS] )
843  : [];
844  }
845 
846  if ( !in_array( 'no*', $transforms['BC'], true ) &&
847  isset( $metadata[self::META_CONTENT] ) && $metadata[self::META_CONTENT] !== '*'
848  ) {
849  $k = $metadata[self::META_CONTENT];
850  $data['*'] = $data[$k];
851  unset( $data[$k] );
852  $metadata[self::META_CONTENT] = '*';
853  }
854 
855  if ( !in_array( 'nosub', $transforms['BC'], true ) &&
856  isset( $metadata[self::META_BC_SUBELEMENTS] )
857  ) {
858  foreach ( $metadata[self::META_BC_SUBELEMENTS] as $k ) {
859  if ( isset( $data[$k] ) ) {
860  $data[$k] = [
861  '*' => $data[$k],
862  self::META_CONTENT => '*',
863  self::META_TYPE => 'assoc',
864  ];
865  }
866  }
867  }
868 
869  if ( isset( $metadata[self::META_TYPE] ) ) {
870  switch ( $metadata[self::META_TYPE] ) {
871  case 'BCarray':
872  case 'BCassoc':
873  $metadata[self::META_TYPE] = 'default';
874  break;
875  case 'BCkvp':
876  $transformTypes['ArmorKVP'] = $metadata[self::META_KVP_KEY_NAME];
877  break;
878  }
879  }
880  }
881 
882  // Figure out type, do recursive calls, and do boolean transform if necessary
883  $defaultType = 'array';
884  $maxKey = -1;
885  foreach ( $data as $k => &$v ) {
886  $v = is_array( $v ) ? self::applyTransformations( $v, $transforms ) : $v;
887  if ( $boolKeys !== null && is_bool( $v ) && !isset( $boolKeys[$k] ) ) {
888  if ( !$v ) {
889  unset( $data[$k] );
890  continue;
891  }
892  $v = '';
893  }
894  if ( is_string( $k ) ) {
895  $defaultType = 'assoc';
896  } elseif ( $k > $maxKey ) {
897  $maxKey = $k;
898  }
899  }
900  unset( $v );
901 
902  // Determine which metadata to keep
903  switch ( $strip ) {
904  case 'all':
905  case 'base':
906  $keepMetadata = [];
907  break;
908  case 'none':
909  $keepMetadata = &$metadata;
910  break;
911  case 'bc':
912  $keepMetadata = array_intersect_key( $metadata, [
913  self::META_INDEXED_TAG_NAME => 1,
914  self::META_SUBELEMENTS => 1,
915  ] );
916  break;
917  default:
918  throw new InvalidArgumentException( __METHOD__ . ': Unknown value for "Strip"' );
919  }
920 
921  // Type transformation
922  if ( $transformTypes !== null ) {
923  if ( $defaultType === 'array' && $maxKey !== count( $data ) - 1 ) {
924  $defaultType = 'assoc';
925  }
926 
927  // Override type, if provided
928  $type = $defaultType;
929  if ( isset( $metadata[self::META_TYPE] ) && $metadata[self::META_TYPE] !== 'default' ) {
930  $type = $metadata[self::META_TYPE];
931  }
932  if ( ( $type === 'kvp' || $type === 'BCkvp' ) &&
933  empty( $transformTypes['ArmorKVP'] )
934  ) {
935  $type = 'assoc';
936  } elseif ( $type === 'BCarray' ) {
937  $type = 'array';
938  } elseif ( $type === 'BCassoc' ) {
939  $type = 'assoc';
940  }
941 
942  // Apply transformation
943  switch ( $type ) {
944  case 'assoc':
945  $metadata[self::META_TYPE] = 'assoc';
946  $data += $keepMetadata;
947  return empty( $transformTypes['AssocAsObject'] ) ? $data : (object)$data;
948 
949  case 'array':
950  ksort( $data );
951  $data = array_values( $data );
952  $metadata[self::META_TYPE] = 'array';
953  return $data + $keepMetadata;
954 
955  case 'kvp':
956  case 'BCkvp':
957  $key = $metadata[self::META_KVP_KEY_NAME] ?? $transformTypes['ArmorKVP'];
958  $valKey = isset( $transforms['BC'] ) ? '*' : 'value';
959  $assocAsObject = !empty( $transformTypes['AssocAsObject'] );
960  $merge = !empty( $metadata[self::META_KVP_MERGE] );
961 
962  $ret = [];
963  foreach ( $data as $k => $v ) {
964  if ( $merge && ( is_array( $v ) || is_object( $v ) ) ) {
965  $vArr = (array)$v;
966  if ( isset( $vArr[self::META_TYPE] ) ) {
967  $mergeType = $vArr[self::META_TYPE];
968  } elseif ( is_object( $v ) ) {
969  $mergeType = 'assoc';
970  } else {
971  $keys = array_keys( $vArr );
972  sort( $keys, SORT_NUMERIC );
973  $mergeType = ( $keys === array_keys( $keys ) ) ? 'array' : 'assoc';
974  }
975  } else {
976  $mergeType = 'n/a';
977  }
978  if ( $mergeType === 'assoc' ) {
979  $item = $vArr + [
980  $key => $k,
981  ];
982  if ( $strip === 'none' ) {
983  self::setPreserveKeysList( $item, [ $key ] );
984  }
985  } else {
986  $item = [
987  $key => $k,
988  $valKey => $v,
989  ];
990  if ( $strip === 'none' ) {
991  $item += [
992  self::META_PRESERVE_KEYS => [ $key ],
993  self::META_CONTENT => $valKey,
994  self::META_TYPE => 'assoc',
995  ];
996  }
997  }
998  $ret[] = $assocAsObject ? (object)$item : $item;
999  }
1000  $metadata[self::META_TYPE] = 'array';
1001 
1002  return $ret + $keepMetadata;
1003 
1004  default:
1005  throw new UnexpectedValueException( "Unknown type '$type'" );
1006  }
1007  } else {
1008  return $data + $keepMetadata;
1009  }
1010  }
1011 
1022  public static function stripMetadata( $data ) {
1023  if ( is_array( $data ) || is_object( $data ) ) {
1024  $isObj = is_object( $data );
1025  if ( $isObj ) {
1026  $data = (array)$data;
1027  }
1028  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1029  ? (array)$data[self::META_PRESERVE_KEYS]
1030  : [];
1031  foreach ( $data as $k => $v ) {
1032  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1033  unset( $data[$k] );
1034  } elseif ( is_array( $v ) || is_object( $v ) ) {
1035  $data[$k] = self::stripMetadata( $v );
1036  }
1037  }
1038  if ( $isObj ) {
1039  $data = (object)$data;
1040  }
1041  }
1042  return $data;
1043  }
1044 
1056  public static function stripMetadataNonRecursive( $data, &$metadata = null ) {
1057  if ( !is_array( $metadata ) ) {
1058  $metadata = [];
1059  }
1060  if ( is_array( $data ) || is_object( $data ) ) {
1061  $isObj = is_object( $data );
1062  if ( $isObj ) {
1063  $data = (array)$data;
1064  }
1065  $preserveKeys = isset( $data[self::META_PRESERVE_KEYS] )
1066  ? (array)$data[self::META_PRESERVE_KEYS]
1067  : [];
1068  foreach ( $data as $k => $v ) {
1069  if ( self::isMetadataKey( $k ) && !in_array( $k, $preserveKeys, true ) ) {
1070  $metadata[$k] = $v;
1071  unset( $data[$k] );
1072  }
1073  }
1074  if ( $isObj ) {
1075  $data = (object)$data;
1076  }
1077  }
1078  return $data;
1079  }
1080 
1087  private static function size( $value ) {
1088  $s = 0;
1089  if ( is_array( $value ) ) {
1090  foreach ( $value as $k => $v ) {
1091  if ( !self::isMetadataKey( $k ) ) {
1092  $s += self::size( $v );
1093  }
1094  }
1095  } elseif ( is_scalar( $value ) ) {
1096  $s = strlen( $value );
1097  }
1098 
1099  return $s;
1100  }
1101 
1113  private function &path( $path, $create = 'append' ) {
1114  $path = (array)$path;
1115  $ret = &$this->data;
1116  foreach ( $path as $i => $k ) {
1117  if ( !isset( $ret[$k] ) ) {
1118  switch ( $create ) {
1119  case 'append':
1120  $ret[$k] = [];
1121  break;
1122  case 'prepend':
1123  $ret = [ $k => [] ] + $ret;
1124  break;
1125  case 'dummy':
1126  $tmp = [];
1127  return $tmp;
1128  default:
1129  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1130  throw new InvalidArgumentException( "Path $fail does not exist" );
1131  }
1132  }
1133  if ( !is_array( $ret[$k] ) ) {
1134  $fail = implode( '.', array_slice( $path, 0, $i + 1 ) );
1135  throw new InvalidArgumentException( "Path $fail is not an array" );
1136  }
1137  $ret = &$ret[$k];
1138  }
1139  return $ret;
1140  }
1141 
1150  public static function addMetadataToResultVars( $vars, $forceHash = true ) {
1151  // Process subarrays and determine if this is a JS [] or {}
1152  $hash = $forceHash;
1153  $maxKey = -1;
1154  $bools = [];
1155  foreach ( $vars as $k => $v ) {
1156  if ( is_array( $v ) || is_object( $v ) ) {
1157  $vars[$k] = self::addMetadataToResultVars( (array)$v, is_object( $v ) );
1158  } elseif ( is_bool( $v ) ) {
1159  // Better here to use real bools even in BC formats
1160  $bools[] = $k;
1161  }
1162  if ( is_string( $k ) ) {
1163  $hash = true;
1164  } elseif ( $k > $maxKey ) {
1165  $maxKey = $k;
1166  }
1167  }
1168  if ( !$hash && $maxKey !== count( $vars ) - 1 ) {
1169  $hash = true;
1170  }
1171 
1172  // Set metadata appropriately
1173  if ( $hash ) {
1174  // Get the list of keys we actually care about. Unfortunately, we can't support
1175  // certain keys that conflict with ApiResult metadata.
1176  $keys = array_diff( array_keys( $vars ), [
1177  self::META_TYPE, self::META_PRESERVE_KEYS, self::META_KVP_KEY_NAME,
1178  self::META_INDEXED_TAG_NAME, self::META_BC_BOOLS
1179  ] );
1180 
1181  return [
1182  self::META_TYPE => 'kvp',
1183  self::META_KVP_KEY_NAME => 'key',
1184  self::META_PRESERVE_KEYS => $keys,
1185  self::META_BC_BOOLS => $bools,
1186  self::META_INDEXED_TAG_NAME => 'var',
1187  ] + $vars;
1188  } else {
1189  return [
1190  self::META_TYPE => 'array',
1191  self::META_BC_BOOLS => $bools,
1192  self::META_INDEXED_TAG_NAME => 'value',
1193  ] + $vars;
1194  }
1195  }
1196 
1205  public static function formatExpiry( $expiry, $infinity = 'infinity' ) {
1206  static $dbInfinity;
1207  if ( $dbInfinity === null ) {
1208  $dbInfinity = wfGetDB( DB_REPLICA )->getInfinity();
1209  }
1210 
1211  if ( $expiry === '' || $expiry === null || $expiry === false ||
1212  wfIsInfinity( $expiry ) || $expiry === $dbInfinity
1213  ) {
1214  return $infinity;
1215  } else {
1216  return wfTimestamp( TS_ISO_8601, $expiry );
1217  }
1218  }
1219 
1222 }
1223 
Formats errors and warnings for the API, and add them to the associated ApiResult.
setErrorFormatter(ApiErrorFormatter $formatter)
Set the error formatter.
Definition: ApiResult.php:173
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:592
$mainForContinuation
Definition: ApiResult.php:149
addParsedLimit( $moduleName, $limit)
Add the numeric limit for a limit=max to the result.
Definition: ApiResult.php:511
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfIsInfinity( $str)
Determine input string is represents as infinity.
const META_TYPE
Key for the &#39;type&#39; metadata item.
Definition: ApiResult.php:110
static isMetadataKey( $key)
Test whether a key should be considered metadata.
Definition: ApiResult.php:793
addArrayTypeRecursive( $path, $tag, $kvpKeyName=null)
Set the array data type for a path recursively.
Definition: ApiResult.php:775
addValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path.
Definition: ApiResult.php:405
getSize()
Get the size of the result, i.e.
Definition: ApiResult.php:275
const META_PRESERVE_KEYS
Key for the &#39;preserve keys&#39; metadata item.
Definition: ApiResult.php:84
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
static numParam( $num)
Definition: Message.php:1042
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:478
__construct( $maxSize)
Definition: ApiResult.php:155
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:616
static size( $value)
Get the &#39;real&#39; size of a result item.
Definition: ApiResult.php:1087
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1205
serializeForApiResult()
Allow for adding one ApiResult into another.
Definition: ApiResult.php:182
static unsetValue(array &$arr, $name)
Remove an output value to the array by name.
Definition: ApiResult.php:434
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
addContentValue( $path, $name, $value, $flags=0)
Add value to the output data at the given path and mark as META_CONTENT.
Definition: ApiResult.php:496
static stripMetadata( $data)
Recursively remove metadata keys from a data array or object.
Definition: ApiResult.php:1022
removeValue( $path, $name, $flags=0)
Remove value from the output data at the given path.
Definition: ApiResult.php:453
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
Definition: ApiResult.php:1150
addContentField( $path, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:553
static setArrayTypeRecursive(array &$arr, $type, $kvpKeyName=null)
Set the array data type recursively.
Definition: ApiResult.php:759
addPreserveKeysList( $path, $names)
Preserve specified keys.
Definition: ApiResult.php:690
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
static applyTransformations(array $dataIn, array $transforms)
Apply transformations to an array, returning the transformed array.
Definition: ApiResult.php:806
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
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don&#39;t use this unless yo...
Definition: ApiResult.php:58
This interface allows for overriding the default conversion applied by ApiResult::validateValue().
This class represents the result of the API operations.
Definition: ApiResult.php:35
static stripMetadataNonRecursive( $data, &$metadata=null)
Remove metadata keys from a data array or object, non-recursive.
Definition: ApiResult.php:1056
addArrayType( $path, $tag, $kvpKeyName=null)
Set the array data type for a path.
Definition: ApiResult.php:747
const NO_VALIDATE
For addValue(), setValue() and similar functions, do not validate data.
Definition: ApiResult.php:66
removePreserveKeysList( $path, $names)
Don&#39;t preserve specified keys.
Definition: ApiResult.php:715
static setSubelementsList(array &$arr, $names)
Causes the elements with the specified names to be output as subelements rather than attributes...
Definition: ApiResult.php:565
static setIndexedTagNameRecursive(array &$arr, $tag)
Set indexed tag name on $arr and all subarrays.
Definition: ApiResult.php:641
static unsetPreserveKeysList(array &$arr, $names)
Don&#39;t preserve specified keys.
Definition: ApiResult.php:702
addSubelementsList( $path, $names)
Causes the elements with the specified names to be output as subelements rather than attributes...
Definition: ApiResult.php:580
addIndexedTagNameRecursive( $path, $tag)
Set indexed tag name on $path and all subarrays.
Definition: ApiResult.php:660
const META_INDEXED_TAG_NAME
Key for the &#39;indexed tag name&#39; metadata item.
Definition: ApiResult.php:72
static validateValue( $value)
Validate a value for addition to the result.
Definition: ApiResult.php:334
static setValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name.
Definition: ApiResult.php:291
const META_SUBELEMENTS
Key for the &#39;subelements&#39; metadata item.
Definition: ApiResult.php:78
reset()
Clear the current result data.
Definition: ApiResult.php:194
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
addIndexedTagName( $path, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:629
const META_BC_BOOLS
Key for the &#39;BC bools&#39; metadata item.
Definition: ApiResult.php:136
& path( $path, $create='append')
Return a reference to the internal data at $path.
Definition: ApiResult.php:1113
static setPreserveKeysList(array &$arr, $names)
Preserve specified keys.
Definition: ApiResult.php:675
static setContentField(array &$arr, $name, $flags=0)
Set the name of the content field name (META_CONTENT)
Definition: ApiResult.php:532
const META_CONTENT
Key for the &#39;content&#39; metadata item.
Definition: ApiResult.php:90
const DB_REPLICA
Definition: defines.php:25
getResultData( $path=[], $transforms=[])
Get the result data array.
Definition: ApiResult.php:254
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:728
const META_BC_SUBELEMENTS
Key for the &#39;BC subelements&#39; metadata item.
Definition: ApiResult.php:143
const OVERRIDE
Override existing value in addValue(), setValue(), and similar functions.
Definition: ApiResult.php:41
removeSubelementsList( $path, $names)
Causes the elements with the specified names to be output as attributes (when possible) rather than a...
Definition: ApiResult.php:605
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