Last active
June 27, 2017 11:48
-
-
Save bojanz/4fbead42616027f5776851ddb4d4c840 to your computer and use it in GitHub Desktop.
Script for regenerating Commerce 1.x currencies based on CLDR
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Regenerates the Commerce 1.x currency list from CLDR. | |
* | |
* Assumes the existence of commerce/ and intl/ folders. | |
* The intl/ folder should contain the commerceguys/intl | |
* library with the CLDR dataset downloaded via | |
* scripts/fetch_data.sh | |
*/ | |
include __DIR__ . '/commerce/includes/commerce.currency.inc'; | |
include __DIR__ . '/intl/vendor/autoload.php'; | |
$dataDir = 'intl/scripts/assets/'; | |
$commerceCurrencies = commerce_commerce_currency_info(); | |
$isoCurrencies = getIsoCurrencies(); | |
$addedCurrencies = array_keys(array_diff_key($isoCurrencies, $commerceCurrencies)); | |
$removedCurrencies = array_keys(array_diff_key($commerceCurrencies, $isoCurrencies)); | |
$currencyData = json_decode(file_get_contents($dataDir . 'cldr-core/supplemental/currencyData.json'), true); | |
$currencyData = $currencyData['supplemental']['currencyData']; | |
$territoryInfo = json_decode(file_get_contents($dataDir . 'cldr-core/supplemental/territoryInfo.json'), true); | |
$territoryInfo = $territoryInfo['supplemental']['territoryInfo']; | |
$newCommerceCurrencies = []; | |
foreach ($isoCurrencies as $currencyCode => $numericCode) { | |
// Determine the currency's default locale (currency -> country -> locale). | |
// This can't work for EUR because it's used by multiple countries. | |
$locale = 'en'; | |
$fallbackLocale = 'en'; | |
$languageCode = NULL; | |
if ($currencyCode == 'EUR') { | |
$locale = 'fr'; | |
} | |
else { | |
$countryCode = getCurrencyCountry($currencyCode); | |
if ($countryCode) { | |
$languageCode = getCountryLanguage($countryCode); | |
$locale = $languageCode . '-' . $countryCode; | |
$fallbackLocale = $languageCode; | |
} | |
} | |
$englishData = json_decode(file_get_contents($dataDir . 'cldr-numbers-full/main/en/currencies.json'), true); | |
$name = $englishData['main']['en']['numbers']['currencies'][$currencyCode]['displayName']; | |
$currency = getCurrency($currencyCode, $locale, $fallbackLocale); | |
$symbol = $currency['symbol']; | |
$numberFormat = getNumberFormat($locale, $fallbackLocale); | |
$currencyPattern = $numberFormat['currencyFormats-numberSystem-latn']['standard']; | |
// Ignore the negative portion of the currency pattern. | |
$currencyPattern = strtok($currencyPattern, ';'); | |
$numberPosition = strpos($currencyPattern, '0.00'); | |
$symbolPosition = strpos($currencyPattern, '¤'); | |
$symbolSpacer = ' '; | |
if ($symbolPosition < $numberPosition) { | |
$symbolPlacement = 'before'; | |
$symbolSpacer = mb_substr($currencyPattern, $symbolPosition + 1, 1, 'UTF-8'); | |
$symbolEdge = mb_substr($symbol, -1, 1, 'UTF-8'); | |
} | |
else { | |
$symbolPlacement = 'after'; | |
$symbolSpacer = mb_substr($currencyPattern, $symbolPosition - 2, 1, 'UTF-8'); | |
$symbolEdge = mb_substr($symbol, 0, 1, 'UTF-8'); | |
} | |
if (in_array($symbolSpacer, ['#', '0', '¤'])) { | |
$symbolSpacer = ''; | |
} | |
// CLDR quote: | |
// "By default a space is automatically added between letters in a currency symbol and adjacent numbers." | |
if ($symbolSpacer == '' && ctype_alpha($symbolEdge)) { | |
$symbolSpacer = ' '; | |
} | |
$decimals = 2; | |
if (isset($currencyData['fractions'][$currencyCode]['_digits'])) { | |
$decimals = $currencyData['fractions'][$currencyCode]['_digits']; | |
} | |
// Override for Hungary, which has digits only in theory. | |
if ($currencyCode == 'HUF') { | |
$decimals = 0; | |
} | |
$newCommerceCurrencies[$currencyCode] = [ | |
'code' => $currencyCode, | |
'numeric_code' => $numericCode, | |
'symbol' => $symbol, | |
'name' => 't(' . $name . ')t', | |
'symbol_placement' => $symbolPlacement, | |
'symbol_spacer' => $symbolSpacer, | |
// The symbol should always be used, not the code. | |
'code_placement' => 'hidden', | |
// CLDR has no information on minor_unit and major_unit. Use the old data when available. | |
'minor_unit' => isset($commerceCurrencies[$currencyCode]['minor_unit']) ? 't(' . $commerceCurrencies[$currencyCode]['minor_unit'] . ')t' : '', | |
'major_unit' => isset($commerceCurrencies[$currencyCode]['major_unit']) ? 't(' . $commerceCurrencies[$currencyCode]['major_unit'] . ')t' : '', | |
'decimals' => (int) $decimals, | |
'thousands_separator' => $numberFormat['symbols-numberSystem-latn']['group'], | |
'decimal_separator' => $numberFormat['symbols-numberSystem-latn']['decimal'], | |
]; | |
// Strip default values. | |
$defaults = [ | |
'symbol' => '', | |
'minor_unit' => '', | |
'decimals' => 2, | |
'rounding_step' => 0, | |
'thousands_separator' => ',', | |
'decimal_separator' => '.', | |
'symbol_placement' => 'hidden', | |
'symbol_spacer' => ' ', | |
'code_placement' => 'after', | |
'code_spacer' => ' ', | |
]; | |
foreach ($newCommerceCurrencies[$currencyCode] as $key => $value) { | |
if (isset($defaults[$key]) && $defaults[$key] == $value) { | |
unset($newCommerceCurrencies[$currencyCode][$key]); | |
} | |
} | |
// 1.x doesn't support arabic number formatting, so it's best to | |
// fallback to the default behavior, just show the currency code | |
// after the number. | |
if ($languageCode == 'ar') { | |
unset($newCommerceCurrencies[$currencyCode]['symbol_placement']); | |
unset($newCommerceCurrencies[$currencyCode]['symbol_spacer']); | |
unset($newCommerceCurrencies[$currencyCode]['code_placement']); | |
} | |
} | |
// CLDR doesn't seem to have a pt-BR number format, and the default pt one | |
// uses no symbol spacer. Confirmed on http://www.telhanorte.com.br/ | |
// and http://www.cec.com.br/ that a spacer is needed, so unset the empty | |
// string to use the default. | |
unset($newCommerceCurrencies['BRL']['symbol_spacer']); | |
// Probably not even used, but we keep it for BC. | |
$newCommerceCurrencies['CHF']['rounding_step'] = '0.05'; | |
// Fix the symbol for HRK ("kn" instead of "HRK"). | |
$newCommerceCurrencies['HRK']['symbol'] = 'kn'; | |
// @todo Get commerce to stop re-sorting the currencies by code, then sort | |
// by name here. | |
ksort($newCommerceCurrencies); | |
file_put_php('currencies.php', $newCommerceCurrencies); | |
if ($count = count($addedCurrencies)) { | |
echo "Added $count currencies: " . implode(', ', $addedCurrencies) . "\n"; | |
} else { | |
echo "No currencies were added. \n"; | |
} | |
if ($count = count($removedCurrencies)) { | |
echo "Removed $count currencies: " . implode(', ', $removedCurrencies) . "\n"; | |
} else { | |
echo "No currencies were removed. \n"; | |
} | |
$updatedCurrencies = []; | |
foreach ($commerceCurrencies as $currencyCode => $currency) { | |
if (isset($newCommerceCurrencies[$currencyCode]) && array_diff($newCommerceCurrencies[$currencyCode], $currency)) { | |
$updatedCurrencies[] = $currencyCode; | |
} | |
} | |
if ($count = count($updatedCurrencies)) { | |
echo "Updated $count currencies: " . implode(', ', $updatedCurrencies) . "\n"; | |
} else { | |
echo "No currencies were updated. \n"; | |
} | |
/** | |
* t() shim to allow commerce_commerce_currency_info() to work. | |
*/ | |
function t($string) | |
{ | |
return $string; | |
} | |
/** | |
* Gets the ISO currencies. | |
* | |
* ISO only lists active currencies, making it practical to use as a base. | |
* | |
* @return array | |
* Format: $currencyCode => $numericCode. | |
*/ | |
function getIsoCurrencies() | |
{ | |
global $dataDir; | |
$isoCurrencies = []; | |
$isoData = simplexml_load_file($dataDir . 'c2.xml'); | |
foreach ($isoData->CcyTbl->CcyNtry as $currency) { | |
$attributes = (array) $currency->CcyNm->attributes(); | |
if (!empty($attributes) && !empty($attributes['@attributes']['IsFund'])) { | |
// Ignore funds. | |
continue; | |
} | |
$currency = (array) $currency; | |
if (empty($currency['Ccy'])) { | |
// Ignore placeholders like "Antarctica". | |
continue; | |
} | |
if (substr($currency['CtryNm'], 0, 2) == 'ZZ' || in_array($currency['Ccy'], ['XUA', 'XSU', 'XDR'])) { | |
// Ignore special currencies. | |
continue; | |
} | |
if (in_array($currency['Ccy'], ['SVC', 'ZWL'])) { | |
// No longer used, but still included in the ISO list. | |
continue; | |
} | |
$isoCurrencies[$currency['Ccy']] = $currency['CcyNbr']; | |
} | |
return $isoCurrencies; | |
} | |
/** | |
* Gets the country for a specific currency. | |
*/ | |
function getCurrencyCountry($currencyCode) | |
{ | |
global $currencyData; | |
foreach ($currencyData['region'] as $countryCode => $data) { | |
foreach ($data as $index => $currencies) { | |
if (isset($currencies[$currencyCode]) && empty($currencies[$currencyCode]['_to'])) { | |
return $countryCode; | |
} | |
} | |
} | |
} | |
/** | |
* Gets the language for a specific country. | |
*/ | |
function getCountryLanguage($countryCode) | |
{ | |
global $territoryInfo; | |
$languages = $territoryInfo[$countryCode]['languagePopulation']; | |
// Sort by population percent date. | |
uasort($languages, function ($a, $b) { | |
$a = $a['_populationPercent']; | |
$b = $b['_populationPercent']; | |
if ($a == $b) { | |
return 0; | |
} | |
return ($a > $b) ? -1 : 1; | |
}); | |
$languageCode = key($languages); | |
$languageCode = str_replace('_', '-', $languageCode); | |
return $languageCode; | |
} | |
function getCurrency($currencyCode, $locale, $fallbackLocale) | |
{ | |
global $dataDir; | |
$usedLocale = NULL; | |
$localeCandidates = [$locale, $fallbackLocale, 'en']; | |
foreach ($localeCandidates as $localeCandidate) { | |
if (file_exists($dataDir . 'cldr-numbers-full/main/' . $localeCandidate . '/currencies.json')) { | |
$usedLocale = $localeCandidate; | |
break; | |
} | |
} | |
$data = json_decode(file_get_contents($dataDir . 'cldr-numbers-full/main/' . $usedLocale . '/currencies.json'), true); | |
$currency = $data['main'][$usedLocale]['numbers']['currencies'][$currencyCode]; | |
return $currency; | |
} | |
function getNumberFormat($locale, $fallbackLocale) | |
{ | |
global $dataDir; | |
$usedLocale = NULL; | |
$localeCandidates = [$locale, $fallbackLocale, 'en']; | |
foreach ($localeCandidates as $localeCandidate) { | |
if (file_exists($dataDir . 'cldr-numbers-full/main/' . $localeCandidate . '/numbers.json')) { | |
$usedLocale = $localeCandidate; | |
break; | |
} | |
} | |
$data = json_decode(file_get_contents($dataDir . 'cldr-numbers-full/main/' . $usedLocale . '/numbers.json'), true); | |
$numberFormat = $data['main'][$usedLocale]['numbers']; | |
return $numberFormat; | |
} | |
/** | |
* Converts the provided data into php and writes it to the disk. | |
*/ | |
function file_put_php($filename, $data) | |
{ | |
$data = var_export($data, true); | |
$data = str_replace(['array (', "=> \n "], ['array(', '=> '], $data); | |
$data = str_replace('=> array(', '=> array(', $data); | |
$data = '<?php' . "\n\n" . '$data = ' . $data . ';'; | |
// Put back t(). | |
$data = str_replace(["'t(", ")t',"], ["t('", "'),"], $data); | |
file_put_contents($filename, $data); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment