'CP949', 'ISO-8859-8-I' => 'ISO-8859-8' ); /** * sole constructor */ public function __construct() { global $sugar_config; $this->localeNameFormatDefault = empty($sugar_config['locale_name_format_default']) ? 's f l' : $sugar_config['default_name_format']; $this->loadCurrencies(); } /** * returns an array of Sugar Config defaults that are determined by locale settings * @return array */ public function getLocaleConfigDefaults() { $coreDefaults = array( 'datef' => 'm/d/Y', 'timef' => 'H:i', 'default_currency_significant_digits' => 2, 'default_currency_symbol' => '$', 'default_export_charset' => $this->default_export_charset, 'default_locale_name_format' => 's f l', 'name_formats' => array( 's f l' => 's f l', 'f l' => 'f l', 's l' => 's l', 'l, s f' => 'l, s f', 'l, f' => 'l, f', 's l, f' => 's l, f', 'l s f' => 'l s f', 'l f s' => 'l f s' ), 'default_number_grouping_seperator' => ',', 'default_decimal_seperator' => '.', 'export_delimiter' => ',', 'default_email_charset' => $this->default_email_charset, ); return $coreDefaults; } /** * abstraction of precedence * @param string prefName Name of preference to retrieve based on overrides * @param object user User in focus, default null (current_user) * @return string pref Most significant preference */ public function getPrecedentPreference($prefName, $user = null, $sugarConfigPrefName = '') { global $current_user; global $sugar_config; $userPref = ''; $coreDefaults = $this->getLocaleConfigDefaults(); $pref = isset($coreDefaults[$prefName]) ? $coreDefaults[$prefName] : ''; // defaults, even before config.php if ($user != null) { $userPref = $user->getPreference($prefName); } elseif (!empty($current_user)) { $userPref = $current_user->getPreference($prefName); } // Bug 39171 - If we are asking for default_email_charset, check in emailSettings['defaultOutboundCharset'] as well if ($prefName == 'default_email_charset') { if ($user != null) { $emailSettings = $user->getPreference('emailSettings', 'Emails'); } elseif (!empty($current_user)) { $emailSettings = $current_user->getPreference('emailSettings', 'Emails'); } if (isset($emailSettings['defaultOutboundCharset'])) { $userPref = $emailSettings['defaultOutboundCharset']; } } // set fallback defaults defined in this class if (isset($this->$prefName)) { $pref = $this->$prefName; } //rrs: 33086 - give the ability to pass in the preference name as stored in $sugar_config. if (!empty($sugarConfigPrefName)) { $prefName = $sugarConfigPrefName; } // cn: 9549 empty() call on a value of 0 (0 significant digits) resulted in a false-positive. changing to "isset()" $pref = (!isset($sugar_config[$prefName]) || (empty($sugar_config[$prefName]) && $sugar_config[$prefName] !== '0')) ? $pref : $sugar_config[$prefName]; $pref = (empty($userPref) && $userPref !== '0') ? $pref : $userPref; return $pref; } /////////////////////////////////////////////////////////////////////////// //// CURRENCY HANDLING /** * wrapper for whatever currency system we implement */ public function loadCurrencies() { // doing it dirty here global $db; global $sugar_config; if (empty($db)) { return array(); } $load = sugar_cache_retrieve('currency_list'); if (!is_array($load)) { // load default from config.php $this->currencies['-99'] = array( 'name' => $sugar_config['default_currency_name'], 'symbol' => $sugar_config['default_currency_symbol'], 'conversion_rate' => 1 ); $q = "SELECT id, name, symbol, conversion_rate FROM currencies WHERE status = 'Active' and deleted = 0"; $r = $db->query($q); while ($a = $db->fetchByAssoc($r)) { $load = array(); $load['name'] = $a['name']; $load['symbol'] = $a['symbol']; $load['conversion_rate'] = $a['conversion_rate']; $this->currencies[$a['id']] = $load; } sugar_cache_put('currency_list', $this->currencies); } else { $this->currencies = $load; } } /** * getter for currencies array * @return array $this->currencies returns array( id => array(name => X, etc */ public function getCurrencies() { return $this->currencies; } /** * retrieves default OOTB currencies for sugar_config and installer. * @return array ret Array of default currencies keyed by ISO4217 code */ public function getDefaultCurrencies() { $ret = array( 'AUD' => array( 'name' => 'Australian Dollars', 'iso4217' => 'AUD', 'symbol' => '$' ), 'BRL' => array( 'name' => 'Brazilian Reais', 'iso4217' => 'BRL', 'symbol' => 'R$' ), 'GBP' => array( 'name' => 'British Pounds', 'iso4217' => 'GBP', 'symbol' => '£' ), 'CAD' => array( 'name' => 'Canadian Dollars', 'iso4217' => 'CAD', 'symbol' => '$' ), 'CNY' => array( 'name' => 'Chinese Yuan', 'iso4217' => 'CNY', 'symbol' => '¥' ), 'EUR' => array( 'name' => 'Euro', 'iso4217' => 'EUR', 'symbol' => '€' ), 'HKD' => array( 'name' => 'Hong Kong Dollars', 'iso4217' => 'HKD', 'symbol' => '$' ), 'INR' => array( 'name' => 'Indian Rupees', 'iso4217' => 'INR', 'symbol' => '₨' ), 'KRW' => array( 'name' => 'Korean Won', 'iso4217' => 'KRW', 'symbol' => '₩' ), 'YEN' => array( 'name' => 'Japanese Yen', 'iso4217' => 'JPY', 'symbol' => '¥' ), 'MXN' => array( 'name' => 'Mexican Pesos', 'iso4217' => 'MXN', 'symbol' => '$' ), 'SGD' => array( 'name' => 'Singaporean Dollars', 'iso4217' => 'SGD', 'symbol' => '$' ), 'CHF' => array( 'name' => 'Swiss Franc', 'iso4217' => 'CHF', 'symbol' => 'SFr.' ), 'THB' => array( 'name' => 'Thai Baht', 'iso4217' => 'THB', 'symbol' => '฿' ), 'USD' => array( 'name' => 'US Dollars', 'iso4217' => 'USD', 'symbol' => '$' ), ); return $ret; } //// END CURRENCY HANDLING /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// //// CHARSET TRANSLATION /** * returns a mod|app_strings array in the target charset * @param array strings $mod_string, et.al. * @param string charset Target charset * @return array Translated string pack */ public function translateStringPack($strings, $charset) { // handle recursive foreach ($strings as $k => $v) { if (is_array($v)) { $strings[$k] = $this->translateStringPack($v, $charset); } else { $strings[$k] = $this->translateCharset($v, 'UTF-8', $charset); } } ksort($strings); return $strings; } /** * translates the passed variable for email sending (export) * @param mixed the var (array or string) to translate * @return mixed the translated variable */ public function translateForEmail($var) { if (is_array($var)) { foreach ($var as $k => $v) { $var[$k] = $this->translateForEmail($v); } return $var; } elseif (!empty($var)) { return $this->translateCharset($var, 'UTF-8', $this->getOutboundEmailCharset()); } } /** * prepares a bean for export by translating any text fields into the export * character set * @param bean object A SugarBean * @return bean object The bean with translated strings */ public function prepBeanForExport($bean) { foreach ($bean->field_defs as $k => $field) { if (is_string($bean->$k)) { // $bean->$k = $this->translateCharset($bean->$k, 'UTF-8', $this->getExportCharset()); } else { $bean->$k = ''; } } return $bean; } /** * translates a character set from one encoding to another encoding * @param string string the string to be translated * @param string fromCharset the charset the string is currently in * @param string toCharset the charset to translate into (defaults to UTF-8) * @param bool forceIconv force using the iconv library instead of mb_string * @return string the translated string */ public function translateCharset($string, $fromCharset, $toCharset = 'UTF-8', $forceIconv = false) { $GLOBALS['log']->debug("Localization: translating [{$string}] from {$fromCharset} into {$toCharset}"); // Bug #35413 Function has to use iconv if $fromCharset is not in mb_list_encodings $isMb = function_exists('mb_convert_encoding') && !$forceIconv; $isIconv = function_exists('iconv'); if ($isMb == true) { $fromCharset = strtoupper($fromCharset); $listEncodings = mb_list_encodings(); $isFound = false; foreach ($listEncodings as $encoding) { if (strtoupper($encoding) == $fromCharset) { $isFound = true; break; } } $isMb = $isFound; } if ($isMb) { return mb_convert_encoding($string, $toCharset, $fromCharset); } elseif ($isIconv) { $newFromCharset = $fromCharset; if (isset($this->iconvCharsetMap[$fromCharset])) { $newFromCharset = $this->iconvCharsetMap[$fromCharset]; $GLOBALS['log']->debug("Localization: iconv using charset {$newFromCharset} instead of {$fromCharset}"); } $newToCharset = $toCharset; if (isset($this->iconvCharsetMap[$toCharset])) { $newToCharset = $this->iconvCharsetMap[$toCharset]; $GLOBALS['log']->debug("Localization: iconv using charset {$newToCharset} instead of {$toCharset}"); } return iconv($newFromCharset, $newToCharset, $string); } return $string; } /** * Prefixes the input with a BOM. * * @param string $string The string to add a BOM to * @param string $fromCharset The charset of the input string * @return string The input string including a BOM * @throws UnexpectedValueException in case the encoding isn't supported */ public function addBOM($string, $fromCharset) { $charset = $this->normalizeCharset($fromCharset); if ($charset === 'utf8') { return "\xef\xbb\xbf" . $string; } else if ($charset === 'utf16le') { return "\xff\xfe" . $string; } else if ($charset === 'utf16be') { return "\xfe\xff" . $string; } throw new UnexpectedValueException('Encoding not supported: ' . $fromCharset); } /** * translates a character set from one to another, and the into MIME-header friendly format */ public function translateCharsetMIME($string, $fromCharset, $toCharset = 'UTF-8', $encoding = "Q") { $previousEncoding = mb_internal_encoding(); mb_internal_encoding($fromCharset); $result = mb_encode_mimeheader($string, $toCharset, $encoding); mb_internal_encoding($previousEncoding); return $result; } public function normalizeCharset($charset) { $charset = strtolower(preg_replace("/[\-\_]*/", "", $charset)); return $charset; } /** * returns an array of charsets with keys for available translations; appropriate for get_select_options_with_id() */ public function getCharsetSelect() { //jc:12293 - the "labels" or "human-readable" representations of the various charsets //should be translatable $translated = array(); foreach ($this->availableCharsets as $key) { //$translated[$key] = translate($value); $translated[$key] = translate($key); } return $translated; //end:12293 } /** * returns the charset preferred in descending order: User, Sugar Config, DEFAULT * @param string charset to override ALL, pass a valid charset here * @return string charset the chosen character set */ public function getExportCharset($charset = '', $user = null) { $charset = $this->getPrecedentPreference('default_export_charset', $user); return $charset; } /** * returns the charset preferred in descending order: User, Sugar Config, DEFAULT * @return string charset the chosen character set */ public function getOutboundEmailCharset($user = null) { $charset = $this->getPrecedentPreference('default_email_charset', $user); return $charset; } //// END CHARSET TRANSLATION /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// //// NUMBER DISPLAY FORMATTING CODE public function getDecimalSeparator($user = null) { // Bug50887 this is purposefully misspelled as ..._separator to match the way it's defined throughout the app. $dec = $this->getPrecedentPreference('default_decimal_seperator', $user); return $dec; } public function getNumberGroupingSeparator($user = null) { $sep = $this->getPrecedentPreference('default_number_grouping_seperator', $user); return $sep; } public function getPrecision($user = null) { $precision = $this->getPrecedentPreference('default_currency_significant_digits', $user); return $precision; } public function getCurrencySymbol($user = null) { $dec = $this->getPrecedentPreference('default_currency_symbol', $user); return $dec; } /** * returns a number formatted by user preference or system default * @param string number Number to be formatted and returned * @param string currencySymbol Currency symbol if override is necessary * @param bool is_currency Flag to also return the currency symbol * @return string Formatted number */ public function getLocaleFormattedNumber($number, $currencySymbol = '', $is_currency = true, $user = null) { $fnum = $number; $majorDigits = ''; $minorDigits = ''; $dec = $this->getDecimalSeparator($user); $thou = $this->getNumberGroupingSeparator($user); $precision = $this->getPrecision($user); $symbol = empty($currencySymbol) ? $this->getCurrencySymbol($user) : $currencySymbol; $exNum = explode($dec, $number); // handle grouping if (is_array($exNum) && count($exNum) > 0) { if (strlen($exNum[0]) > 3) { $offset = strlen($exNum[0]) % 3; if ($offset > 0) { for ($i = 0; $i < $offset; $i++) { $majorDigits .= $exNum[0][$i]; } } $tic = 0; for ($i = $offset; $i < strlen($exNum[0]); $i++) { if ($tic % 3 == 0 && $i != 0) { $majorDigits .= $thou; // add separator } $majorDigits .= $exNum[0][$i]; $tic++; } } else { $majorDigits = $exNum[0]; // no formatting needed } $fnum = $majorDigits; } // handle decimals if ($precision > 0) { // we toss the minor digits otherwise if (is_array($exNum) && isset($exNum[1])) { } } if ($is_currency) { $fnum = $symbol . $fnum; } return $fnum; } /** * returns Javascript to format numbers and currency for ***DISPLAY*** */ public function getNumberJs() { $out = << 0) { var strlength = majorDigits.length; if (strlength > 3) { var offset = strlength % 3; // find how many to lead off by for (j = 0; j < offset; j++) { formattedMajor += majorDigits[j]; } tic = 0; for (i = offset; i < strlength; i++) { if (tic % 3 == 0 && i != 0) formattedMajor += sep; formattedMajor += majorDigits.substr(i,1); tic++; } } } else { formattedMajor = majorDigits; // no grouping marker } // handle decimal precision if (precision > 0) { for (i = 0; i < precision; i++) { if (minorDigits[i] != undefined) { formattedMinor += minorDigits[i]; } else { formattedMinor += '0'; } } } else { // we're just returning the major digits, no decimal marker dec = ''; // just in case } return formattedMajor + dec + formattedMinor; } function setSigDigits() { var sym = document.getElementById('symbol').value; var thou = document.getElementById('default_number_grouping_seperator').value; var dec = document.getElementById('default_decimal_seperator').value; var precision = document.getElementById('sigDigits').value; //umber(n, num_grp_sep, dec_sep, round, precision) var newNumber = sym + formatNumber(exampleDigits, thou, dec, precision, precision); document.getElementById('sigDigitsExample').value = newNumber; } JAVASCRIPT; return $out; } //// END NUMBER DISPLAY FORMATTING CODE /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// //// NAME DISPLAY FORMATTING CODE /** * get's the Name format macro string, preferring $current_user * @return string format Name Format macro for locale */ public function getLocaleFormatMacro($user = null) { $returnFormat = $this->getPrecedentPreference('default_locale_name_format', $user); return $returnFormat; } /** * returns formatted name according to $current_user's locale settings * * @param string firstName * @param string lastName * @param string salutation * @param string title * @param string format If a particular format is desired, then pass this optional parameter as a simple string. * sfl is "Salutation FirstName LastName", "l, f s" is "LastName[comma][space]FirstName[space]Salutation" * @param object user object * @param bool returnEmptyStringIfEmpty true if we should return back an empty string rather than a single space * when the formatted name would be blank * @return string formattedName */ public function getLocaleFormattedName($firstName, $lastName, $salutationKey = '', $title = '', $format = "", $user = null, $returnEmptyStringIfEmpty = false) { global $current_user; global $app_list_strings; if ($user == null) { $user = $current_user; } $salutation = $salutationKey; if (!empty($salutationKey) && !empty($app_list_strings['salutation_dom'][$salutationKey])) { $salutation = (!empty($app_list_strings['salutation_dom'][$salutationKey]) ? $app_list_strings['salutation_dom'][$salutationKey] : $salutationKey); } //check to see if passed in variables are set, if so, then populate array with value, //if not, then populate array with blank '' $names = array(); $names['f'] = (empty($firstName) && $firstName != 0) ? '' : $firstName; $names['l'] = (empty($lastName) && $lastName != 0) ? '' : $lastName; $names['s'] = (empty($salutation) && $salutation != 0) ? '' : $salutation; $names['t'] = (empty($title) && $title != 0) ? '' : $title; //Bug: 39936 - if all of the inputs are empty, then don't try to format the name. $allEmpty = true; foreach ($names as $key => $val) { if (!empty($val)) { $allEmpty = false; break; } } if ($allEmpty) { return $returnEmptyStringIfEmpty ? '' : ' '; } //end Bug: 39936 if (empty($format)) { $this->localeNameFormat = $this->getLocaleFormatMacro($user); } else { $this->localeNameFormat = $format; } // parse localeNameFormat $formattedName = ''; for ($i = 0; $i < strlen($this->localeNameFormat); $i++) { $formattedName .= array_key_exists($this->localeNameFormat[$i], $names) ? $names[$this->localeNameFormat[$i]] : $this->localeNameFormat[$i]; } $formattedName = trim($formattedName); if (strlen($formattedName) == 0) { return $returnEmptyStringIfEmpty ? '' : ' '; } if (strpos($formattedName, ',', strlen($formattedName) - 1)) { // remove trailing commas $formattedName = substr($formattedName, 0, strlen($formattedName) - 1); } return trim($formattedName); } /** * outputs some simple Javascript to show a preview of Name format in "My Account" and "Admin->Localization" * @param string first First Name, use app_strings default if not specified * @param string last Last Name, use app_strings default if not specified * @param string salutation Saluation, use app_strings default if not specified * @return string some Javascript */ public function getNameJs($first = '', $last = '', $salutation = '', $title = '') { global $app_strings; $salutation = !empty($salutation) ? $salutation : $app_strings['LBL_LOCALE_NAME_EXAMPLE_SALUTATION']; $first = !empty($first) ? $first : $app_strings['LBL_LOCALE_NAME_EXAMPLE_FIRST']; $last = !empty($last) ? $last : $app_strings['LBL_LOCALE_NAME_EXAMPLE_LAST']; $title = !empty($title) ? $title : $app_strings['LBL_LOCALE_NAME_EXAMPLE_TITLE']; $ret = <<invalidNameFormatUpgradeFilename); } /** * Creates the file that is created when there is an invalid name format during an upgrade */ public function createInvalidLocaleNameFormatUpgradeNotice() { $fh = fopen($this->invalidNameFormatUpgradeFilename, 'wb'); fclose($fh); } /** * Removes the file that is created when there is an invalid name format during an upgrade */ public function removeInvalidLocaleNameFormatUpgradeNotice() { if ($this->invalidLocaleNameFormatUpgrade()) { unlink($this->invalidNameFormatUpgradeFilename); } } /** * Creates dropdown items that have localized example names while filtering out invalid formats * * @param array un-prettied dropdown list * @return array array of dropdown options */ public function getUsableLocaleNameOptions($options) { global $app_strings; $examples = array( 's' => $app_strings['LBL_LOCALE_NAME_EXAMPLE_SALUTATION'], 'f' => $app_strings['LBL_LOCALE_NAME_EXAMPLE_FIRST'], 'l' => $app_strings['LBL_LOCALE_NAME_EXAMPLE_LAST'] ); $newOpts = array(); foreach ($options as $key => $val) { if ($this->isAllowedNameFormat($key) && $this->isAllowedNameFormat($val)) { $newVal = ''; $pieces = str_split($val); foreach ($pieces as $piece) { if (isset($examples[$piece])) { $newVal .= $examples[$piece]; } else { $newVal .= $piece; } } $newOpts[$key] = $newVal; } } return $newOpts; } //// END NAME DISPLAY FORMATTING CODE /////////////////////////////////////////////////////////////////////////// /** * Attempts to detect the charset used in the string * * @param $str string * @param $strict bool default false (use strict encoding?) * @return string */ public function detectCharset($str, $strict = false) { if (function_exists('mb_convert_encoding')) { return mb_detect_encoding($str, 'ASCII,JIS,UTF-8,EUC-JP,SJIS,ISO-8859-1', $strict); } return false; } }