1<?php 2/** 3 * Copyright 1999-2016 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (LGPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/lgpl21. 7 * 8 * @author Jon Parise <jon@horde.org> 9 * @author Chuck Hagenbuch <chuck@horde.org> 10 * @author Jan Schneider <jan@horde.org> 11 * @author Michael Slusarz <slusarz@horde.org> 12 * @category Horde 13 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 14 * @package Nls 15 */ 16 17/** 18 * The Horde_Nls class provides Native Language Support. 19 * 20 * This includes common methods for handling language data, timezones, and 21 * hostname->country lookups. 22 * 23 * @author Jon Parise <jon@horde.org> 24 * @author Chuck Hagenbuch <chuck@horde.org> 25 * @author Jan Schneider <jan@horde.org> 26 * @author Michael Slusarz <slusarz@horde.org> 27 * @category Horde 28 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 29 * @package Nls 30 */ 31class Horde_Nls 32{ 33 /** 34 * DNS resolver. 35 * 36 * @var Net_DNS2_Resolver 37 */ 38 public static $dnsResolver; 39 40 /** 41 * Cached values. 42 * 43 * @var array 44 */ 45 protected static $_cache = array(); 46 47 /** 48 * Check to see if character set is valid for htmlspecialchars() calls. 49 * 50 * @param string $charset The character set to check. 51 * 52 * @return boolean Is charset valid for the current system? 53 */ 54 public static function checkCharset($charset) 55 { 56 if (is_null($charset) || empty($charset)) { 57 return false; 58 } 59 60 $valid = true; 61 62 ini_set('track_errors', 1); 63 @htmlspecialchars('', ENT_COMPAT, $charset); 64 if (isset($php_errormsg)) { 65 $valid = false; 66 } 67 ini_restore('track_errors'); 68 69 return $valid; 70 } 71 72 /** 73 * Returns a list of available timezones. 74 * 75 * @return array List of timezones. 76 */ 77 public static function getTimezones() 78 { 79 $timezones = DateTimeZone::listIdentifiers(); 80 return array_combine($timezones, $timezones); 81 } 82 83 /** 84 * Returns a list of available timezones, including timezone abbreviations. 85 * 86 * Contrary to getTimezones() the timezone IDs are values and the timezone 87 * labels are the keys, to allow multiple labels for the same timezone. 88 * 89 * @since 2.2.0 90 * 91 * @return array List of timezones. 92 */ 93 public static function getTimezonesWithAbbreviations() 94 { 95 $timezones = array_flip(self::getTimezones()); 96 foreach (DateTimeZone::listAbbreviations() as $abbreviation => $timezone) { 97 $abbreviation = Horde_String::upper($abbreviation); 98 if ($abbreviation == 'UTC' || strlen($abbreviation) < 2) { 99 continue; 100 } 101 $timezones[$abbreviation] = $timezone[0]['timezone_id']; 102 } 103 return $timezones; 104 } 105 106 /** 107 * Get the locale info returned by localeconv(), but cache it, to 108 * avoid repeated calls. 109 * 110 * @return array The results of localeconv(). 111 */ 112 public static function getLocaleInfo() 113 { 114 if (!isset(self::$_cache['lc_info'])) { 115 self::$_cache['lc_info'] = localeconv(); 116 } 117 118 return self::$_cache['lc_info']; 119 } 120 121 /** 122 * Get the language info returned by nl_langinfo(), but cache it, to 123 * avoid repeated calls. 124 * 125 * @param const $item The langinfo item to return. 126 * 127 * @return array The results of nl_langinfo(). 128 */ 129 public static function getLangInfo($item) 130 { 131 if (!function_exists('nl_langinfo')) { 132 return false; 133 } 134 135 if (!isset(self::$_cache['nl_info'])) { 136 self::$_cache['nl_info'] = array(); 137 } 138 139 if (!isset(self::$_cache['nl_info'][$item])) { 140 self::$_cache['nl_info'][$item] = nl_langinfo($item); 141 } 142 143 return self::$_cache['nl_info'][$item]; 144 } 145 146 /** 147 * Get country information from a hostname or IP address. 148 * 149 * @param string $host The hostname or IP address. 150 * @param string $datafile The datafile for the GeoIP lookup. If not set, 151 * will skip this lookup. 152 * 153 * @return mixed On success, return an array with the following entries: 154 * 'code' => Country Code 155 * 'name' => Country Name 156 * On failure, return false. 157 */ 158 public static function getCountryByHost($host, $datafile = null) 159 { 160 /* List of generic domains that we know is not in the country TLD 161 list. See: http://www.iana.org/gtld/gtld.htm */ 162 $generic = array( 163 'aero', 'biz', 'com', 'coop', 'edu', 'gov', 'info', 'int', 'mil', 164 'museum', 'name', 'net', 'org', 'pro' 165 ); 166 167 $checkHost = null; 168 if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $host)) { 169 if (isset(self::$dnsResolver)) { 170 try { 171 $response = self::$dnsResolver->query($host, 'PTR'); 172 foreach ($response->answer as $val) { 173 if (isset($val->ptrdname)) { 174 $checkHost = $val->ptrdname; 175 break; 176 } 177 } 178 } catch (Net_DNS2_Exception $e) {} 179 } 180 if (is_null($checkHost)) { 181 $checkHost = @gethostbyaddr($host); 182 } 183 } else { 184 $checkHost = $host; 185 } 186 187 /* Get the TLD of the hostname. */ 188 $pos = strrpos($checkHost, '.'); 189 if ($pos === false) { 190 return false; 191 } 192 $domain = Horde_String::lower(substr($checkHost, $pos + 1)); 193 194 /* Try lookup via TLD first. */ 195 if (!in_array($domain, $generic)) { 196 $name = self::tldLookup($domain); 197 if ($name) { 198 return array( 199 'code' => $domain, 200 'name' => $name 201 ); 202 } 203 } 204 205 /* Try GeoIP lookup next. */ 206 $geoip = new Horde_Nls_Geoip($datafile); 207 return $geoip->getCountryInfo($checkHost); 208 } 209 210 /** 211 * Do a top level domain (TLD) lookup. 212 * 213 * @param string $code A 2-letter country code. 214 * 215 * @return mixed The localized country name, or null if not found. 216 */ 217 public static function tldLookup($code) 218 { 219 if (!isset(self::$_cache['tld'])) { 220 include __DIR__ . '/Nls/Tld.php'; 221 self::$_cache['tld'] = $tld; 222 } 223 224 $code = Horde_String::lower($code); 225 226 return isset(self::$_cache['tld'][$code]) 227 ? self::$_cache['tld'][$code] 228 : null; 229 } 230 231 /** 232 * Returns either a specific or all ISO-3166 country names. 233 * 234 * @param string $code The ISO 3166 country code. 235 * 236 * @return mixed If a country code has been requested will return the 237 * corresponding country name. If empty will return an 238 * array of all the country codes and their names. 239 */ 240 public static function getCountryISO($code = null) 241 { 242 if (!isset(self::$_cache['iso3166'])) { 243 include __DIR__ . '/Nls/Countries.php'; 244 self::$_cache['iso3166'] = $countries; 245 } 246 247 if (empty($code)) { 248 return self::$_cache['iso3166']; 249 } 250 251 $code = Horde_String::upper($code); 252 253 return isset(self::$_cache['iso3166'][$code]) 254 ? self::$_cache['iso3166'][$code] 255 : null; 256 } 257 258 /** 259 * Returns either a specific or all ISO-639 language names. 260 * 261 * @param string $code The ISO 639 language code. 262 * 263 * @return mixed If a language code has been requested will return the 264 * corresponding language name. If empty will return an 265 * array of all the language codes (keys) and their names 266 * (values). 267 */ 268 public static function getLanguageISO($code = null) 269 { 270 if (!isset(self::$_cache['iso639'])) { 271 include __DIR__ . '/Nls/Languages.php'; 272 self::$_cache['iso639'] = $languages; 273 } 274 275 if (empty($code)) { 276 return self::$_cache['iso639']; 277 } 278 279 $code = substr(Horde_String::lower(trim($code)), 0, 2); 280 281 return isset(self::$_cache['iso639'][$code]) 282 ? self::$_cache['iso639'][$code] 283 : null; 284 } 285 286} 287