From d4baf8e5ad2ff5755fc3b2508c013f42404bb791 Mon Sep 17 00:00:00 2001 From: Bradley Taylor Date: Wed, 31 Aug 2022 19:57:29 +0100 Subject: [PATCH] dev/financial#192 Fallback to custom currency object for codes which Brick does not support --- CRM/Utils/Money.php | 46 ++++++++++++++++++++++---- Civi/Core/Format.php | 23 ++++++++----- tests/phpunit/Civi/Core/FormatTest.php | 12 +++++++ 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/CRM/Utils/Money.php b/CRM/Utils/Money.php index 3cc33e4226..dae51abf9e 100644 --- a/CRM/Utils/Money.php +++ b/CRM/Utils/Money.php @@ -15,10 +15,13 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ -use Brick\Money\Money; -use Brick\Money\Context\DefaultContext; -use Brick\Money\Context\CustomContext; use Brick\Math\RoundingMode; +use Brick\Money\Context\CustomContext; +use Brick\Money\Context\DefaultContext; +use Brick\Money\Currency; +use Brick\Money\ISOCurrencyProvider; +use Brick\Money\Money; +use Brick\Money\Exception\UnknownCurrencyException; /** * Money utilties @@ -118,6 +121,31 @@ class CRM_Utils_Money { return 2; } + /** + * Get the currency object for a given + * + * Wrapper around the Brick library to support currency codes which Brick doesn't support + * + * @internal + * @param string $currencyCode + * @return Brick\Money\Currency + */ + public static function getCurrencyObject(string $currencyCode): Currency { + try { + $currency = ISOCurrencyProvider::getInstance()->getCurrency($currencyCode); + } + catch (UnknownCurrencyException $e) { + $currency = new Currency( + $currencyCode, + 0, + $currencyCode, + 2 + ); + } + + return $currency; + } + /** * Subtract currencies using integers instead of floats, to preserve precision * @@ -130,8 +158,9 @@ class CRM_Utils_Money { */ public static function subtractCurrencies($leftOp, $rightOp, $currency) { if (is_numeric($leftOp) && is_numeric($rightOp)) { - $leftMoney = Money::of($leftOp, $currency, new DefaultContext(), RoundingMode::CEILING); - $rightMoney = Money::of($rightOp, $currency, new DefaultContext(), RoundingMode::CEILING); + $currencyObject = self::getCurrencyObject($currency); + $leftMoney = Money::of($leftOp, $currencyObject, new DefaultContext(), RoundingMode::CEILING); + $rightMoney = Money::of($rightOp, $currencyObject, new DefaultContext(), RoundingMode::CEILING); return $leftMoney->minus($rightMoney)->getAmount()->toFloat(); } } @@ -173,7 +202,9 @@ class CRM_Utils_Money { * @throws \Brick\Money\Exception\UnknownCurrencyException */ protected static function formatLocaleNumeric(string $amount, $locale = NULL, $currency = NULL, $numberOfPlaces = 2): string { - $money = Money::of($amount, $currency ?? CRM_Core_Config::singleton()->defaultCurrency, new CustomContext($numberOfPlaces), RoundingMode::HALF_UP); + $currency = $currency ?? CRM_Core_Config::singleton()->defaultCurrency; + $currencyObject = self::getCurrencyObject($currency); + $money = Money::of($amount, $currencyObject, new CustomContext($numberOfPlaces), RoundingMode::HALF_UP); $formatter = new \NumberFormatter($locale ?? CRM_Core_I18n::getLocale(), NumberFormatter::DECIMAL); $formatter->setAttribute(\NumberFormatter::MIN_FRACTION_DIGITS, $numberOfPlaces); return $money->formatWith($formatter); @@ -206,7 +237,8 @@ class CRM_Utils_Money { } return self::formatNumericByFormat($amount, '%!.' . $numberOfPlaces . 'i'); } - $money = Money::of($amount, CRM_Core_Config::singleton()->defaultCurrency, new CustomContext($numberOfPlaces), RoundingMode::HALF_UP); + $currencyObject = self::getCurrencyObject(CRM_Core_Config::singleton()->defaultCurrency); + $money = Money::of($amount, $currencyObject, new CustomContext($numberOfPlaces), RoundingMode::HALF_UP); // @todo - we specify en_US here because we don't want this function to do // currency replacement at the moment because // formatLocaleNumericRoundedByPrecision is doing it and if it diff --git a/Civi/Core/Format.php b/Civi/Core/Format.php index 1686fa3ea9..8a711933f6 100644 --- a/Civi/Core/Format.php +++ b/Civi/Core/Format.php @@ -9,6 +9,7 @@ use Civi; use CRM_Core_Config; use CRM_Core_I18n; use CRM_Utils_Constant; +use CRM_Utils_Money; use NumberFormatter; use Brick\Money\Context\AutoContext; @@ -46,7 +47,8 @@ class Format { global $civicrmLocale; $locale = $civicrmLocale->moneyFormat ?? (Civi::settings()->get('format_locale') ?? CRM_Core_I18n::getLocale()); } - $money = Money::of($amount, $currency, NULL, RoundingMode::HALF_UP); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); + $money = Money::of($amount, $currencyObject, NULL, RoundingMode::HALF_UP); $formatter = $this->getMoneyFormatter($currency, $locale); return $money->formatWith($formatter); } @@ -94,7 +96,8 @@ class Format { return ''; } $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::DECIMAL); - return Money::of($amount, $currency, NULL, RoundingMode::HALF_UP)->formatWith($formatter); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); + return Money::of($amount, $currencyObject, NULL, RoundingMode::HALF_UP)->formatWith($formatter); } /** @@ -121,7 +124,8 @@ class Format { */ public function machineMoney($amount, string $currency = 'USD'): string { $formatter = $this->getMoneyFormatter($currency, 'en_US', NumberFormatter::DECIMAL, [NumberFormatter::GROUPING_USED => FALSE]); - return Money::of($amount, $currency, NULL, RoundingMode::HALF_UP)->formatWith($formatter); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); + return Money::of($amount, $currencyObject, NULL, RoundingMode::HALF_UP)->formatWith($formatter); } /** @@ -143,7 +147,8 @@ class Format { $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::CURRENCY, [ NumberFormatter::MAX_FRACTION_DIGITS => 9, ]); - $money = Money::of($amount, $currency, new AutoContext()); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); + $money = Money::of($amount, $currencyObject, new AutoContext()); return $money->formatWith($formatter); } @@ -166,7 +171,8 @@ class Format { $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::DECIMAL, [ NumberFormatter::MAX_FRACTION_DIGITS => 9, ]); - $money = Money::of($amount, $currency, new AutoContext()); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); + $money = Money::of($amount, $currencyObject, new AutoContext()); return $money->formatWith($formatter); } @@ -214,13 +220,12 @@ class Format { if (!isset(\Civi::$statics[$cacheKey])) { $formatter = new NumberFormatter($locale, $style); + $currencyObject = CRM_Utils_Money::getCurrencyObject($currency); if (!isset($attributes[NumberFormatter::MIN_FRACTION_DIGITS])) { - $attributes[NumberFormatter::MIN_FRACTION_DIGITS] = Currency::of($currency) - ->getDefaultFractionDigits(); + $attributes[NumberFormatter::MIN_FRACTION_DIGITS] = $currencyObject->getDefaultFractionDigits(); } if (!isset($attributes[NumberFormatter::MAX_FRACTION_DIGITS])) { - $attributes[NumberFormatter::MAX_FRACTION_DIGITS] = Currency::of($currency) - ->getDefaultFractionDigits(); + $attributes[NumberFormatter::MAX_FRACTION_DIGITS] = $currencyObject->getDefaultFractionDigits(); } foreach ($attributes as $attribute => $value) { diff --git a/tests/phpunit/Civi/Core/FormatTest.php b/tests/phpunit/Civi/Core/FormatTest.php index 93920d8c85..1438f3b7c8 100644 --- a/tests/phpunit/Civi/Core/FormatTest.php +++ b/tests/phpunit/Civi/Core/FormatTest.php @@ -281,6 +281,18 @@ class FormatTest extends CiviUnitTestCase { 'money_long' => '$0.00', ], ]; + $cases['en_US_ZMK'] = [ + [ + 'amount' => '1234.56', + 'locale' => 'en_US', + 'currency' => 'ZMK', + 'money' => 'ZMK 1,234.56', + 'money_number' => '1,234.56', + 'money_number_long' => '1,234.56', + 'number' => '1,234.56', + 'money_long' => 'ZMK 1,234.56', + ], + ]; return $cases; } -- 2.25.1