Commit | Line | Data |
---|---|---|
386fe6c2 EM |
1 | <?php |
2 | ||
3 | namespace Civi\Core; | |
4 | ||
5 | use Brick\Money\Currency; | |
6 | use Brick\Money\Money; | |
7 | use Brick\Math\RoundingMode; | |
8 | use Civi; | |
9 | use CRM_Core_Config; | |
10 | use CRM_Core_I18n; | |
11 | use CRM_Utils_Constant; | |
12 | use NumberFormatter; | |
13 | use Brick\Money\Context\AutoContext; | |
14 | ||
15 | /** | |
16 | * Class Paths | |
17 | * @package Civi\Core | |
18 | * | |
19 | * This class provides standardised formatting | |
20 | */ | |
21 | class Format { | |
22 | ||
23 | /** | |
24 | * Get formatted money | |
25 | * | |
26 | * @param string $amount | |
27 | * @param string|null $currency | |
28 | * Currency, defaults to site currency if not provided. | |
29 | * @param string|null $locale | |
30 | * | |
31 | * @return string | |
32 | * | |
33 | * @noinspection PhpDocMissingThrowsInspection | |
34 | * @noinspection PhpUnhandledExceptionInspection | |
35 | */ | |
36 | public function money(string $amount, ?string $currency = NULL, ?string $locale = NULL): string { | |
37 | if (!$currency) { | |
38 | $currency = Civi::settings()->get('defaultCurrency'); | |
39 | } | |
40 | if (!isset($locale)) { | |
41 | $locale = CRM_Core_I18n::getLocale(); | |
42 | } | |
43 | $money = Money::of($amount, $currency, NULL, RoundingMode::HALF_UP); | |
44 | $formatter = $this->getMoneyFormatter($currency, $locale); | |
45 | return $money->formatWith($formatter); | |
46 | } | |
47 | ||
48 | /** | |
49 | * Get a formatted number. | |
50 | * | |
51 | * @param string|int|float|Money $amount | |
52 | * Amount in a machine money format. | |
53 | * @param string|null $locale | |
54 | * @param array $attributes | |
55 | * Additional values supported by NumberFormatter | |
56 | * https://www.php.net/manual/en/class.numberformatter.php | |
57 | * By default this will set it to round to 8 places and not | |
58 | * add any padding. | |
59 | * | |
60 | * @return string | |
61 | */ | |
62 | public function number($amount, ?string $locale = NULL, array $attributes = [ | |
63 | NumberFormatter::MIN_FRACTION_DIGITS => 0, | |
64 | NumberFormatter::MAX_FRACTION_DIGITS => 8, | |
65 | ]): string { | |
66 | $formatter = $this->getMoneyFormatter(NULL, $locale, NumberFormatter::DECIMAL, $attributes); | |
67 | return $formatter->format($amount); | |
68 | } | |
69 | ||
70 | /** | |
71 | * Get a number formatted with rounding expectations derived from the currency. | |
72 | * | |
73 | * @param string|float|int $amount | |
74 | * @param string $currency | |
75 | * @param $locale | |
76 | * | |
77 | * @return string | |
78 | * | |
79 | * @noinspection PhpDocMissingThrowsInspection | |
80 | * @noinspection PhpUnhandledExceptionInspection | |
81 | */ | |
82 | public function moneyNumber($amount, string $currency, $locale): string { | |
83 | $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::DECIMAL); | |
84 | $money = Money::of($amount, $currency, NULL, RoundingMode::HALF_UP); | |
85 | return $money->formatWith($formatter); | |
86 | } | |
87 | ||
88 | /** | |
89 | * Get a money value with formatting but not rounding. | |
90 | * | |
91 | * @param string|float|int $amount | |
92 | * @param string|null $currency | |
93 | * @param string|null $locale | |
94 | * | |
95 | * @return string | |
96 | * | |
97 | * @noinspection PhpDocMissingThrowsInspection | |
98 | * @noinspection PhpUnhandledExceptionInspection | |
99 | */ | |
100 | public function moneyLong($amount, ?string $currency, ?string $locale): string { | |
101 | $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::CURRENCY, [ | |
102 | NumberFormatter::MAX_FRACTION_DIGITS => 9, | |
103 | ]); | |
104 | $money = Money::of($amount, $currency, new AutoContext()); | |
105 | return $money->formatWith($formatter); | |
106 | } | |
107 | ||
108 | /** | |
109 | * Get a number with minimum decimal places based on the currency but no rounding. | |
110 | * | |
111 | * @param string|float|int $amount | |
112 | * @param string|null $currency | |
113 | * @param string|null $locale | |
114 | * | |
115 | * @return string | |
116 | * | |
117 | * @noinspection PhpDocMissingThrowsInspection | |
118 | * @noinspection PhpUnhandledExceptionInspection | |
119 | */ | |
120 | public function moneyNumberLong($amount, ?string $currency, ?string $locale): string { | |
121 | $formatter = $this->getMoneyFormatter($currency, $locale, NumberFormatter::DECIMAL, [ | |
122 | NumberFormatter::MAX_FRACTION_DIGITS => 9, | |
123 | ]); | |
124 | $money = Money::of($amount, $currency, new AutoContext()); | |
125 | return $money->formatWith($formatter); | |
126 | } | |
127 | ||
128 | /** | |
129 | * Should we use the configured thousand & decimal separators. | |
130 | * | |
131 | * The goal is to phase this into being FALSE - but for now | |
132 | * we are looking at how to manage an 'opt in' | |
133 | */ | |
134 | protected function isUseSeparatorSettings(): bool { | |
135 | return !CRM_Utils_Constant::value('IGNORE_SEPARATOR_CONFIG'); | |
136 | } | |
137 | ||
138 | /** | |
139 | * Get the money formatter for when we are using configured thousand separators. | |
140 | * | |
141 | * Our intent is to phase out these settings in favour of deriving them from the locale. | |
142 | * | |
143 | * @param string|null $currency | |
144 | * @param string|null $locale | |
145 | * @param int $style | |
146 | * See https://www.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants | |
147 | * @param array $attributes | |
148 | * See https://www.php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute | |
149 | * | |
150 | * @return \NumberFormatter | |
151 | * | |
152 | * @noinspection PhpDocMissingThrowsInspection | |
153 | * @noinspection PhpUnhandledExceptionInspection | |
154 | */ | |
155 | public function getMoneyFormatter(?string $currency = NULL, ?string $locale = NULL, int $style = NumberFormatter::CURRENCY, array $attributes = []): NumberFormatter { | |
156 | if (!$currency) { | |
157 | $currency = Civi::settings()->get('defaultCurrency'); | |
158 | } | |
159 | $cacheKey = __CLASS__ . $currency . '_' . $locale . '_' . $style . (!empty($attributes) ? md5(json_encode($attributes)) : ''); | |
160 | if (!isset(\Civi::$statics[$cacheKey])) { | |
161 | $formatter = new NumberFormatter($locale, $style); | |
162 | ||
163 | if (!isset($attributes[NumberFormatter::MIN_FRACTION_DIGITS])) { | |
164 | $attributes[NumberFormatter::MIN_FRACTION_DIGITS] = Currency::of($currency) | |
165 | ->getDefaultFractionDigits(); | |
166 | } | |
167 | if (!isset($attributes[NumberFormatter::MAX_FRACTION_DIGITS])) { | |
168 | $attributes[NumberFormatter::MAX_FRACTION_DIGITS] = Currency::of($currency) | |
169 | ->getDefaultFractionDigits(); | |
170 | } | |
171 | ||
172 | foreach ($attributes as $attribute => $value) { | |
173 | $formatter->setAttribute($attribute, $value); | |
174 | } | |
175 | if ($locale === CRM_Core_I18n::getLocale() && $this->isUseSeparatorSettings()) { | |
176 | $formatter->setSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, CRM_Core_Config::singleton()->monetaryThousandSeparator); | |
177 | $formatter->setSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL, CRM_Core_Config::singleton()->monetaryDecimalPoint); | |
178 | } | |
179 | \Civi::$statics[$cacheKey] = $formatter; | |
180 | } | |
181 | return \Civi::$statics[$cacheKey]; | |
182 | } | |
183 | ||
184 | } |