Util.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <?php
  2. namespace Braintree;
  3. use DateTime;
  4. use InvalidArgumentException;
  5. /**
  6. * Braintree Utility methods
  7. * PHP version 5
  8. */
  9. class Util
  10. {
  11. /**
  12. * extracts an attribute and returns an array of objects
  13. *
  14. * extracts the requested element from an array, and converts the contents
  15. * of its child arrays to objects of type $attributeName, or returns
  16. * an array with a single element containing the value of that array element
  17. *
  18. * @param array $attribArray attributes from a search response
  19. * @param string $attributeName indicates which element of the passed array to extract
  20. * @return array array of $attributeName objects, or a single element array
  21. */
  22. public static function extractAttributeAsArray(&$attribArray, $attributeName)
  23. {
  24. if(!isset($attribArray[$attributeName])):
  25. return [];
  26. endif;
  27. // get what should be an array from the passed array
  28. $data = $attribArray[$attributeName];
  29. // set up the class that will be used to convert each array element
  30. $classFactory = self::buildClassName($attributeName) . '::factory';
  31. if(is_array($data)):
  32. // create an object from the data in each element
  33. $objectArray = array_map($classFactory, $data);
  34. else:
  35. return [$data];
  36. endif;
  37. unset($attribArray[$attributeName]);
  38. return $objectArray;
  39. }
  40. /**
  41. * throws an exception based on the type of error
  42. * @param string $statusCode HTTP status code to throw exception from
  43. * @param null|string $message
  44. * @throws Exception multiple types depending on the error
  45. * @return void
  46. */
  47. public static function throwStatusCodeException($statusCode, $message=null)
  48. {
  49. switch($statusCode) {
  50. case 401:
  51. throw new Exception\Authentication();
  52. break;
  53. case 403:
  54. throw new Exception\Authorization($message);
  55. break;
  56. case 404:
  57. throw new Exception\NotFound();
  58. break;
  59. case 426:
  60. throw new Exception\UpgradeRequired();
  61. break;
  62. case 429:
  63. throw new Exception\TooManyRequests();
  64. break;
  65. case 500:
  66. throw new Exception\ServerError();
  67. break;
  68. case 503:
  69. throw new Exception\DownForMaintenance();
  70. break;
  71. default:
  72. throw new Exception\Unexpected('Unexpected HTTP_RESPONSE #' . $statusCode);
  73. break;
  74. }
  75. }
  76. /**
  77. *
  78. * @param string $className
  79. * @param object $resultObj
  80. * @return object returns the passed object if successful
  81. * @throws Exception\ValidationsFailed
  82. */
  83. public static function returnObjectOrThrowException($className, $resultObj)
  84. {
  85. $resultObjName = self::cleanClassName($className);
  86. if ($resultObj->success) {
  87. return $resultObj->$resultObjName;
  88. } else {
  89. throw new Exception\ValidationsFailed();
  90. }
  91. }
  92. /**
  93. * removes the header from a classname
  94. *
  95. * @param string $name ClassName
  96. * @return camelCased classname minus header
  97. */
  98. public static function cleanClassName($name)
  99. {
  100. $classNamesToResponseKeys = [
  101. 'Braintree\CreditCard' => 'creditCard',
  102. 'Braintree_CreditCard' => 'creditCard',
  103. 'Braintree\CreditCardGateway' => 'creditCard',
  104. 'Braintree_CreditCardGateway' => 'creditCard',
  105. 'Braintree\Customer' => 'customer',
  106. 'Braintree_Customer' => 'customer',
  107. 'Braintree\CustomerGateway' => 'customer',
  108. 'Braintree_CustomerGateway' => 'customer',
  109. 'Braintree\Subscription' => 'subscription',
  110. 'Braintree_Subscription' => 'subscription',
  111. 'Braintree\SubscriptionGateway' => 'subscription',
  112. 'Braintree_SubscriptionGateway' => 'subscription',
  113. 'Braintree\Transaction' => 'transaction',
  114. 'Braintree_Transaction' => 'transaction',
  115. 'Braintree\TransactionGateway' => 'transaction',
  116. 'Braintree_TransactionGateway' => 'transaction',
  117. 'Braintree\CreditCardVerification' => 'verification',
  118. 'Braintree_CreditCardVerification' => 'verification',
  119. 'Braintree\CreditCardVerificationGateway' => 'verification',
  120. 'Braintree_CreditCardVerificationGateway' => 'verification',
  121. 'Braintree\AddOn' => 'addOn',
  122. 'Braintree_AddOn' => 'addOn',
  123. 'Braintree\AddOnGateway' => 'addOn',
  124. 'Braintree_AddOnGateway' => 'addOn',
  125. 'Braintree\Discount' => 'discount',
  126. 'Braintree_Discount' => 'discount',
  127. 'Braintree\DiscountGateway' => 'discount',
  128. 'Braintree_DiscountGateway' => 'discount',
  129. 'Braintree\Dispute' => 'dispute',
  130. 'Braintree_Dispute' => 'dispute',
  131. 'Braintree\Dispute\EvidenceDetails' => 'evidence',
  132. 'Braintree_Dispute_EvidenceDetails' => 'evidence',
  133. 'Braintree\DocumentUpload' => 'documentUpload',
  134. 'Braintree_DocumentUpload' => 'doumentUpload',
  135. 'Braintree\Plan' => 'plan',
  136. 'Braintree_Plan' => 'plan',
  137. 'Braintree\PlanGateway' => 'plan',
  138. 'Braintree_PlanGateway' => 'plan',
  139. 'Braintree\Address' => 'address',
  140. 'Braintree_Address' => 'address',
  141. 'Braintree\AddressGateway' => 'address',
  142. 'Braintree_AddressGateway' => 'address',
  143. 'Braintree\SettlementBatchSummary' => 'settlementBatchSummary',
  144. 'Braintree_SettlementBatchSummary' => 'settlementBatchSummary',
  145. 'Braintree\SettlementBatchSummaryGateway' => 'settlementBatchSummary',
  146. 'Braintree_SettlementBatchSummaryGateway' => 'settlementBatchSummary',
  147. 'Braintree\Merchant' => 'merchant',
  148. 'Braintree_Merchant' => 'merchant',
  149. 'Braintree\MerchantGateway' => 'merchant',
  150. 'Braintree_MerchantGateway' => 'merchant',
  151. 'Braintree\MerchantAccount' => 'merchantAccount',
  152. 'Braintree_MerchantAccount' => 'merchantAccount',
  153. 'Braintree\MerchantAccountGateway' => 'merchantAccount',
  154. 'Braintree_MerchantAccountGateway' => 'merchantAccount',
  155. 'Braintree\OAuthCredentials' => 'credentials',
  156. 'Braintree_OAuthCredentials' => 'credentials',
  157. 'Braintree\OAuthResult' => 'result',
  158. 'Braintree_OAuthResult' => 'result',
  159. 'Braintree\PayPalAccount' => 'paypalAccount',
  160. 'Braintree_PayPalAccount' => 'paypalAccount',
  161. 'Braintree\PayPalAccountGateway' => 'paypalAccount',
  162. 'Braintree_PayPalAccountGateway' => 'paypalAccount',
  163. 'Braintree\UsBankAccountVerification' => 'usBankAccountVerification',
  164. 'Braintree_UsBankAccountVerification' => 'usBankAccountVerification',
  165. ];
  166. return $classNamesToResponseKeys[$name];
  167. }
  168. /**
  169. *
  170. * @param string $name className
  171. * @return string ClassName
  172. */
  173. public static function buildClassName($name)
  174. {
  175. $responseKeysToClassNames = [
  176. 'creditCard' => 'Braintree\CreditCard',
  177. 'customer' => 'Braintree\Customer',
  178. 'dispute' => 'Braintree\Dispute',
  179. 'documentUpload' => 'Braintree\DocumentUpload',
  180. 'subscription' => 'Braintree\Subscription',
  181. 'transaction' => 'Braintree\Transaction',
  182. 'verification' => 'Braintree\CreditCardVerification',
  183. 'addOn' => 'Braintree\AddOn',
  184. 'discount' => 'Braintree\Discount',
  185. 'plan' => 'Braintree\Plan',
  186. 'address' => 'Braintree\Address',
  187. 'settlementBatchSummary' => 'Braintree\SettlementBatchSummary',
  188. 'merchantAccount' => 'Braintree\MerchantAccount',
  189. ];
  190. return (string) $responseKeysToClassNames[$name];
  191. }
  192. /**
  193. * convert alpha-beta-gamma to alphaBetaGamma
  194. *
  195. * @access public
  196. * @param string $string
  197. * @param null|string $delimiter
  198. * @return string modified string
  199. */
  200. public static function delimiterToCamelCase($string, $delimiter = '[\-\_]')
  201. {
  202. static $callback = null;
  203. if ($callback === null) {
  204. $callback = function ($matches) {
  205. return strtoupper($matches[1]);
  206. };
  207. }
  208. return preg_replace_callback('/' . $delimiter . '(\w)/', $callback, $string);
  209. }
  210. /**
  211. * convert alpha-beta-gamma to alpha_beta_gamma
  212. *
  213. * @access public
  214. * @param string $string
  215. * @return string modified string
  216. */
  217. public static function delimiterToUnderscore($string)
  218. {
  219. return preg_replace('/-/', '_', $string);
  220. }
  221. /**
  222. * find capitals and convert to delimiter + lowercase
  223. *
  224. * @access public
  225. * @param string $string
  226. * @param null|string $delimiter
  227. * @return string modified string
  228. */
  229. public static function camelCaseToDelimiter($string, $delimiter = '-')
  230. {
  231. return strtolower(preg_replace('/([A-Z])/', "$delimiter\\1", $string));
  232. }
  233. public static function delimiterToCamelCaseArray($array, $delimiter = '[\-\_]')
  234. {
  235. $converted = [];
  236. foreach ($array as $key => $value) {
  237. if (is_string($key)) {
  238. $key = self::delimiterToCamelCase($key, $delimiter);
  239. }
  240. if (is_array($value)) {
  241. // Make an exception for custom fields, which must be underscore (can't be
  242. // camelCase).
  243. if ($key === 'customFields') {
  244. $value = self::delimiterToUnderscoreArray($value);
  245. } else {
  246. $value = self::delimiterToCamelCaseArray($value, $delimiter);
  247. }
  248. }
  249. $converted[$key] = $value;
  250. }
  251. return $converted;
  252. }
  253. public static function camelCaseToDelimiterArray($array, $delimiter = '-')
  254. {
  255. $converted = [];
  256. foreach ($array as $key => $value) {
  257. if (is_string($key)) {
  258. $key = self::camelCaseToDelimiter($key, $delimiter);
  259. }
  260. if (is_array($value)) {
  261. $value = self::camelCaseToDelimiterArray($value, $delimiter);
  262. }
  263. $converted[$key] = $value;
  264. }
  265. return $converted;
  266. }
  267. public static function delimiterToUnderscoreArray($array)
  268. {
  269. $converted = [];
  270. foreach ($array as $key => $value) {
  271. $key = self::delimiterToUnderscore($key);
  272. $converted[$key] = $value;
  273. }
  274. return $converted;
  275. }
  276. /**
  277. *
  278. * @param array $array associative array to implode
  279. * @param string $separator (optional, defaults to =)
  280. * @param string $glue (optional, defaults to ', ')
  281. * @return bool
  282. */
  283. public static function implodeAssociativeArray($array, $separator = '=', $glue = ', ')
  284. {
  285. // build a new array with joined keys and values
  286. $tmpArray = null;
  287. foreach ($array AS $key => $value) {
  288. if ($value instanceof DateTime) {
  289. $value = $value->format('r');
  290. }
  291. $tmpArray[] = $key . $separator . $value;
  292. }
  293. // implode and return the new array
  294. return (is_array($tmpArray)) ? implode($glue, $tmpArray) : false;
  295. }
  296. public static function attributesToString($attributes) {
  297. $printableAttribs = [];
  298. foreach ($attributes AS $key => $value) {
  299. if (is_array($value)) {
  300. $pAttrib = self::attributesToString($value);
  301. } else if ($value instanceof DateTime) {
  302. $pAttrib = $value->format(DateTime::RFC850);
  303. } else {
  304. $pAttrib = $value;
  305. }
  306. $printableAttribs[$key] = sprintf('%s', $pAttrib);
  307. }
  308. return self::implodeAssociativeArray($printableAttribs);
  309. }
  310. /**
  311. * verify user request structure
  312. *
  313. * compares the expected signature of a gateway request
  314. * against the actual structure sent by the user
  315. *
  316. * @param array $signature
  317. * @param array $attributes
  318. */
  319. public static function verifyKeys($signature, $attributes)
  320. {
  321. $validKeys = self::_flattenArray($signature);
  322. $userKeys = self::_flattenUserKeys($attributes);
  323. $invalidKeys = array_diff($userKeys, $validKeys);
  324. $invalidKeys = self::_removeWildcardKeys($validKeys, $invalidKeys);
  325. if(!empty($invalidKeys)) {
  326. asort($invalidKeys);
  327. $sortedList = join(', ', $invalidKeys);
  328. throw new InvalidArgumentException('invalid keys: ' . $sortedList);
  329. }
  330. }
  331. /**
  332. * flattens a numerically indexed nested array to a single level
  333. * @param array $keys
  334. * @param string $namespace
  335. * @return array
  336. */
  337. private static function _flattenArray($keys, $namespace = null)
  338. {
  339. $flattenedArray = [];
  340. foreach($keys AS $key) {
  341. if(is_array($key)) {
  342. $theKeys = array_keys($key);
  343. $theValues = array_values($key);
  344. $scope = $theKeys[0];
  345. $fullKey = empty($namespace) ? $scope : $namespace . '[' . $scope . ']';
  346. $flattenedArray = array_merge($flattenedArray, self::_flattenArray($theValues[0], $fullKey));
  347. } else {
  348. $fullKey = empty($namespace) ? $key : $namespace . '[' . $key . ']';
  349. $flattenedArray[] = $fullKey;
  350. }
  351. }
  352. sort($flattenedArray);
  353. return $flattenedArray;
  354. }
  355. private static function _flattenUserKeys($keys, $namespace = null)
  356. {
  357. $flattenedArray = [];
  358. foreach($keys AS $key => $value) {
  359. $fullKey = empty($namespace) ? $key : $namespace;
  360. if (!is_numeric($key) && $namespace != null) {
  361. $fullKey .= '[' . $key . ']';
  362. }
  363. if (is_numeric($key) && is_string($value)) {
  364. $fullKey .= '[' . $value . ']';
  365. }
  366. if(is_array($value)) {
  367. $more = self::_flattenUserKeys($value, $fullKey);
  368. $flattenedArray = array_merge($flattenedArray, $more);
  369. } else {
  370. $flattenedArray[] = $fullKey;
  371. }
  372. }
  373. sort($flattenedArray);
  374. return $flattenedArray;
  375. }
  376. /**
  377. * removes wildcard entries from the invalid keys array
  378. * @param array $validKeys
  379. * @param <array $invalidKeys
  380. * @return array
  381. */
  382. private static function _removeWildcardKeys($validKeys, $invalidKeys)
  383. {
  384. foreach($validKeys AS $key) {
  385. if (stristr($key, '[_anyKey_]')) {
  386. $wildcardKey = str_replace('[_anyKey_]', '', $key);
  387. foreach ($invalidKeys AS $index => $invalidKey) {
  388. if (stristr($invalidKey, $wildcardKey)) {
  389. unset($invalidKeys[$index]);
  390. }
  391. }
  392. }
  393. }
  394. return $invalidKeys;
  395. }
  396. }
  397. class_alias('Braintree\Util', 'Braintree_Util');