Util.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <?php
  2. namespace Stripe\Util;
  3. use Stripe\StripeObject;
  4. abstract class Util
  5. {
  6. private static $isMbstringAvailable = null;
  7. private static $isHashEqualsAvailable = null;
  8. /**
  9. * Whether the provided array (or other) is a list rather than a dictionary.
  10. * A list is defined as an array for which all the keys are consecutive
  11. * integers starting at 0. Empty arrays are considered to be lists.
  12. *
  13. * @param array|mixed $array
  14. *
  15. * @return bool true if the given object is a list
  16. */
  17. public static function isList($array)
  18. {
  19. if (!\is_array($array)) {
  20. return false;
  21. }
  22. if ([] === $array) {
  23. return true;
  24. }
  25. if (\array_keys($array) !== \range(0, \count($array) - 1)) {
  26. return false;
  27. }
  28. return true;
  29. }
  30. /**
  31. * Converts a response from the Stripe API to the corresponding PHP object.
  32. *
  33. * @param array $resp the response from the Stripe API
  34. * @param array $opts
  35. *
  36. * @return array|StripeObject
  37. */
  38. public static function convertToStripeObject($resp, $opts)
  39. {
  40. $types = \Stripe\Util\ObjectTypes::mapping;
  41. if (self::isList($resp)) {
  42. $mapped = [];
  43. foreach ($resp as $i) {
  44. $mapped[] = self::convertToStripeObject($i, $opts);
  45. }
  46. return $mapped;
  47. }
  48. if (\is_array($resp)) {
  49. if (isset($resp['object']) && \is_string($resp['object']) && isset($types[$resp['object']])) {
  50. $class = $types[$resp['object']];
  51. } else {
  52. $class = \Stripe\StripeObject::class;
  53. }
  54. return $class::constructFrom($resp, $opts);
  55. }
  56. return $resp;
  57. }
  58. /**
  59. * @param mixed|string $value a string to UTF8-encode
  60. *
  61. * @return mixed|string the UTF8-encoded string, or the object passed in if
  62. * it wasn't a string
  63. */
  64. public static function utf8($value)
  65. {
  66. if (null === self::$isMbstringAvailable) {
  67. self::$isMbstringAvailable = \function_exists('mb_detect_encoding');
  68. if (!self::$isMbstringAvailable) {
  69. \trigger_error('It looks like the mbstring extension is not enabled. ' .
  70. 'UTF-8 strings will not properly be encoded. Ask your system ' .
  71. 'administrator to enable the mbstring extension, or write to ' .
  72. 'support@stripe.com if you have any questions.', \E_USER_WARNING);
  73. }
  74. }
  75. if (\is_string($value) && self::$isMbstringAvailable && 'UTF-8' !== \mb_detect_encoding($value, 'UTF-8', true)) {
  76. return \utf8_encode($value);
  77. }
  78. return $value;
  79. }
  80. /**
  81. * Compares two strings for equality. The time taken is independent of the
  82. * number of characters that match.
  83. *
  84. * @param string $a one of the strings to compare
  85. * @param string $b the other string to compare
  86. *
  87. * @return bool true if the strings are equal, false otherwise
  88. */
  89. public static function secureCompare($a, $b)
  90. {
  91. if (null === self::$isHashEqualsAvailable) {
  92. self::$isHashEqualsAvailable = \function_exists('hash_equals');
  93. }
  94. if (self::$isHashEqualsAvailable) {
  95. return \hash_equals($a, $b);
  96. }
  97. if (\strlen($a) !== \strlen($b)) {
  98. return false;
  99. }
  100. $result = 0;
  101. for ($i = 0; $i < \strlen($a); ++$i) {
  102. $result |= \ord($a[$i]) ^ \ord($b[$i]);
  103. }
  104. return 0 === $result;
  105. }
  106. /**
  107. * Recursively goes through an array of parameters. If a parameter is an instance of
  108. * ApiResource, then it is replaced by the resource's ID.
  109. * Also clears out null values.
  110. *
  111. * @param mixed $h
  112. *
  113. * @return mixed
  114. */
  115. public static function objectsToIds($h)
  116. {
  117. if ($h instanceof \Stripe\ApiResource) {
  118. return $h->id;
  119. }
  120. if (static::isList($h)) {
  121. $results = [];
  122. foreach ($h as $v) {
  123. $results[] = static::objectsToIds($v);
  124. }
  125. return $results;
  126. }
  127. if (\is_array($h)) {
  128. $results = [];
  129. foreach ($h as $k => $v) {
  130. if (null === $v) {
  131. continue;
  132. }
  133. $results[$k] = static::objectsToIds($v);
  134. }
  135. return $results;
  136. }
  137. return $h;
  138. }
  139. /**
  140. * @param array $params
  141. *
  142. * @return string
  143. */
  144. public static function encodeParameters($params)
  145. {
  146. $flattenedParams = self::flattenParams($params);
  147. $pieces = [];
  148. foreach ($flattenedParams as $param) {
  149. list($k, $v) = $param;
  150. $pieces[] = self::urlEncode($k) . '=' . self::urlEncode($v);
  151. }
  152. return \implode('&', $pieces);
  153. }
  154. /**
  155. * @param array $params
  156. * @param null|string $parentKey
  157. *
  158. * @return array
  159. */
  160. public static function flattenParams($params, $parentKey = null)
  161. {
  162. $result = [];
  163. foreach ($params as $key => $value) {
  164. $calculatedKey = $parentKey ? "{$parentKey}[{$key}]" : $key;
  165. if (self::isList($value)) {
  166. $result = \array_merge($result, self::flattenParamsList($value, $calculatedKey));
  167. } elseif (\is_array($value)) {
  168. $result = \array_merge($result, self::flattenParams($value, $calculatedKey));
  169. } else {
  170. \array_push($result, [$calculatedKey, $value]);
  171. }
  172. }
  173. return $result;
  174. }
  175. /**
  176. * @param array $value
  177. * @param string $calculatedKey
  178. *
  179. * @return array
  180. */
  181. public static function flattenParamsList($value, $calculatedKey)
  182. {
  183. $result = [];
  184. foreach ($value as $i => $elem) {
  185. if (self::isList($elem)) {
  186. $result = \array_merge($result, self::flattenParamsList($elem, $calculatedKey));
  187. } elseif (\is_array($elem)) {
  188. $result = \array_merge($result, self::flattenParams($elem, "{$calculatedKey}[{$i}]"));
  189. } else {
  190. \array_push($result, ["{$calculatedKey}[{$i}]", $elem]);
  191. }
  192. }
  193. return $result;
  194. }
  195. /**
  196. * @param string $key a string to URL-encode
  197. *
  198. * @return string the URL-encoded string
  199. */
  200. public static function urlEncode($key)
  201. {
  202. $s = \urlencode((string) $key);
  203. // Don't use strict form encoding by changing the square bracket control
  204. // characters back to their literals. This is fine by the server, and
  205. // makes these parameter strings easier to read.
  206. $s = \str_replace('%5B', '[', $s);
  207. return \str_replace('%5D', ']', $s);
  208. }
  209. public static function normalizeId($id)
  210. {
  211. if (\is_array($id)) {
  212. $params = $id;
  213. $id = $params['id'];
  214. unset($params['id']);
  215. } else {
  216. $params = [];
  217. }
  218. return [$id, $params];
  219. }
  220. /**
  221. * Returns UNIX timestamp in milliseconds.
  222. *
  223. * @return int current time in millis
  224. */
  225. public static function currentTimeMillis()
  226. {
  227. return (int) \round(\microtime(true) * 1000);
  228. }
  229. }