<?php
 class language { public static $selected = []; public static $languages = []; private static $_cache = []; private static $_cache_token; private static $_accessed_translations = []; public static function init() { if (preg_match('#^'. preg_quote(WS_DIR_APP . BACKEND_ALIAS, '#') .'/#', $_SERVER['REQUEST_URI'])) { if (!isset(session::$data['backend'])) { session::$data['backend'] = []; } if (!isset(session::$data['backend']['language'])) { session::$data['backend']['language'] = []; } self::$selected = &session::$data['backend']['language']; } else { if (!isset(session::$data['language'])) session::$data['language'] = []; self::$selected = &session::$data['language']; } self::load(); self::set(); if (strtoupper(self::$selected['charset']) != 'UTF-8') { self::load(); self::set(self::$selected['code']); } self::$_cache_token = cache::token('translations', ['endpoint', 'language'], 'memory'); if (!self::$_cache['translations'] = cache::get(self::$_cache_token)) { self::$_cache['translations'] = []; $translations_query = database::query( "select id, code, if(text_". self::$selected['code'] ." != '', text_". self::$selected['code'] .", text_en) as text from ". DB_TABLE_PREFIX ."translations
          where ". (preg_match('#^'. preg_quote(ltrim(WS_DIR_ADMIN, '/'), '#') .'.*#', route::$request) ? "backend = 1" : "frontend = 1") ."
          having text != '';" ); while ($translation = database::fetch($translations_query)) { self::$_cache['translations'][self::$selected['code']][$translation['code']] = $translation['text']; } } event::register('before_capture', [__CLASS__, 'before_capture']); event::register('before_output', [__CLASS__, 'before_output']); event::register('shutdown', [__CLASS__, 'shutdown']); } public static function before_capture() { header('Content-Language: '. self::$selected['code']); } public static function before_output() { if (empty(self::$selected['code'])) { throw new Error('No language set'); } } public static function shutdown() { database::query( "update ". DB_TABLE_PREFIX ."translations
        set ". (preg_match('#^'. preg_quote(ltrim(WS_DIR_ADMIN, '/'), '#') .'.*#', route::$request) ? "backend = 1" : "frontend = 1") .",
          date_accessed = '". date('Y-m-d H:i:s') ."'
        where code in ('". implode("', '", database::input(self::$_accessed_translations)) ."');" ); cache::set(self::$_cache_token, self::$_cache['translations']); } public static function load() { self::$languages = []; $languages_query = database::query( "select * from ". DB_TABLE_PREFIX ."languages
        where status
        order by priority, name;" ); while ($row = database::fetch($languages_query)) { self::$languages[$row['code']] = $row; } } public static function set($code=null) { if (empty($code)) $code = self::identify(); if (!isset(self::$languages[$code])) { trigger_error('Cannot set unsupported language ('. $code .')', E_USER_WARNING); $code = self::identify(); } if (preg_match('#^'. preg_quote(WS_DIR_APP . BACKEND_ALIAS, '#') .'/#', $_SERVER['REQUEST_URI'])) { session::$data['backend']['language'] = self::$languages[$code]; } else { session::$data['language'] = self::$languages[$code]; } if (!empty($_COOKIE['cookies_accepted']) || !settings::get('cookie_policy')) { header('Set-Cookie: language_code='. $code .'; Path='. WS_DIR_APP .'; Expires='. gmdate('r', strtotime('+3 months')) .'; SameSite=Lax', false); } if (!setlocale(LC_TIME, preg_split('#\s*,\s*#', self::$selected['locale'], -1, PREG_SPLIT_NO_EMPTY))) { trigger_error('Warning: Failed setting locale '. self::$selected['locale'] .' for '. self::$selected['code'], E_USER_WARNING); } mb_internal_encoding(self::$selected['charset']); mb_regex_encoding(self::$selected['charset']); mb_http_output(self::$selected['charset']); database::set_encoding(self::$selected['charset']); } public static function identify() { $all_languages = array_keys(self::$languages); $enabled_languages = []; foreach (self::$languages as $language) { if (!empty(user::$data['id']) || $language['status'] == 1) $enabled_languages[] = $language['code']; } if (preg_match('#^'. preg_quote(WS_DIR_APP . BACKEND_ALIAS, '#') .'/#', $_SERVER['REQUEST_URI'])) { if (!empty($_GET['language'])) { if (in_array($_GET['language'], $all_languages)) return $_GET['language']; } if (!empty(self::$selected['code']) && in_array(self::$selected['code'], $all_languages)) { return self::$selected['code']; } } foreach ($enabled_languages as $language_code) { if (self::$languages[$language_code]['url_type'] == 'domain') { if (!empty(self::$languages[$language_code]['domain_name']) && preg_match('#^'. preg_quote(self::$languages[$language_code]['domain_name'], '#') .'$#', $_SERVER['HTTP_HOST'])) { return $language_code; } } } if (!empty($_GET['language'])) { if (in_array($_GET['language'], $all_languages)) return $_GET['language']; } $code = current(explode('/', substr($_SERVER['REQUEST_URI'], strlen(WS_DIR_APP)))); if (in_array($code, $all_languages)) return $code; foreach ($enabled_languages as $language_code) { if (self::$languages[$language_code]['url_type'] == 'none') { $webpath = strtok($_SERVER['REQUEST_URI'], '?'); if (!$webpath || !preg_match('#^'. preg_quote(WS_DIR_APP, '#') .'[a-z]{2}(/|$)#', $webpath)) { return $language_code; } } } if (isset(self::$selected['code']) && in_array(self::$selected['code'], $all_languages)) return self::$selected['code']; if (isset($_COOKIE['language_code']) && in_array($_COOKIE['language_code'], $all_languages)) return $_COOKIE['language_code']; if (preg_match('#\.([a-z]{2})$#', $_SERVER['HTTP_HOST'], $matches)) { $countries_query = database::query( "select * from ". DB_TABLE_PREFIX ."countries
          where iso_code_2 = '". database::input(strtoupper($matches[1])) ."'
          limit 1;" ); $country = database::fetch($countries_query); if (!empty($country['language_code']) && in_array($country['language_code'], $enabled_languages)) return $country['language_code']; } if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { $browser_locales = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']); } elseif (isset($_SERVER['LC_CTYPE'])) { $browser_locales = explode(',', $_SERVER['LC_CTYPE']); } else { $browser_locales = []; } foreach ($browser_locales as $browser_locale) { if (preg_match('#('. implode('|', array_keys(self::$languages)) .')-?.*#', $browser_locale, $reg)) { if (!empty($reg[1]) && in_array($reg[1], $enabled_languages)) return $reg[1]; } } if (in_array(settings::get('default_language_code'), $all_languages)) return settings::get('default_language_code'); if (in_array(settings::get('store_language_code'), $all_languages)) return settings::get('store_language_code'); return (!empty($enabled_languages)) ? $enabled_languages[0] : $all_languages[0]; } public static function translate($code, $default=null, $language_code=null) { $code = strtolower($code); self::$_accessed_translations[] = $code; if (empty($language_code)) { $language_code = self::$selected['code']; } if (empty($language_code) || empty(self::$languages[$language_code])) { trigger_error('Unknown language code for translation ('. $language_code .')', E_USER_WARNING); return; } if (isset(self::$_cache['translations'][$language_code][$code])) { return self::$_cache['translations'][$language_code][$code]; } $translation_query = database::query( "select id, text_en, `text_". $language_code ."` from ". DB_TABLE_PREFIX ."translations
        where code = '". database::input($code) ."'
        limit 1;" ); if (!$translation = database::fetch($translation_query)) { database::query( "insert into ". DB_TABLE_PREFIX ."translations
          (code, text_en, html, date_created, date_updated)
          values ('". database::input($code) ."', '". database::input($default, true) ."', '". (($default != strip_tags($default)) ? 1 : 0) ."', '". date('Y-m-d H:i:s') ."', '". date('Y-m-d H:i:s') ."');" ); } if (!empty($translation['text_'.$language_code])) { return self::$_cache['translations'][$language_code][$code] = $translation['text_'.$language_code]; } if (!empty($translation['text_en'])) { $secondary_translation_query = database::query( "select id, text_en, `text_". $language_code ."` from ". DB_TABLE_PREFIX ."translations
          where text_en = '". database::input($translation['text_en']) ."'
          and text_en != ''
          and text_". self::$selected['code'] ." != ''
          limit 1;" ); if ($secondary_translation = database::fetch($secondary_translation_query)) { database::query( "update ". DB_TABLE_PREFIX ."translations
            set `text_". $language_code ."` = '". database::input($translation['text_'.$language_code], true) ."',
            date_updated = '". date('Y-m-d H:i:s') ."'
            where text_en = '". database::input($translation['text_en']) ."'
            and text_". self::$selected['code'] ." = '';" ); return self::$_cache['translations'][$language_code][$code] = $secondary_translation['text_'.$language_code]; } return self::$_cache['translations'][$language_code][$code] = $translation['text_en']; } return self::$_cache['translations'][$language_code][$code] = $default; } public static function number_format($number, $decimals=0) { return number_format((float)$number, $decimals, self::$selected['decimal_point'], self::$selected['thousands_sep']); } public static function strftime($format, $timestamp=null) { if ($timestamp === null) { $timestamp = new \DateTime(); } elseif (is_numeric($timestamp)) { $timestamp = new \DateTime('@' . $timestamp, new DateTimeZone('UTC')); $timestamp->setTimezone(new DateTimeZone(date_default_timezone_get())); } elseif (is_string($timestamp)) { $timestamp = new \DateTime($timestamp); } if (!extension_loaded('intl')) { trigger_error('You need the PHP Intl extension enabled to format dates', E_USER_WARNING); $ts = ($timestamp instanceof \DateTimeInterface) ? $timestamp->getTimestamp() : (int)$timestamp; return date('Y-m-d H:i:s', $ts); } if (!($timestamp instanceof \DateTimeInterface)) { trigger_error('$timestamp argument is neither a valid UNIX timestamp, a valid date-time string or a DateTime object.', E_USER_WARNING); return 'n/a'; } $intl_formats = [ '%a' => 'EEE', '%A' => 'EEEE', '%b' => 'MMM', '%B' => 'MMMM', '%h' => 'MMM', '%p' => 'aa', '%P' => 'aa', ]; $intl_formatter = function (\DateTimeInterface $timestamp, $format) use ($intl_formats) { $tz = $timestamp->getTimezone(); $date_type = IntlDateFormatter::FULL; $time_type = IntlDateFormatter::FULL; $pattern = ''; switch ($format) { case '%c': $date_type = IntlDateFormatter::LONG; $time_type = IntlDateFormatter::SHORT; break; case '%x': $date_type = IntlDateFormatter::SHORT; $time_type = IntlDateFormatter::NONE; break; case '%X': $date_type = IntlDateFormatter::NONE; $time_type = IntlDateFormatter::MEDIUM; break; default: $pattern = $intl_formats[$format]; break; } return (new IntlDateFormatter(self::$selected['code'], $date_type, $time_type, $tz, null, $pattern))->format($timestamp); }; $mappings = [ '%a' => $intl_formatter, '%A' => $intl_formatter, '%d' => 'd', '%e' => 'j', '%j' => function ($timestamp) { return sprintf('%03d', $timestamp->format('z')+1); }, '%u' => 'N', '%w' => 'w', '%U' => function ($timestamp) { $day = new \DateTime(sprintf('%d-01 Sunday', $timestamp->format('Y'))); return intval(($timestamp->format('z') - $day->format('z')) / 7); }, '%W' => function ($timestamp) { $day = new \DateTime(sprintf('%d-01 Monday', $timestamp->format('Y'))); return intval(($timestamp->format('z') - $day->format('z')) / 7); }, '%V' => 'W', '%b' => $intl_formatter, '%B' => $intl_formatter, '%h' => $intl_formatter, '%m' => 'm', '%C' => function ($timestamp) { return (int) $timestamp->format('Y') / 100; }, '%g' => function ($timestamp) { return substr($timestamp->format('o'), -2); }, '%G' => 'o', '%y' => 'y', '%Y' => 'Y', '%H' => 'H', '%k' => 'G', '%I' => 'h', '%l' => 'g', '%M' => 'i', '%p' => $intl_formatter, '%P' => $intl_formatter, '%r' => 'G:i:s A', '%R' => 'H:i', '%S' => 's', '%X' => $intl_formatter, '%z' => 'O', '%Z' => 'T', '%c' => $intl_formatter, '%D' => 'm/d/Y', '%F' => 'Y-m-d', '%s' => 'U', '%x' => $intl_formatter, ]; $out = preg_replace_callback('/(?<!%)(%[a-zA-Z])/', function ($match) use ($mappings, $timestamp) { if ($match[1] == '%n') { return "\n"; } elseif ($match[1] == '%t') { return "\t"; } if (!isset($mappings[$match[1]])) { throw new \InvalidArgumentException(sprintf('Format "%s" is unknown in time format', $match[1])); } $replace = $mappings[$match[1]]; if (is_string($replace)) { return $timestamp->format($replace); } else { return $replace($timestamp, $match[1]); } }, $format); $out = str_replace('%%', '%', $out); return $out; } public static function convert_characters($variable, $from_charset=null, $to_charset=null) { if (empty($from_charset)) $from_charset = self::$selected['charset']; if (empty($to_charset)) $to_charset = self::$selected['charset']; if ($from_charset == $to_charset) return $variable; if (!mb_convert_variables($to_charset, $from_charset, $variable)) { trigger_error('Could not encode variable from '. $from_charset .' to '. $to_charset, E_USER_WARNING); return false; } return $variable; } } 