99use Magento \Framework \App \ObjectManager ;
1010use Magento \Framework \Exception \InputException ;
1111use Magento \Directory \Model \Currency \Filter ;
12+ use Magento \Framework \Locale \Currency as LocaleCurrency ;
13+ use Magento \Framework \Locale \ResolverInterface as LocalResolverInterface ;
14+ use Magento \Framework \NumberFormatterFactory ;
15+ use Magento \Framework \Serialize \Serializer \Json ;
1216
1317/**
1418 * Currency model
@@ -71,6 +75,31 @@ class Currency extends \Magento\Framework\Model\AbstractModel
7175 */
7276 private $ currencyConfig ;
7377
78+ /**
79+ * @var LocalResolverInterface
80+ */
81+ private $ localeResolver ;
82+
83+ /**
84+ * @var NumberFormatterFactory
85+ */
86+ private $ numberFormatterFactory ;
87+
88+ /**
89+ * @var \Magento\Framework\NumberFormatter
90+ */
91+ private $ numberFormatter ;
92+
93+ /**
94+ * @var array
95+ */
96+ private $ numberFormatterCache ;
97+
98+ /**
99+ * @var Json
100+ */
101+ private $ serializer ;
102+
74103 /**
75104 * @param \Magento\Framework\Model\Context $context
76105 * @param \Magento\Framework\Registry $registry
@@ -79,10 +108,13 @@ class Currency extends \Magento\Framework\Model\AbstractModel
79108 * @param \Magento\Directory\Helper\Data $directoryHelper
80109 * @param Currency\FilterFactory $currencyFilterFactory
81110 * @param \Magento\Framework\Locale\CurrencyInterface $localeCurrency
82- * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
83- * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
111+ * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
112+ * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
84113 * @param array $data
85- * @param CurrencyConfig $currencyConfig
114+ * @param CurrencyConfig|null $currencyConfig
115+ * @param LocalResolverInterface|null $localeResolver
116+ * @param NumberFormatterFactory|null $numberFormatterFactory
117+ * @param Json|null $serializer
86118 * @SuppressWarnings(PHPMD.ExcessiveParameterList)
87119 */
88120 public function __construct (
@@ -96,7 +128,10 @@ public function __construct(
96128 \Magento \Framework \Model \ResourceModel \AbstractResource $ resource = null ,
97129 \Magento \Framework \Data \Collection \AbstractDb $ resourceCollection = null ,
98130 array $ data = [],
99- CurrencyConfig $ currencyConfig = null
131+ CurrencyConfig $ currencyConfig = null ,
132+ LocalResolverInterface $ localeResolver = null ,
133+ \Magento \Framework \NumberFormatterFactory $ numberFormatterFactory = null ,
134+ Json $ serializer = null
100135 ) {
101136 parent ::__construct (
102137 $ context ,
@@ -111,6 +146,9 @@ public function __construct(
111146 $ this ->_currencyFilterFactory = $ currencyFilterFactory ;
112147 $ this ->_localeCurrency = $ localeCurrency ;
113148 $ this ->currencyConfig = $ currencyConfig ?: ObjectManager::getInstance ()->get (CurrencyConfig::class);
149+ $ this ->localeResolver = $ localeResolver ?: ObjectManager::getInstance ()->get (LocalResolverInterface::class);
150+ $ this ->numberFormatterFactory = $ numberFormatterFactory ?: ObjectManager::getInstance ()->get (NumberFormatterFactory::class);
151+ $ this ->serializer = $ serializer ?: ObjectManager::getInstance ()->get (Json::class);
114152 }
115153
116154 /**
@@ -326,9 +364,121 @@ public function formatTxt($price, $options = [])
326364 * %F - the argument is treated as a float, and presented as a floating-point number (non-locale aware).
327365 */
328366 $ price = sprintf ("%F " , $ price );
367+
368+ if ($ this ->canUseNumberFormatter ($ options )) {
369+ return $ this ->formatCurrency ($ price , $ options );
370+ }
371+
329372 return $ this ->_localeCurrency ->getCurrency ($ this ->getCode ())->toCurrency ($ price , $ options );
330373 }
331374
375+ /**
376+ * Check if to use Intl.NumberFormatter to format currency.
377+ *
378+ * @param array $options
379+ * @return bool
380+ */
381+ private function canUseNumberFormatter (array $ options ): bool
382+ {
383+ $ allowedOptions = [
384+ 'precision ' ,
385+ LocaleCurrency::CURRENCY_OPTION_DISPLAY ,
386+ LocaleCurrency::CURRENCY_OPTION_SYMBOL
387+ ];
388+
389+ if (!empty (array_diff (array_keys ($ options ), $ allowedOptions ))) {
390+ return false ;
391+ }
392+
393+ if (array_key_exists ('display ' , $ options )
394+ && $ options ['display ' ] !== \Magento \Framework \Currency::NO_SYMBOL
395+ && $ options ['display ' ] !== \Magento \Framework \Currency::USE_SYMBOL
396+ ) {
397+ return false ;
398+ }
399+
400+ return true ;
401+ }
402+
403+ /**
404+ * Format currency.
405+ *
406+ * @param string $price
407+ * @param array $options
408+ * @return string
409+ */
410+ private function formatCurrency (string $ price , array $ options ): string
411+ {
412+ $ customerOptions = new \Magento \Framework \DataObject ([]);
413+
414+ $ this ->_eventManager ->dispatch (
415+ 'currency_display_options_forming ' ,
416+ ['currency_options ' => $ customerOptions , 'base_code ' => $ this ->getCode ()]
417+ );
418+ $ options += $ customerOptions ->toArray ();
419+
420+ $ this ->numberFormatter = $ this ->getNumberFormatter ($ options );
421+
422+ $ formattedCurrency = $ this ->numberFormatter ->formatCurrency (
423+ $ price , $ this ->getCode () ?? $ this ->numberFormatter ->getTextAttribute (\NumberFormatter::CURRENCY_CODE )
424+ );
425+
426+ if (array_key_exists (LocaleCurrency::CURRENCY_OPTION_SYMBOL , $ options )) {
427+ // remove only one non-breaking space from custom currency symbol to allow custom NBSP in currency symbol
428+ $ formattedCurrency = preg_replace ('/ /u ' , '' , $ formattedCurrency , 1 );
429+ }
430+
431+ if ((array_key_exists (LocaleCurrency::CURRENCY_OPTION_DISPLAY , $ options )
432+ && $ options [LocaleCurrency::CURRENCY_OPTION_DISPLAY ] === \Magento \Framework \Currency::NO_SYMBOL )) {
433+ $ formattedCurrency = str_replace (' ' , '' , $ formattedCurrency );
434+ }
435+
436+ return preg_replace ('/^\s+|\s+$/u ' , '' , $ formattedCurrency );
437+ }
438+
439+ /**
440+ * Get NumberFormatter object from cache.
441+ *
442+ * @param array $options
443+ * @return \Magento\Framework\NumberFormatter
444+ */
445+ private function getNumberFormatter (array $ options ): \Magento \Framework \NumberFormatter
446+ {
447+ $ key = 'currency_ ' . md5 ($ this ->localeResolver ->getLocale () . $ this ->serializer ->serialize ($ options ));
448+ if (!isset ($ this ->numberFormatterCache [$ key ])) {
449+ $ this ->numberFormatter = $ this ->numberFormatterFactory ->create (
450+ ['locale ' => $ this ->localeResolver ->getLocale (), 'style ' => \NumberFormatter::CURRENCY ]
451+ );
452+
453+ $ this ->setOptions ($ options );
454+ $ this ->numberFormatterCache [$ key ] = $ this ->numberFormatter ;
455+ }
456+
457+ return $ this ->numberFormatterCache [$ key ];
458+ }
459+
460+ /**
461+ * Set number formatter custom options.
462+ *
463+ * @param array $options
464+ * @return void
465+ */
466+ private function setOptions (array $ options ): void
467+ {
468+ if (array_key_exists (LocaleCurrency::CURRENCY_OPTION_SYMBOL , $ options )) {
469+ $ this ->numberFormatter ->setSymbol (
470+ \NumberFormatter::CURRENCY_SYMBOL , $ options [LocaleCurrency::CURRENCY_OPTION_SYMBOL ]
471+ );
472+ }
473+ if (array_key_exists (LocaleCurrency::CURRENCY_OPTION_DISPLAY , $ options )
474+ && $ options [LocaleCurrency::CURRENCY_OPTION_DISPLAY ] === \Magento \Framework \Currency::NO_SYMBOL ) {
475+ $ this ->numberFormatter ->setSymbol (\NumberFormatter::CURRENCY_SYMBOL , '' );
476+ }
477+ if (array_key_exists ('precision ' , $ options )) {
478+ $ this ->numberFormatter ->setAttribute (\NumberFormatter::FRACTION_DIGITS , $ options ['precision ' ]);
479+ }
480+ }
481+
332482 /**
333483 * Return currency symbol for current locale and currency code
334484 *
0 commit comments