PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/sites/all/modules/contrib/civicrm/CRM/Utils/Money.php

https://gitlab.com/virtualrealms/d7civicrm
PHP | 284 lines | 91 code | 25 blank | 168 comment | 16 complexity | 05db1639e2ef6c191440e4ddfaf3e3a0 MD5 | raw file
  1. <?php
  2. /*
  3. +--------------------------------------------------------------------+
  4. | CiviCRM version 5 |
  5. +--------------------------------------------------------------------+
  6. | Copyright CiviCRM LLC (c) 2004-2019 |
  7. +--------------------------------------------------------------------+
  8. | This file is a part of CiviCRM. |
  9. | |
  10. | CiviCRM is free software; you can copy, modify, and distribute it |
  11. | under the terms of the GNU Affero General Public License |
  12. | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
  13. | |
  14. | CiviCRM is distributed in the hope that it will be useful, but |
  15. | WITHOUT ANY WARRANTY; without even the implied warranty of |
  16. | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
  17. | See the GNU Affero General Public License for more details. |
  18. | |
  19. | You should have received a copy of the GNU Affero General Public |
  20. | License and the CiviCRM Licensing Exception along |
  21. | with this program; if not, contact CiviCRM LLC |
  22. | at info[AT]civicrm[DOT]org. If you have questions about the |
  23. | GNU Affero General Public License or the licensing of CiviCRM, |
  24. | see the CiviCRM license FAQ at http://civicrm.org/licensing |
  25. +--------------------------------------------------------------------+
  26. */
  27. /**
  28. *
  29. * @package CRM
  30. * @copyright CiviCRM LLC (c) 2004-2019
  31. */
  32. /**
  33. * Money utilties
  34. */
  35. class CRM_Utils_Money {
  36. public static $_currencySymbols = NULL;
  37. /**
  38. * Format a monetary string.
  39. *
  40. * Format a monetary string basing on the amount provided,
  41. * ISO currency code provided and a format string consisting of:
  42. *
  43. * %a - the formatted amount
  44. * %C - the currency ISO code (e.g., 'USD') if provided
  45. * %c - the currency symbol (e.g., '$') if available
  46. *
  47. * @param float $amount
  48. * The monetary amount to display (1234.56).
  49. * @param string $currency
  50. * The three-letter ISO currency code ('USD').
  51. * @param string $format
  52. * The desired currency format.
  53. * @param bool $onlyNumber
  54. * @param string $valueFormat
  55. * The desired monetary value display format (e.g. '%!i').
  56. *
  57. * @return string
  58. * formatted monetary string
  59. *
  60. */
  61. public static function format($amount, $currency = NULL, $format = NULL, $onlyNumber = FALSE, $valueFormat = NULL) {
  62. if (CRM_Utils_System::isNull($amount)) {
  63. return '';
  64. }
  65. $config = CRM_Core_Config::singleton();
  66. if (!$format) {
  67. $format = $config->moneyformat;
  68. }
  69. if (!$valueFormat) {
  70. $valueFormat = $config->moneyvalueformat;
  71. }
  72. if ($onlyNumber) {
  73. // money_format() exists only in certain PHP install (CRM-650)
  74. if (is_numeric($amount) and function_exists('money_format')) {
  75. $amount = money_format($valueFormat, $amount);
  76. }
  77. return $amount;
  78. }
  79. if (!self::$_currencySymbols) {
  80. self::$_currencySymbols = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', [
  81. 'keyColumn' => 'name',
  82. 'labelColumn' => 'symbol',
  83. ]);
  84. }
  85. if (!$currency) {
  86. $currency = $config->defaultCurrency;
  87. }
  88. // ensure $currency is a valid currency code
  89. // for backwards-compatibility, also accept one space instead of a currency
  90. if ($currency != ' ' && !array_key_exists($currency, self::$_currencySymbols)) {
  91. throw new CRM_Core_Exception("Invalid currency \"{$currency}\"");
  92. }
  93. $amount = self::formatNumericByFormat($amount, $valueFormat);
  94. // If it contains tags, means that HTML was passed and the
  95. // amount is already converted properly,
  96. // so don't mess with it again.
  97. // @todo deprecate handling for the html tags because .... WTF
  98. if (strpos($amount, '<') === FALSE) {
  99. $amount = self::replaceCurrencySeparators($amount);
  100. }
  101. $replacements = [
  102. '%a' => $amount,
  103. '%C' => $currency,
  104. '%c' => CRM_Utils_Array::value($currency, self::$_currencySymbols, $currency),
  105. ];
  106. return strtr($format, $replacements);
  107. }
  108. /**
  109. * This is a placeholder function for calculating the number of decimal places for a currency.
  110. *
  111. * Currently code assumes 2 decimal places but some currencies (bitcoin, middle eastern) have
  112. * more. By using this function we can signpost the locations where the number of decimal places is
  113. * currency specific for future enhancement.
  114. *
  115. * @param string $currency
  116. *
  117. * @return int
  118. * Number of decimal places.
  119. */
  120. public static function getCurrencyPrecision($currency = NULL) {
  121. return 2;
  122. }
  123. /**
  124. * Subtract currencies using integers instead of floats, to preserve precision
  125. *
  126. * @return float
  127. * Result of subtracting $rightOp from $leftOp to the precision of $currency
  128. */
  129. public static function subtractCurrencies($leftOp, $rightOp, $currency) {
  130. if (is_numeric($leftOp) && is_numeric($rightOp)) {
  131. $precision = pow(10, self::getCurrencyPrecision($currency));
  132. return (($leftOp * $precision) - ($rightOp * $precision)) / $precision;
  133. }
  134. }
  135. /**
  136. * Tests if two currency values are equal, taking into account the currency's
  137. * precision, so that if the difference between the two values is less than
  138. * one more order of magnitude for the precision, then the values are
  139. * considered as equal. So, if the currency has precision of 2 decimal
  140. * points, a difference of more than 0.001 will cause the values to be
  141. * considered as different. Anything less than 0.001 will be considered as
  142. * equal.
  143. *
  144. * Eg.
  145. *
  146. * 1.2312 == 1.2319 with a currency precision of 2 decimal points
  147. * 1.2310 != 1.2320 with a currency precision of 2 decimal points
  148. * 1.3000 != 1.2000 with a currency precision of 2 decimal points
  149. *
  150. * @param $value1
  151. * @param $value2
  152. * @param $currency
  153. *
  154. * @return bool
  155. */
  156. public static function equals($value1, $value2, $currency) {
  157. $precision = 1 / pow(10, self::getCurrencyPrecision($currency) + 1);
  158. $difference = self::subtractCurrencies($value1, $value2, $currency);
  159. if (abs($difference) > $precision) {
  160. return FALSE;
  161. }
  162. return TRUE;
  163. }
  164. /**
  165. * Format money for display (just numeric part) according to the current locale.
  166. *
  167. * This calls the underlying system function but does not handle currency separators.
  168. *
  169. * It's not totally clear when it changes the $amount value but has historical usage.
  170. *
  171. * @param $amount
  172. *
  173. * @return string
  174. */
  175. protected static function formatLocaleNumeric($amount) {
  176. return self::formatNumericByFormat($amount, CRM_Core_Config::singleton()->moneyvalueformat);
  177. }
  178. /**
  179. * Format money for display (just numeric part) according to the current locale with rounding.
  180. *
  181. * At this stage this is conceived as an internal function with the currency wrapper
  182. * functions determining the number of places.
  183. *
  184. * This calls the underlying system function but does not handle currency separators.
  185. *
  186. * It's not totally clear when it changes the $amount value but has historical usage.
  187. *
  188. * @param string $amount
  189. * @param int $numberOfPlaces
  190. *
  191. * @return string
  192. */
  193. protected static function formatLocaleNumericRounded($amount, $numberOfPlaces) {
  194. return self::formatLocaleNumeric(round($amount, $numberOfPlaces));
  195. }
  196. /**
  197. * Format money for display (just numeric part) according to the current locale with rounding.
  198. *
  199. * This handles both rounding & replacement of the currency separators for the locale.
  200. *
  201. * @param string $amount
  202. * @param string $currency
  203. *
  204. * @return string
  205. * Formatted amount.
  206. */
  207. public static function formatLocaleNumericRoundedByCurrency($amount, $currency) {
  208. $amount = self::formatLocaleNumericRounded($amount, self::getCurrencyPrecision($currency));
  209. return self::replaceCurrencySeparators($amount);
  210. }
  211. /**
  212. * Format money for display (just numeric part) according to the current locale with rounding based on the
  213. * default currency for the site.
  214. *
  215. * @param $amount
  216. * @return mixed
  217. */
  218. public static function formatLocaleNumericRoundedForDefaultCurrency($amount) {
  219. return self::formatLocaleNumericRoundedByCurrency($amount, self::getCurrencyPrecision(CRM_Core_Config::singleton()->defaultCurrency));
  220. }
  221. /**
  222. * Replace currency separators.
  223. *
  224. * @param string $amount
  225. *
  226. * @return string
  227. */
  228. protected static function replaceCurrencySeparators($amount) {
  229. $config = CRM_Core_Config::singleton();
  230. $rep = [
  231. ',' => $config->monetaryThousandSeparator,
  232. '.' => $config->monetaryDecimalPoint,
  233. ];
  234. return strtr($amount, $rep);
  235. }
  236. /**
  237. * Format numeric part of currency by the passed in format.
  238. *
  239. * This is envisaged as an internal function, with wrapper functions defining valueFormat
  240. * into easily understood functions / variables and handling separator conversions and
  241. * rounding.
  242. *
  243. * @param string $amount
  244. * @param string $valueFormat
  245. *
  246. * @return string
  247. */
  248. protected static function formatNumericByFormat($amount, $valueFormat) {
  249. // money_format() exists only in certain PHP install (CRM-650)
  250. // setlocale() affects native gettext (CRM-11054, CRM-9976)
  251. if (is_numeric($amount) && function_exists('money_format')) {
  252. $lc = setlocale(LC_MONETARY, 0);
  253. setlocale(LC_MONETARY, 'en_US.utf8', 'en_US', 'en_US.utf8', 'en_US', 'C');
  254. $amount = money_format($valueFormat, $amount);
  255. setlocale(LC_MONETARY, $lc);
  256. }
  257. return $amount;
  258. }
  259. }