languages.php 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163
  1. <?php
  2. /**
  3. * SquirrelMail internationalization functions
  4. *
  5. * This file contains variuos functions that are needed to do
  6. * internationalization of SquirrelMail.
  7. *
  8. * Internally the output character set is used. Other characters are
  9. * encoded using Unicode entities according to HTML 4.0.
  10. *
  11. * Before 1.5.2 functions were stored in functions/i18n.php. Script is moved
  12. * because it executes some code in order to detect functions supported by
  13. * existing PHP installation and implements fallback functions when required
  14. * functions are not available. Scripts in functions/ directory should not
  15. * setup anything when they are loaded.
  16. * @copyright &copy; 1999-2007 The SquirrelMail Project Team
  17. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  18. * @version $Id$
  19. * @package squirrelmail
  20. * @subpackage i18n
  21. */
  22. /**
  23. * Wrapper for textdomain(), bindtextdomain() and
  24. * bind_textdomain_codeset() primarily intended for
  25. * plugins when changing into their own text domain
  26. * and back again.
  27. *
  28. * Note that if plugins using this function have
  29. * their translation files located in the SquirrelMail
  30. * locale directory, the second argument is optional.
  31. *
  32. * @param string $domain_name The name of the text domain
  33. * (usually the plugin name, or
  34. * "squirrelmail") being switched to.
  35. * @param string $directory The directory that contains
  36. * all translations for the domain
  37. * (OPTIONAL; default is SquirrelMail
  38. * locale directory).
  39. *
  40. * @return string The name of the text domain that was set
  41. * *BEFORE* it is changed herein - NOTE that
  42. * this differs from PHP's textdomain()
  43. *
  44. * @since 1.5.2 and 1.4.10
  45. */
  46. function sq_change_text_domain($domain_name, $directory='') {
  47. static $domains_already_seen = array();
  48. global $gettext_domain;
  49. $return_value = $gettext_domain;
  50. // only need to call bindtextdomain() once
  51. //
  52. if (in_array($domain_name, $domains_already_seen)) {
  53. sq_textdomain($domain_name);
  54. return $return_value;
  55. }
  56. $domains_already_seen[] = $domain_name;
  57. if (empty($directory)) $directory = SM_PATH . 'locale/';
  58. sq_bindtextdomain($domain_name, $directory);
  59. sq_textdomain($domain_name);
  60. return $return_value;
  61. }
  62. /**
  63. * Gettext bindtextdomain wrapper.
  64. *
  65. * Wrapper solves differences between php versions in order to provide
  66. * ngettext support. Should be used if translation uses ngettext
  67. * functions.
  68. *
  69. * This also provides a bind_textdomain_codeset call to make sure the
  70. * domain's encoding will not be overridden.
  71. *
  72. * @since 1.4.10 and 1.5.1
  73. * @param string $domain gettext domain name
  74. * @param string $dir directory that contains all translations (OPTIONAL;
  75. * if not specified, defaults to SquirrelMail locale
  76. * directory)
  77. * @return string path to translation directory
  78. */
  79. function sq_bindtextdomain($domain,$dir='') {
  80. global $l10n, $gettext_flags, $sm_notAlias;
  81. if (empty($dir)) $dir = SM_PATH . 'locale/';
  82. if ($gettext_flags==7) {
  83. // gettext extension without ngettext
  84. if (substr($dir, -1) != '/') $dir .= '/';
  85. $mofile=$dir . $sm_notAlias . '/LC_MESSAGES/' . $domain . '.mo';
  86. $input = new FileReader($mofile);
  87. $l10n[$domain] = new gettext_reader($input);
  88. }
  89. $dir=bindtextdomain($domain,$dir);
  90. // set codeset in order to avoid gettext charset conversions
  91. if (function_exists('bind_textdomain_codeset')
  92. && isset($languages[$sm_notAlias]['CHARSET'])) {
  93. // Japanese translation uses different internal charset
  94. if ($sm_notAlias == 'ja_JP') {
  95. bind_textdomain_codeset ($domain_name, 'EUC-JP');
  96. } else {
  97. bind_textdomain_codeset ($domain_name, $languages[$sm_notAlias]['CHARSET']);
  98. }
  99. }
  100. return $dir;
  101. }
  102. /**
  103. * Gettext textdomain wrapper.
  104. * Makes sure that gettext_domain global is modified.
  105. * @since 1.5.1
  106. * @param string $name gettext domain name
  107. * @return string gettext domain name
  108. */
  109. function sq_textdomain($domain) {
  110. global $gettext_domain;
  111. $gettext_domain=textdomain($domain);
  112. return $gettext_domain;
  113. }
  114. /**
  115. * php setlocale function wrapper
  116. *
  117. * From php 4.3.0 it is possible to use arrays in order to set locale.
  118. * php gettext extension works only when locale is set. This wrapper
  119. * function allows to use more than one locale name.
  120. *
  121. * @param int $category locale category name. Use php named constants
  122. * (LC_ALL, LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME)
  123. * @param mixed $locale option contains array with possible locales or string with one locale
  124. * @return string name of set locale or false, if all locales fail.
  125. * @since 1.5.1 and 1.4.5
  126. * @see http://www.php.net/setlocale
  127. */
  128. function sq_setlocale($category,$locale) {
  129. if (is_string($locale)) {
  130. // string with only one locale
  131. $ret = setlocale($category,$locale);
  132. } elseif (! check_php_version(4,3)) {
  133. // older php version (second setlocale argument must be string)
  134. $ret=false;
  135. $index=0;
  136. while ( ! $ret && $index<count($locale)) {
  137. $ret=setlocale($category,$locale[$index]);
  138. $index++;
  139. }
  140. } else {
  141. // php 4.3.0 or better, use entire array
  142. $ret=setlocale($category,$locale);
  143. }
  144. /* safety checks */
  145. if (preg_match("/^.*\/.*\/.*\/.*\/.*\/.*$/",$ret)) {
  146. /**
  147. * Welcome to We-Don't-Follow-Own-Fine-Manual department
  148. * OpenBSD 3.8, 3.9-current and maybe later versions
  149. * return invalid response to setlocale command.
  150. * SM bug report #1427512.
  151. */
  152. $ret = false;
  153. }
  154. return $ret;
  155. }
  156. /**
  157. * Converts string from given charset to charset, that can be displayed by user translation.
  158. *
  159. * Function by default returns html encoded strings, if translation uses different encoding.
  160. * If Japanese translation is used - function returns string converted to euc-jp
  161. * If iconv or recode functions are enabled and translation uses utf-8 - function returns utf-8 encoded string.
  162. * If $charset is not supported - function returns unconverted string.
  163. *
  164. * sanitizing of html tags is also done by this function.
  165. *
  166. * @param string $charset
  167. * @param string $string Text to be decoded
  168. * @param boolean $force_decode converts string to html without $charset!=$default_charset check.
  169. * Argument is available since 1.5.1 and 1.4.5.
  170. * @param boolean $save_html disables htmlspecialchars() in order to preserve
  171. * html formating. Use with care. Available since 1.5.1
  172. * @return string decoded string
  173. */
  174. function charset_decode ($charset, $string, $force_decode=false, $save_html=false) {
  175. global $languages, $squirrelmail_language, $default_charset;
  176. global $use_php_recode, $use_php_iconv, $aggressive_decoding;
  177. if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
  178. function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode')) {
  179. $string = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode', $string);
  180. }
  181. $charset = strtolower($charset);
  182. set_my_charset();
  183. // Variables that allow to use functions without function_exist() calls
  184. if (! isset($use_php_recode) || $use_php_recode=="" ) {
  185. $use_php_recode=false; }
  186. if (! isset($use_php_iconv) || $use_php_iconv=="" ) {
  187. $use_php_iconv=false; }
  188. // Don't do conversion if charset is the same.
  189. if ( ! $force_decode && $charset == strtolower($default_charset) )
  190. return ($save_html ? $string : htmlspecialchars($string));
  191. // catch iso-8859-8-i thing
  192. if ( $charset == "iso-8859-8-i" )
  193. $charset = "iso-8859-8";
  194. /*
  195. * Recode converts html special characters automatically if you use
  196. * 'charset..html' decoding. There is no documented way to put -d option
  197. * into php recode function call.
  198. */
  199. if ( $use_php_recode ) {
  200. if ( $default_charset == "utf-8" ) {
  201. // other charsets can be converted to utf-8 without loss.
  202. // and output string is smaller
  203. $string = recode_string($charset . "..utf-8",$string);
  204. return ($save_html ? $string : htmlspecialchars($string));
  205. } else {
  206. $string = recode_string($charset . "..html",$string);
  207. // recode does not convert single quote, htmlspecialchars does.
  208. $string = str_replace("'", '&#039;', $string);
  209. // undo html specialchars
  210. if ($save_html)
  211. $string=str_replace(array('&amp;','&quot;','&lt;','&gt;'),
  212. array('&','"','<','>'),$string);
  213. return $string;
  214. }
  215. }
  216. // iconv functions does not have html target and can be used only with utf-8
  217. if ( $use_php_iconv && $default_charset=='utf-8') {
  218. $string = iconv($charset,$default_charset,$string);
  219. return ($save_html ? $string : htmlspecialchars($string));
  220. }
  221. // If we don't use recode and iconv, we'll do it old way.
  222. /* All HTML special characters are 7 bit and can be replaced first */
  223. if (! $save_html) $string = htmlspecialchars ($string);
  224. /* controls cpu and memory intensive decoding cycles */
  225. if (! isset($aggressive_decoding) || $aggressive_decoding=="" ) {
  226. $aggressive_decoding=false; }
  227. $decode=fixcharset($charset);
  228. $decodefile=SM_PATH . 'functions/decode/' . $decode . '.php';
  229. if ($decode != 'index' && file_exists($decodefile)) {
  230. include_once($decodefile);
  231. // send $save_html argument to decoding function. needed for iso-2022-xx decoding.
  232. $ret = call_user_func('charset_decode_'.$decode, $string, $save_html);
  233. } else {
  234. $ret = $string;
  235. }
  236. return( $ret );
  237. }
  238. /**
  239. * Converts html string to given charset
  240. * @since 1.5.1 and 1.4.4
  241. * @param string $string
  242. * @param string $charset
  243. * @param boolean $htmlencode keep htmlspecialchars encoding
  244. * @return string
  245. */
  246. function charset_encode($string,$charset,$htmlencode=true) {
  247. global $default_charset;
  248. $encode=fixcharset($charset);
  249. $encodefile=SM_PATH . 'functions/encode/' . $encode . '.php';
  250. if ($encode != 'index' && file_exists($encodefile)) {
  251. include_once($encodefile);
  252. $ret = call_user_func('charset_encode_'.$encode, $string);
  253. } elseif(file_exists(SM_PATH . 'functions/encode/us_ascii.php')) {
  254. // function replaces all 8bit html entities with question marks.
  255. // it is used when other encoding functions are unavailable
  256. include_once(SM_PATH . 'functions/encode/us_ascii.php');
  257. $ret = charset_encode_us_ascii($string);
  258. } else {
  259. /**
  260. * fix for yahoo users that remove all us-ascii related things
  261. */
  262. $ret = $string;
  263. }
  264. /**
  265. * Undo html special chars, some places (like compose form) have
  266. * own sanitizing functions and don't need html symbols.
  267. * Undo chars only after encoding in order to prevent conversion of
  268. * html entities in plain text emails.
  269. */
  270. if (! $htmlencode ) {
  271. $ret = str_replace(array('&amp;','&gt;','&lt;','&quot;'),array('&','>','<','"'),$ret);
  272. }
  273. return( $ret );
  274. }
  275. /**
  276. * Combined decoding and encoding functions
  277. *
  278. * If conversion is done to charset different that utf-8, unsupported symbols
  279. * will be replaced with question marks.
  280. * @since 1.5.1 and 1.4.4
  281. * @param string $in_charset initial charset
  282. * @param string $string string that has to be converted
  283. * @param string $out_charset final charset
  284. * @param boolean $htmlencode keep htmlspecialchars encoding
  285. * @return string converted string
  286. */
  287. function charset_convert($in_charset,$string,$out_charset,$htmlencode=true) {
  288. $string=charset_decode($in_charset,$string,true);
  289. $string=sqi18n_convert_entities($string);
  290. $string=charset_encode($string,$out_charset,$htmlencode);
  291. return $string;
  292. }
  293. /**
  294. * Makes charset name suitable for decoding cycles
  295. *
  296. * @since 1.5.0 and 1.4.4
  297. * @param string $charset Name of charset
  298. * @return string $charset Adjusted name of charset
  299. */
  300. function fixcharset($charset) {
  301. /* remove minus and characters that might be used in paths from charset
  302. * name in order to be able to use it in function names and include calls.
  303. */
  304. $charset=preg_replace("/[-:.\/\\\]/",'_',$charset);
  305. // OE ks_c_5601_1987 > cp949
  306. $charset=str_replace('ks_c_5601_1987','cp949',$charset);
  307. // Moz x-euc-tw > euc-tw
  308. $charset=str_replace('x_euc','euc',$charset);
  309. // Moz x-windows-949 > cp949
  310. $charset=str_replace('x_windows_','cp',$charset);
  311. // windows-125x and cp125x charsets
  312. $charset=str_replace('windows_','cp',$charset);
  313. // ibm > cp
  314. $charset=str_replace('ibm','cp',$charset);
  315. // iso-8859-8-i -> iso-8859-8
  316. // use same cycle until I'll find differences
  317. $charset=str_replace('iso_8859_8_i','iso_8859_8',$charset);
  318. return $charset;
  319. }
  320. /**
  321. * Set up the language to be output
  322. * if $do_search is true, then scan the browser information
  323. * for a possible language that we know
  324. *
  325. * Function sets system locale environment (LC_ALL, LANG, LANGUAGE),
  326. * gettext translation bindings and html header information.
  327. *
  328. * Function returns error codes, if there is some fatal error.
  329. * 0 = no error,
  330. * 1 = mbstring support is not present,
  331. * 2 = mbstring support is not present, user's translation reverted to en_US.
  332. *
  333. * @param string $sm_language translation used by user's interface
  334. * @param bool $do_search use browser's preferred language detection functions. Defaults to false.
  335. * @param bool $default set $sm_language to $squirrelmail_default_language if language detection fails or language is not set. Defaults to false.
  336. * @return int function execution error codes.
  337. */
  338. function set_up_language($sm_language, $do_search = false, $default = false) {
  339. static $SetupAlready = 0;
  340. global $use_gettext, $languages,
  341. $squirrelmail_language, $squirrelmail_default_language, $default_charset,
  342. $sm_notAlias, $username, $data_dir;
  343. if ($SetupAlready) {
  344. return;
  345. }
  346. $SetupAlready = TRUE;
  347. sqgetGlobalVar('HTTP_ACCEPT_LANGUAGE', $accept_lang, SQ_SERVER);
  348. /**
  349. * If function is asked to detect preferred language
  350. * OR squirrelmail default language is set to empty string
  351. * AND
  352. * squirrelmail language ($sm_language) is empty string
  353. * (not set in user's prefs and no cookie with language info)
  354. * AND
  355. * browser provides list of preferred languages
  356. * THEN
  357. * get preferred language from HTTP_ACCEPT_LANGUAGE header
  358. */
  359. if (($do_search || empty($squirrelmail_default_language)) &&
  360. ! $sm_language &&
  361. isset($accept_lang)) {
  362. // TODO: use more than one language, if first language is not available
  363. // FIXME: function assumes that string contains two or more characters.
  364. // FIXME: some languages use 5 chars
  365. $sm_language = substr($accept_lang, 0, 2);
  366. }
  367. /**
  368. * If language preference is not set OR script asks to use default language
  369. * AND
  370. * default squirrelmail language is not set to empty string
  371. * THEN
  372. * use default squirrelmail language value from configuration.
  373. */
  374. if ((!$sm_language||$default) &&
  375. ! empty($squirrelmail_default_language)) {
  376. $squirrelmail_language = $squirrelmail_default_language;
  377. $sm_language = $squirrelmail_default_language;
  378. }
  379. /** provide failsafe language when detection fails */
  380. if (! $sm_language) $sm_language='en_US';
  381. $sm_notAlias = $sm_language;
  382. // Catching removed translation
  383. // System reverts to English translation if user prefs contain translation
  384. // that is not available in $languages array
  385. if (!isset($languages[$sm_notAlias])) {
  386. $sm_notAlias="en_US";
  387. }
  388. while (isset($languages[$sm_notAlias]['ALIAS'])) {
  389. $sm_notAlias = $languages[$sm_notAlias]['ALIAS'];
  390. }
  391. if ( isset($sm_language) &&
  392. $use_gettext &&
  393. $sm_language != '' &&
  394. isset($languages[$sm_notAlias]['CHARSET']) ) {
  395. sq_bindtextdomain( 'squirrelmail', SM_PATH . 'locale/' );
  396. sq_textdomain( 'squirrelmail' );
  397. // Use LOCALE key, if it is set.
  398. if (isset($languages[$sm_notAlias]['LOCALE'])){
  399. $longlocale=$languages[$sm_notAlias]['LOCALE'];
  400. } else {
  401. $longlocale=$sm_notAlias;
  402. }
  403. // try setting locale
  404. $retlocale=sq_setlocale(LC_ALL, $longlocale);
  405. // check if locale is set and assign that locale to $longlocale
  406. // in order to use it in putenv calls.
  407. if (! is_bool($retlocale)) {
  408. $longlocale=$retlocale;
  409. } elseif (is_array($longlocale)) {
  410. // setting of all locales failed.
  411. // we need string instead of array used in LOCALE key.
  412. $longlocale=$sm_notAlias;
  413. }
  414. if ( !((bool)ini_get('safe_mode')) &&
  415. getenv( 'LC_ALL' ) != $longlocale ) {
  416. putenv( "LC_ALL=$longlocale" );
  417. putenv( "LANG=$longlocale" );
  418. putenv( "LANGUAGE=$longlocale" );
  419. putenv( "LC_NUMERIC=C" );
  420. if ($sm_notAlias=='tr_TR') putenv( "LC_CTYPE=C" );
  421. }
  422. // Workaround for plugins that use numbers with floating point
  423. // It might be removed if plugins use correct decimal delimiters
  424. // according to locale settings.
  425. setlocale(LC_NUMERIC, 'C');
  426. // Workaround for specific Turkish strtolower/strtoupper rules.
  427. // Many functions expect English conversion rules.
  428. if ($sm_notAlias=='tr_TR') setlocale(LC_CTYPE,'C');
  429. /**
  430. * Set text direction/alignment variables
  431. * When language environment is setup, scripts can use these globals
  432. * without accessing $languages directly and making checks for optional
  433. * array key.
  434. */
  435. global $text_direction, $left_align, $right_align;
  436. if (isset($languages[$sm_notAlias]['DIR']) &&
  437. $languages[$sm_notAlias]['DIR'] == 'rtl') {
  438. /**
  439. * Text direction
  440. * @global string $text_direction
  441. */
  442. $text_direction='rtl';
  443. /**
  444. * Left alignment
  445. * @global string $left_align
  446. */
  447. $left_align='right';
  448. /**
  449. * Right alignment
  450. * @global string $right_align
  451. */
  452. $right_align='left';
  453. } else {
  454. $text_direction='ltr';
  455. $left_align='left';
  456. $right_align='right';
  457. }
  458. $squirrelmail_language = $sm_notAlias;
  459. if ($squirrelmail_language == 'ja_JP') {
  460. header ('Content-Type: text/html; charset=EUC-JP');
  461. if (!function_exists('mb_internal_encoding')) {
  462. // Error messages can't be displayed here
  463. $error = 1;
  464. // Revert to English if possible.
  465. if (function_exists('setPref') && $username!='' && $data_dir!="") {
  466. setPref($data_dir, $username, 'language', "en_US");
  467. $error = 2;
  468. }
  469. // stop further execution in order not to get php errors on mb_internal_encoding().
  470. return $error;
  471. }
  472. if (function_exists('mb_language')) {
  473. mb_language('Japanese');
  474. }
  475. mb_internal_encoding('EUC-JP');
  476. mb_http_output('pass');
  477. } elseif ($squirrelmail_language == 'en_US') {
  478. header( 'Content-Type: text/html; charset=' . $default_charset );
  479. } else {
  480. header( 'Content-Type: text/html; charset=' . $languages[$sm_notAlias]['CHARSET'] );
  481. }
  482. /**
  483. * mbstring.func_overload fix (#929644).
  484. *
  485. * php mbstring extension can replace standard string functions with their multibyte
  486. * equivalents. See http://www.php.net/ref.mbstring#mbstring.overload. This feature
  487. * was added in php v.4.2.0
  488. *
  489. * Some SquirrelMail functions work with 8bit strings in bytes. If interface is forced
  490. * to use mbstring functions and mbstring internal encoding is set to multibyte charset,
  491. * interface can't trust regular string functions. Due to mbstring overloading design
  492. * limits php scripts can't control this setting.
  493. *
  494. * This hack should fix some issues related to 8bit strings in passwords. Correct fix is
  495. * to disable mbstring overloading. Japanese translation uses different internal encoding.
  496. */
  497. if ($squirrelmail_language != 'ja_JP' &&
  498. function_exists('mb_internal_encoding') &&
  499. check_php_version(4,2,0) &&
  500. (int)ini_get('mbstring.func_overload')!=0) {
  501. mb_internal_encoding('pass');
  502. }
  503. }
  504. return 0;
  505. }
  506. /**
  507. * Sets default_charset variable according to the one that is used by user's translations.
  508. *
  509. * Function changes global $default_charset variable in order to be sure, that it
  510. * contains charset used by user's translation. Sanity of $squirrelmail_language
  511. * and $default_charset combination is also tested.
  512. *
  513. * There can be a $default_charset setting in the
  514. * config.php file, but the user may have a different language
  515. * selected for a user interface. This function checks the
  516. * language selected by the user and tags the outgoing messages
  517. * with the appropriate charset corresponding to the language
  518. * selection. This is "more right" (tm), than just stamping the
  519. * message blindly with the system-wide $default_charset.
  520. */
  521. function set_my_charset(){
  522. global $data_dir, $username, $default_charset, $languages, $squirrelmail_language;
  523. $my_language = getPref($data_dir, $username, 'language');
  524. if (!$my_language) {
  525. $my_language = $squirrelmail_language ;
  526. }
  527. // Catch removed translation
  528. if (!isset($languages[$my_language])) {
  529. $my_language="en_US";
  530. }
  531. while (isset($languages[$my_language]['ALIAS'])) {
  532. $my_language = $languages[$my_language]['ALIAS'];
  533. }
  534. $my_charset = $languages[$my_language]['CHARSET'];
  535. if ($my_language!='en_US') {
  536. $default_charset = $my_charset;
  537. }
  538. }
  539. /**
  540. * Replaces non-braking spaces inserted by some browsers with regular space
  541. *
  542. * This function can be used to replace non-braking space symbols
  543. * that are inserted in forms by some browsers instead of normal
  544. * space symbol.
  545. *
  546. * @param string $string Text that needs to be cleaned
  547. * @param string $charset Charset used in text
  548. * @return string Cleaned text
  549. */
  550. function cleanup_nbsp($string,$charset) {
  551. // reduce number of case statements
  552. if (stristr('iso-8859-',substr($charset,0,9))){
  553. $output_charset="iso-8859-x";
  554. }
  555. if (stristr('windows-125',substr($charset,0,11))){
  556. $output_charset="cp125x";
  557. }
  558. if (stristr('koi8',substr($charset,0,4))){
  559. $output_charset="koi8-x";
  560. }
  561. if (! isset($output_charset)){
  562. $output_charset=strtolower($charset);
  563. }
  564. // where is non-braking space symbol
  565. switch($output_charset):
  566. case "iso-8859-x":
  567. case "cp125x":
  568. case "iso-2022-jp":
  569. $nbsp="\xA0";
  570. break;
  571. case "koi8-x":
  572. $nbsp="\x9A";
  573. break;
  574. case "utf-8":
  575. $nbsp="\xC2\xA0";
  576. break;
  577. default:
  578. // don't change string if charset is unmatched
  579. return $string;
  580. endswitch;
  581. // return space instead of non-braking space.
  582. return str_replace($nbsp,' ',$string);
  583. }
  584. /**
  585. * Function informs if it is safe to convert given charset to the one that is used by user.
  586. *
  587. * It is safe to use conversion only if user uses utf-8 encoding and when
  588. * converted charset is similar to the one that is used by user.
  589. *
  590. * @param string $input_charset Charset of text that needs to be converted
  591. * @return bool is it possible to convert to user's charset
  592. */
  593. function is_conversion_safe($input_charset) {
  594. global $languages, $sm_notAlias, $default_charset, $lossy_encoding;
  595. if (isset($lossy_encoding) && $lossy_encoding )
  596. return true;
  597. // convert to lower case
  598. $input_charset = strtolower($input_charset);
  599. // Is user's locale Unicode based ?
  600. if ( $default_charset == "utf-8" ) {
  601. return true;
  602. }
  603. // Charsets that are similar
  604. switch ($default_charset) {
  605. case "windows-1251":
  606. if ( $input_charset == "iso-8859-5" ||
  607. $input_charset == "koi8-r" ||
  608. $input_charset == "koi8-u" ) {
  609. return true;
  610. } else {
  611. return false;
  612. }
  613. case "windows-1257":
  614. if ( $input_charset == "iso-8859-13" ||
  615. $input_charset == "iso-8859-4" ) {
  616. return true;
  617. } else {
  618. return false;
  619. }
  620. case "iso-8859-4":
  621. if ( $input_charset == "iso-8859-13" ||
  622. $input_charset == "windows-1257" ) {
  623. return true;
  624. } else {
  625. return false;
  626. }
  627. case "iso-8859-5":
  628. if ( $input_charset == "windows-1251" ||
  629. $input_charset == "koi8-r" ||
  630. $input_charset == "koi8-u" ) {
  631. return true;
  632. } else {
  633. return false;
  634. }
  635. case "iso-8859-13":
  636. if ( $input_charset == "iso-8859-4" ||
  637. $input_charset == "windows-1257" ) {
  638. return true;
  639. } else {
  640. return false;
  641. }
  642. case "koi8-r":
  643. if ( $input_charset == "windows-1251" ||
  644. $input_charset == "iso-8859-5" ||
  645. $input_charset == "koi8-u" ) {
  646. return true;
  647. } else {
  648. return false;
  649. }
  650. case "koi8-u":
  651. if ( $input_charset == "windows-1251" ||
  652. $input_charset == "iso-8859-5" ||
  653. $input_charset == "koi8-r" ) {
  654. return true;
  655. } else {
  656. return false;
  657. }
  658. default:
  659. return false;
  660. }
  661. }
  662. /**
  663. * Converts html character entities to numeric entities
  664. *
  665. * SquirrelMail encoding functions work only with numeric entities.
  666. * This function fixes issues with decoding functions that might convert
  667. * some symbols to character entities. Issue is specific to PHP recode
  668. * extension decoding. Function is used internally in charset_convert()
  669. * function.
  670. * @param string $str string that might contain html character entities
  671. * @return string string with character entities converted to decimals.
  672. * @since 1.5.2
  673. */
  674. function sqi18n_convert_entities($str) {
  675. $entities = array(
  676. // Latin 1
  677. '&nbsp;' => '&#160;',
  678. '&iexcl;' => '&#161;',
  679. '&cent;' => '&#162;',
  680. '&pound;' => '&#163;',
  681. '&curren;' => '&#164;',
  682. '&yen;' => '&#165;',
  683. '&brvbar;' => '&#166;',
  684. '&sect;' => '&#167;',
  685. '&uml;' => '&#168;',
  686. '&copy;' => '&#169;',
  687. '&ordf;' => '&#170;',
  688. '&laquo;' => '&#171;',
  689. '&not;' => '&#172;',
  690. '&shy;' => '&#173;',
  691. '&reg;' => '&#174;',
  692. '&macr;' => '&#175;',
  693. '&deg;' => '&#176;',
  694. '&plusmn;' => '&#177;',
  695. '&sup2;' => '&#178;',
  696. '&sup3;' => '&#179;',
  697. '&acute;' => '&#180;',
  698. '&micro;' => '&#181;',
  699. '&para;' => '&#182;',
  700. '&middot;' => '&#183;',
  701. '&cedil;' => '&#184;',
  702. '&sup1;' => '&#185;',
  703. '&ordm;' => '&#186;',
  704. '&raquo;' => '&#187;',
  705. '&frac14;' => '&#188;',
  706. '&frac12;' => '&#189;',
  707. '&frac34;' => '&#190;',
  708. '&iquest;' => '&#191;',
  709. '&Agrave;' => '&#192;',
  710. '&Aacute;' => '&#193;',
  711. '&Acirc;' => '&#194;',
  712. '&Atilde;' => '&#195;',
  713. '&Auml;' => '&#196;',
  714. '&Aring;' => '&#197;',
  715. '&AElig;' => '&#198;',
  716. '&Ccedil;' => '&#199;',
  717. '&Egrave;' => '&#200;',
  718. '&Eacute;' => '&#201;',
  719. '&Ecirc;' => '&#202;',
  720. '&Euml;' => '&#203;',
  721. '&Igrave;' => '&#204;',
  722. '&Iacute;' => '&#205;',
  723. '&Icirc;' => '&#206;',
  724. '&Iuml;' => '&#207;',
  725. '&ETH;' => '&#208;',
  726. '&Ntilde;' => '&#209;',
  727. '&Ograve;' => '&#210;',
  728. '&Oacute;' => '&#211;',
  729. '&Ocirc;' => '&#212;',
  730. '&Otilde;' => '&#213;',
  731. '&Ouml;' => '&#214;',
  732. '&times;' => '&#215;',
  733. '&Oslash;' => '&#216;',
  734. '&Ugrave;' => '&#217;',
  735. '&Uacute;' => '&#218;',
  736. '&Ucirc;' => '&#219;',
  737. '&Uuml;' => '&#220;',
  738. '&Yacute;' => '&#221;',
  739. '&THORN;' => '&#222;',
  740. '&szlig;' => '&#223;',
  741. '&agrave;' => '&#224;',
  742. '&aacute;' => '&#225;',
  743. '&acirc;' => '&#226;',
  744. '&atilde;' => '&#227;',
  745. '&auml;' => '&#228;',
  746. '&aring;' => '&#229;',
  747. '&aelig;' => '&#230;',
  748. '&ccedil;' => '&#231;',
  749. '&egrave;' => '&#232;',
  750. '&eacute;' => '&#233;',
  751. '&ecirc;' => '&#234;',
  752. '&euml;' => '&#235;',
  753. '&igrave;' => '&#236;',
  754. '&iacute;' => '&#237;',
  755. '&icirc;' => '&#238;',
  756. '&iuml;' => '&#239;',
  757. '&eth;' => '&#240;',
  758. '&ntilde;' => '&#241;',
  759. '&ograve;' => '&#242;',
  760. '&oacute;' => '&#243;',
  761. '&ocirc;' => '&#244;',
  762. '&otilde;' => '&#245;',
  763. '&ouml;' => '&#246;',
  764. '&divide;' => '&#247;',
  765. '&oslash;' => '&#248;',
  766. '&ugrave;' => '&#249;',
  767. '&uacute;' => '&#250;',
  768. '&ucirc;' => '&#251;',
  769. '&uuml;' => '&#252;',
  770. '&yacute;' => '&#253;',
  771. '&thorn;' => '&#254;',
  772. '&yuml;' => '&#255;',
  773. // Latin Extended-A
  774. '&OElig;' => '&#338;',
  775. '&oelig;' => '&#339;',
  776. '&Scaron;' => '&#352;',
  777. '&scaron;' => '&#353;',
  778. '&Yuml;' => '&#376;',
  779. // Spacing Modifier Letters
  780. '&circ;' => '&#710;',
  781. '&tilde;' => '&#732;',
  782. // General Punctuation
  783. '&ensp;' => '&#8194;',
  784. '&emsp;' => '&#8195;',
  785. '&thinsp;' => '&#8201;',
  786. '&zwnj;' => '&#8204;',
  787. '&zwj;' => '&#8205;',
  788. '&lrm;' => '&#8206;',
  789. '&rlm;' => '&#8207;',
  790. '&ndash;' => '&#8211;',
  791. '&mdash;' => '&#8212;',
  792. '&lsquo;' => '&#8216;',
  793. '&rsquo;' => '&#8217;',
  794. '&sbquo;' => '&#8218;',
  795. '&ldquo;' => '&#8220;',
  796. '&rdquo;' => '&#8221;',
  797. '&bdquo;' => '&#8222;',
  798. '&dagger;' => '&#8224;',
  799. '&Dagger;' => '&#8225;',
  800. '&permil;' => '&#8240;',
  801. '&lsaquo;' => '&#8249;',
  802. '&rsaquo;' => '&#8250;',
  803. '&euro;' => '&#8364;',
  804. // Latin Extended-B
  805. '&fnof;' => '&#402;',
  806. // Greek
  807. '&Alpha;' => '&#913;',
  808. '&Beta;' => '&#914;',
  809. '&Gamma;' => '&#915;',
  810. '&Delta;' => '&#916;',
  811. '&Epsilon;' => '&#917;',
  812. '&Zeta;' => '&#918;',
  813. '&Eta;' => '&#919;',
  814. '&Theta;' => '&#920;',
  815. '&Iota;' => '&#921;',
  816. '&Kappa;' => '&#922;',
  817. '&Lambda;' => '&#923;',
  818. '&Mu;' => '&#924;',
  819. '&Nu;' => '&#925;',
  820. '&Xi;' => '&#926;',
  821. '&Omicron;' => '&#927;',
  822. '&Pi;' => '&#928;',
  823. '&Rho;' => '&#929;',
  824. '&Sigma;' => '&#931;',
  825. '&Tau;' => '&#932;',
  826. '&Upsilon;' => '&#933;',
  827. '&Phi;' => '&#934;',
  828. '&Chi;' => '&#935;',
  829. '&Psi;' => '&#936;',
  830. '&Omega;' => '&#937;',
  831. '&alpha;' => '&#945;',
  832. '&beta;' => '&#946;',
  833. '&gamma;' => '&#947;',
  834. '&delta;' => '&#948;',
  835. '&epsilon;' => '&#949;',
  836. '&zeta;' => '&#950;',
  837. '&eta;' => '&#951;',
  838. '&theta;' => '&#952;',
  839. '&iota;' => '&#953;',
  840. '&kappa;' => '&#954;',
  841. '&lambda;' => '&#955;',
  842. '&mu;' => '&#956;',
  843. '&nu;' => '&#957;',
  844. '&xi;' => '&#958;',
  845. '&omicron;' => '&#959;',
  846. '&pi;' => '&#960;',
  847. '&rho;' => '&#961;',
  848. '&sigmaf;' => '&#962;',
  849. '&sigma;' => '&#963;',
  850. '&tau;' => '&#964;',
  851. '&upsilon;' => '&#965;',
  852. '&phi;' => '&#966;',
  853. '&chi;' => '&#967;',
  854. '&psi;' => '&#968;',
  855. '&omega;' => '&#969;',
  856. '&thetasym;' => '&#977;',
  857. '&upsih;' => '&#978;',
  858. '&piv;' => '&#982;',
  859. // General Punctuation
  860. '&bull;' => '&#8226;',
  861. '&hellip;' => '&#8230;',
  862. '&prime;' => '&#8242;',
  863. '&Prime;' => '&#8243;',
  864. '&oline;' => '&#8254;',
  865. '&frasl;' => '&#8260;',
  866. // Letterlike Symbols
  867. '&weierp;' => '&#8472;',
  868. '&image;' => '&#8465;',
  869. '&real;' => '&#8476;',
  870. '&trade;' => '&#8482;',
  871. '&alefsym;' => '&#8501;',
  872. // Arrows
  873. '&larr;' => '&#8592;',
  874. '&uarr;' => '&#8593;',
  875. '&rarr;' => '&#8594;',
  876. '&darr;' => '&#8595;',
  877. '&harr;' => '&#8596;',
  878. '&crarr;' => '&#8629;',
  879. '&lArr;' => '&#8656;',
  880. '&uArr;' => '&#8657;',
  881. '&rArr;' => '&#8658;',
  882. '&dArr;' => '&#8659;',
  883. '&hArr;' => '&#8660;',
  884. // Mathematical Operators
  885. '&forall;' => '&#8704;',
  886. '&part;' => '&#8706;',
  887. '&exist;' => '&#8707;',
  888. '&empty;' => '&#8709;',
  889. '&nabla;' => '&#8711;',
  890. '&isin;' => '&#8712;',
  891. '&notin;' => '&#8713;',
  892. '&ni;' => '&#8715;',
  893. '&prod;' => '&#8719;',
  894. '&sum;' => '&#8721;',
  895. '&minus;' => '&#8722;',
  896. '&lowast;' => '&#8727;',
  897. '&radic;' => '&#8730;',
  898. '&prop;' => '&#8733;',
  899. '&infin;' => '&#8734;',
  900. '&ang;' => '&#8736;',
  901. '&and;' => '&#8743;',
  902. '&or;' => '&#8744;',
  903. '&cap;' => '&#8745;',
  904. '&cup;' => '&#8746;',
  905. '&int;' => '&#8747;',
  906. '&there4;' => '&#8756;',
  907. '&sim;' => '&#8764;',
  908. '&cong;' => '&#8773;',
  909. '&asymp;' => '&#8776;',
  910. '&ne;' => '&#8800;',
  911. '&equiv;' => '&#8801;',
  912. '&le;' => '&#8804;',
  913. '&ge;' => '&#8805;',
  914. '&sub;' => '&#8834;',
  915. '&sup;' => '&#8835;',
  916. '&nsub;' => '&#8836;',
  917. '&sube;' => '&#8838;',
  918. '&supe;' => '&#8839;',
  919. '&oplus;' => '&#8853;',
  920. '&otimes;' => '&#8855;',
  921. '&perp;' => '&#8869;',
  922. '&sdot;' => '&#8901;',
  923. // Miscellaneous Technical
  924. '&lceil;' => '&#8968;',
  925. '&rceil;' => '&#8969;',
  926. '&lfloor;' => '&#8970;',
  927. '&rfloor;' => '&#8971;',
  928. '&lang;' => '&#9001;',
  929. '&rang;' => '&#9002;',
  930. // Geometric Shapes
  931. '&loz;' => '&#9674;',
  932. // Miscellaneous Symbols
  933. '&spades;' => '&#9824;',
  934. '&clubs;' => '&#9827;',
  935. '&hearts;' => '&#9829;',
  936. '&diams;' => '&#9830;');
  937. $str = str_replace(array_keys($entities), array_values($entities), $str);
  938. return $str;
  939. }
  940. /* ------------------------------ main --------------------------- */
  941. global $squirrelmail_language, $languages, $use_gettext;
  942. if (! sqgetGlobalVar('squirrelmail_language',$squirrelmail_language,SQ_COOKIE)) {
  943. $squirrelmail_language = '';
  944. }
  945. /**
  946. * Array specifies the available translations.
  947. *
  948. * Structure of array:
  949. * $languages['language']['variable'] = 'value'
  950. *
  951. * Possible 'variable' names:
  952. * NAME - Translation name in English
  953. * CHARSET - Encoding used by translation
  954. * ALIAS - used when 'language' is only short name and 'value' should provide long language name
  955. * ALTNAME - Native translation name. Any 8bit symbols must be html encoded.
  956. * LOCALE - Full locale name (in xx_XX.charset format). It can use array with more than one locale name since 1.4.5 and 1.5.1
  957. * DIR - Text direction. Used to define Right-to-Left languages. Possible values 'rtl' or 'ltr'. If undefined - defaults to 'ltr'
  958. * XTRA_CODE - translation uses special functions. See doc/i18n.txt
  959. *
  960. * Each 'language' definition requires NAME+CHARSET or ALIAS variables.
  961. *
  962. * @name $languages
  963. * @global array $languages
  964. */
  965. $languages['en_US']['NAME'] = 'English';
  966. $languages['en_US']['CHARSET'] = 'iso-8859-1';
  967. $languages['en_US']['LOCALE'] = 'en_US.ISO8859-1';
  968. $languages['en']['ALIAS'] = 'en_US';
  969. /**
  970. * Automatic translation loading from setup.php files.
  971. * Solution for bug. 1240889.
  972. * setup.php file can contain $languages array entries and XTRA_CODE functions.
  973. */
  974. if (is_dir(SM_PATH . 'locale') &&
  975. is_readable(SM_PATH . 'locale')) {
  976. $localedir = dir(SM_PATH . 'locale');
  977. while($lang_dir=$localedir->read()) {
  978. // remove trailing slash, if present
  979. if (substr($lang_dir,-1)=='/') {
  980. $lang_dir = substr($lang_dir,0,-1);
  981. }
  982. if ($lang_dir != '..' && $lang_dir != '.' && $lang_dir != 'CVS' &&
  983. $lang_dir != '.svn' && is_dir(SM_PATH.'locale/'.$lang_dir) &&
  984. file_exists(SM_PATH.'locale/'.$lang_dir.'/setup.php')) {
  985. include_once(SM_PATH.'locale/'.$lang_dir.'/setup.php');
  986. }
  987. }
  988. $localedir->close();
  989. }
  990. /* Detect whether gettext is installed. */
  991. $gettext_flags = 0;
  992. if (function_exists('_')) {
  993. $gettext_flags += 1;
  994. }
  995. if (function_exists('bindtextdomain')) {
  996. $gettext_flags += 2;
  997. }
  998. if (function_exists('textdomain')) {
  999. $gettext_flags += 4;
  1000. }
  1001. if (function_exists('ngettext')) {
  1002. $gettext_flags += 8;
  1003. }
  1004. /* If gettext is fully loaded, cool */
  1005. if ($gettext_flags == 15) {
  1006. $use_gettext = true;
  1007. }
  1008. /* If ngettext support is missing, load it */
  1009. elseif ($gettext_flags == 7) {
  1010. $use_gettext = true;
  1011. // load internal ngettext functions
  1012. include_once(SM_PATH . 'class/l10n.class.php');
  1013. include_once(SM_PATH . 'functions/ngettext.php');
  1014. }
  1015. /* If we can fake gettext, try that */
  1016. elseif ($gettext_flags == 0) {
  1017. $use_gettext = true;
  1018. include_once(SM_PATH . 'functions/gettext.php');
  1019. } else {
  1020. /* Uh-ho. A weird install */
  1021. if (! $gettext_flags & 1) {
  1022. /**
  1023. * Function is used as replacement in broken installs
  1024. * @ignore
  1025. */
  1026. function _($str) {
  1027. return $str;
  1028. }
  1029. }
  1030. if (! $gettext_flags & 2) {
  1031. /**
  1032. * Function is used as replacement in broken installs
  1033. * @ignore
  1034. */
  1035. function bindtextdomain() {
  1036. return;
  1037. }
  1038. }
  1039. if (! $gettext_flags & 4) {
  1040. /**
  1041. * Function is used as replacemet in broken installs
  1042. * @ignore
  1043. */
  1044. function textdomain() {
  1045. return;
  1046. }
  1047. }
  1048. if (! $gettext_flags & 8) {
  1049. /**
  1050. * Function is used as replacemet in broken installs
  1051. * @ignore
  1052. */
  1053. function ngettext($str,$str2,$number) {
  1054. if ($number>1) {
  1055. return $str2;
  1056. } else {
  1057. return $str;
  1058. }
  1059. }
  1060. }
  1061. if (! function_exists('dgettext')) {
  1062. /**
  1063. * Replacement for broken setups.
  1064. * @ignore
  1065. */
  1066. function dgettext($domain,$str) {
  1067. return $str;
  1068. }
  1069. }
  1070. if (! function_exists('dngettext')) {
  1071. /**
  1072. * Replacement for broken setups
  1073. * @ignore
  1074. */
  1075. function dngettext($domain,$str1,$strn,$number) {
  1076. return ($number==1 ? $str1 : $strn);
  1077. }
  1078. }
  1079. }