Merge pull request #22162 from eileenmcnaughton/pay_test
[civicrm-core.git] / Civi / Core / Format.php
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 }