_localeFormat = $localeFormat; $this->_storeManager = $storeManager; $this->_directoryHelper = $directoryHelper; $this->_currencyFilterFactory = $currencyFilterFactory; $this->_localeCurrency = $localeCurrency; $this->currencyConfig = $currencyConfig ?: ObjectManager::getInstance()->get(CurrencyConfig::class); $this->localeResolver = $localeResolver ?: ObjectManager::getInstance()->get(LocalResolverInterface::class); $this->numberFormatterFactory = $numberFormatterFactory ?: ObjectManager::getInstance()->get(NumberFormatterFactory::class); $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); } /** * @return void */ protected function _construct() { $this->_init(\Magento\Directory\Model\ResourceModel\Currency::class); } /** * Get currency code * * @return string */ public function getCode() { return $this->_getData('currency_code'); } /** * Get currency code * * @return string */ public function getCurrencyCode() { return $this->_getData('currency_code'); } /** * Currency Rates getter * * @return array */ public function getRates() { return $this->_rates; } /** * Currency Rates setter * * @param array $rates Currency Rates * @return $this */ public function setRates(array $rates) { $this->_rates = $rates; return $this; } /** * Loading currency data * * @param string $id * @param string $field * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function load($id, $field = null) { $this->unsRate(); $this->setData('currency_code', $id); return $this; } /** * Get currency rate (only base => allowed) * * @param mixed $toCurrency * @return float */ public function getRate($toCurrency) { $code = $this->getCurrencyCodeFromToCurrency($toCurrency); $rates = $this->getRates(); if (!isset($rates[$code])) { $rates[$code] = $this->_getResource()->getRate($this->getCode(), $toCurrency); $this->setRates($rates); } return $rates[$code]; } /** * Get currency rate (base=>allowed or allowed=>base) * * @param mixed $toCurrency * @return float */ public function getAnyRate($toCurrency) { $code = $this->getCurrencyCodeFromToCurrency($toCurrency); $rates = $this->getRates(); if (!isset($rates[$code])) { $rates[$code] = $this->_getResource()->getAnyRate($this->getCode(), $toCurrency); $this->setRates($rates); } return $rates[$code]; } /** * Convert price to currency format * * @param float $price * @param mixed $toCurrency * @return float * @throws \Exception */ public function convert($price, $toCurrency = null) { if ($toCurrency === null) { return $price; } elseif ($rate = $this->getRate($toCurrency)) { return (float)$price * (float)$rate; } throw new \Exception(__( 'Undefined rate from "%1-%2".', $this->getCode(), $this->getCurrencyCodeFromToCurrency($toCurrency) )); } /** * @param mixed $toCurrency * @return string * @throws \Magento\Framework\Exception\InputException */ private function getCurrencyCodeFromToCurrency($toCurrency) { if (is_string($toCurrency)) { $code = $toCurrency; } elseif ($toCurrency instanceof \Magento\Directory\Model\Currency) { $code = $toCurrency->getCurrencyCode(); } else { throw new InputException(__('Please correct the target currency.')); } return $code; } /** * Get currency filter * * @return Filter */ public function getFilter() { if (!$this->_filter) { $this->_filter = $this->_currencyFilterFactory->create(['code' => $this->getCode()]); } return $this->_filter; } /** * Format price to currency format * * @param float $price * @param array $options * @param bool $includeContainer * @param bool $addBrackets * @return string */ public function format($price, $options = [], $includeContainer = true, $addBrackets = false) { return $this->formatPrecision($price, 2, $options, $includeContainer, $addBrackets); } /** * Apply currency format to number with specific rounding precision * * @param float $price * @param int $precision * @param array $options * @param bool $includeContainer * @param bool $addBrackets * @return string */ public function formatPrecision( $price, $precision, $options = [], $includeContainer = true, $addBrackets = false ) { if (!isset($options['precision'])) { $options['precision'] = $precision; } if ($includeContainer) { return '' . ($addBrackets ? '[' : '') . $this->formatTxt( $price, $options ) . ($addBrackets ? ']' : '') . ''; } return $this->formatTxt($price, $options); } /** * @param float $price * @param array $options * @return string */ public function formatTxt($price, $options = []) { if (!is_numeric($price)) { $price = $this->_localeFormat->getNumber($price); } /** * Fix problem with 12 000 000, 1 200 000 * * %f - the argument is treated as a float, and presented as a floating-point number (locale aware). * %F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). */ $price = sprintf("%F", $price); if ($this->canUseNumberFormatter($options)) { return $this->formatCurrency($price, $options); } return $this->_localeCurrency->getCurrency($this->getCode())->toCurrency($price, $options); } /** * Check if to use Intl.NumberFormatter to format currency. * * @param array $options * @return bool */ private function canUseNumberFormatter(array $options): bool { $allowedOptions = [ 'precision', LocaleCurrency::CURRENCY_OPTION_DISPLAY, LocaleCurrency::CURRENCY_OPTION_SYMBOL ]; if (!empty(array_diff(array_keys($options), $allowedOptions))) { return false; } if (array_key_exists('display', $options) && $options['display'] !== \Magento\Framework\Currency::NO_SYMBOL && $options['display'] !== \Magento\Framework\Currency::USE_SYMBOL ) { return false; } return true; } /** * Format currency. * * @param string $price * @param array $options * @return string */ private function formatCurrency(string $price, array $options): string { $customerOptions = new \Magento\Framework\DataObject([]); $this->_eventManager->dispatch( 'currency_display_options_forming', ['currency_options' => $customerOptions, 'base_code' => $this->getCode()] ); $options += $customerOptions->toArray(); $this->numberFormatter = $this->getNumberFormatter($options); $formattedCurrency = $this->numberFormatter->formatCurrency( $price, $this->getCode() ?? $this->numberFormatter->getTextAttribute(\NumberFormatter::CURRENCY_CODE) ); if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_SYMBOL, $options)) { // remove only one non-breaking space from custom currency symbol to allow custom NBSP in currency symbol $formattedCurrency = preg_replace('/ /u', '', $formattedCurrency, 1); } if ((array_key_exists(LocaleCurrency::CURRENCY_OPTION_DISPLAY, $options) && $options[LocaleCurrency::CURRENCY_OPTION_DISPLAY] === \Magento\Framework\Currency::NO_SYMBOL)) { $formattedCurrency = str_replace(' ', '', $formattedCurrency); } return preg_replace('/^\s+|\s+$/u', '', $formattedCurrency); } /** * Get NumberFormatter object from cache. * * @param array $options * @return NumberFormatter */ private function getNumberFormatter(array $options): NumberFormatter { $key = 'currency_' . md5($this->localeResolver->getLocale() . $this->serializer->serialize($options)); if (!isset($this->numberFormatterCache[$key])) { $this->numberFormatter = $this->numberFormatterFactory->create( ['locale' => $this->localeResolver->getLocale(), 'style' => \NumberFormatter::CURRENCY] ); $this->setOptions($options); $this->numberFormatterCache[$key] = $this->numberFormatter; } return $this->numberFormatterCache[$key]; } /** * Set number formatter custom options. * * @param array $options * @return void */ private function setOptions(array $options): void { if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_SYMBOL, $options)) { $this->numberFormatter->setSymbol( \NumberFormatter::CURRENCY_SYMBOL, $options[LocaleCurrency::CURRENCY_OPTION_SYMBOL] ); } if (array_key_exists(LocaleCurrency::CURRENCY_OPTION_DISPLAY, $options) && $options[LocaleCurrency::CURRENCY_OPTION_DISPLAY] === \Magento\Framework\Currency::NO_SYMBOL) { $this->numberFormatter->setSymbol(\NumberFormatter::CURRENCY_SYMBOL, ''); $this->numberFormatter->setPattern(str_replace('¤', '', $this->numberFormatter->getPattern())); } if (array_key_exists('precision', $options)) { $this->numberFormatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $options['precision']); } } /** * Return currency symbol for current locale and currency code * * @return string */ public function getCurrencySymbol() { return $this->_localeCurrency->getCurrency($this->getCode())->getSymbol(); } /** * @return string */ public function getOutputFormat() { $formatted = $this->formatTxt(0); $number = str_replace($this->getCurrencySymbol(), '', $formatted); return str_replace($this->trimUnicodeDirectionMark($number), '%s', $formatted); } /** * Retrieve allowed currencies according to config * * @return array */ public function getConfigAllowCurrencies() { $allowedCurrencies = $this->currencyConfig->getConfigCurrencies(self::XML_PATH_CURRENCY_ALLOW); $appBaseCurrencyCode = $this->_directoryHelper->getBaseCurrencyCode(); if (!in_array($appBaseCurrencyCode, $allowedCurrencies)) { $allowedCurrencies[] = $appBaseCurrencyCode; } foreach ($this->_storeManager->getStores() as $store) { $code = $store->getBaseCurrencyCode(); if (!in_array($code, $allowedCurrencies)) { $allowedCurrencies[] = $code; } } return $allowedCurrencies; } /** * Retrieve default currencies according to config * * @return array */ public function getConfigDefaultCurrencies() { return $this->currencyConfig->getConfigCurrencies(self::XML_PATH_CURRENCY_DEFAULT); } /** * @return array */ public function getConfigBaseCurrencies() { return $this->currencyConfig->getConfigCurrencies(self::XML_PATH_CURRENCY_BASE); } /** * Retrieve currency rates to other currencies * * @param string $currency * @param array|null $toCurrencies * @return array */ public function getCurrencyRates($currency, $toCurrencies = null) { if ($currency instanceof \Magento\Directory\Model\Currency) { $currency = $currency->getCode(); } $data = $this->_getResource()->getCurrencyRates($currency, $toCurrencies); return $data; } /** * Save currency rates * * @param array $rates * @return $this */ public function saveRates($rates) { $this->_getResource()->saveRates($rates); return $this; } /** * This method removes LRM and RLM marks from string * * @param string $string * @return string */ private function trimUnicodeDirectionMark(string $string): string { if (preg_match('/^(\x{200E}|\x{200F})/u', $string, $match)) { $string = preg_replace('/^' . $match[1] . '/u', '', $string); } return $string; } }