<?php
/**
 * The NLS:: class provides Native Language Support. This includes common
 * methods for handling language detection and selection, and timezones.
 *
 * $Horde: horde/lib/NLS.php,v 1.50 2003/08/15 17:55:43 jan Exp $
 *
 * Copyright 1999-2003 Jon Parise <jon@horde.org>
 * Copyright 1999-2003 Chuck Hagenbuch <chuck@horde.org>
 * Copyright 2002-2003 Jan Schneider <jan@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Jon Parise <jon@horde.org>
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Jan Schneider <jan@horde.org>
 * @version $Revision: 1.50 $
 * @since   Horde 3.0
 * @package horde
 */
class NLS {

    /**
     * Selects the most preferred language for the current client session.
     *
     * @access public
     *
     * @return string  The selected language abbreviation.
     */
    function select()
    {
        global $nls, $prefs;

        $lang = Horde::getFormData('new_lang');

        /* First, check if language pref is locked and, if so, set it to its
           value */
        if (isset($prefs) && $prefs->isLocked('language')) {
            $language = $prefs->getValue('language');
        /* Check if the user selected a language from the login screen */
        } elseif (!empty($lang)) {
            $language = $lang;
        /* Check if we have a language set in a cookie */
        } elseif (isset($_SESSION['horde_language'])) {
            $language = $_SESSION['horde_language'];

        /* Try browser-accepted languages, then default. */
        } elseif (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            /* The browser supplies a list, so return the first valid one. */
            $browser_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
            foreach ($browser_langs as $lang) {
                $lang = NLS::_map(trim($lang));
                if (NLS::isValid($lang)) {
                    $language = $lang;
                    break;
                } elseif (NLS::isValid(NLS::_map(substr($lang, 0, 2)))) {
                    $language = NLS::_map(substr($lang, 0, 2));
                    break;
                }
            }
        }

        /* No dice auto-detecting, so give them the server default. */
        if (!isset($language)) {
            $language = $nls['defaults']['language'];
        }

        return basename($language);
    }

    /**
     * Sets the language.
     *
     * @access public
     *
     * @param optional string $lang     The language abbriviation.
     */
    function setLang($lang = null)
    {
        if (@file_exists(HORDE_BASE . '/config/nls.php')) {
            include_once HORDE_BASE . '/config/nls.php';
        } else {
            include_once HORDE_BASE . '/config/nls.php.dist';
        }
        if (empty($lang) || !NLS::isValid($lang)) {
            $lang = NLS::select();
        }
        $GLOBALS['language'] = $lang;

        /* First try language with the current charset. */
        $lang_charset = $lang . '.' . NLS::getCharset();
        if ($lang_charset != setlocale(LC_ALL, $lang_charset)) {
            /* Next try language with its default charset. */
            global $nls;
            $charset = !empty($nls['charsets'][$lang]) ? $nls['charsets'][$lang] : $nls['defaults']['charset'];
            $lang_charset = $lang . '.' . $charset;
            NLS::_cachedCharset(0, $charset);
            if ($lang_charset != setlocale(LC_ALL, $lang_charset)) {
                /* At last try language solely. */
                $lang_charset = $lang;
                setlocale(LC_ALL, $lang_charset);
            }
        }
        @putenv('LANG=' . $lang_charset);
        @putenv('LANGUAGE=' . $lang_charset);
    }

    /**
     * Sets the gettext domain.
     *
     * @access public
     *
     * @param string $app        The application name.
     * @param string $directory  The directory where the application's
     *                           LC_MESSAGES directory resides.
     * @param string $charset    The charset.
     */
    function setTextdomain($app, $directory, $charset)
    {
        bindtextdomain($app, $directory);
        textdomain($app);

        /* The existence of this function depends on the platform. */
        if (function_exists('bind_textdomain_codeset')) {
           bind_textdomain_codeset($app, $charset);
        }

        if (!headers_sent()) {
            header('Content-Type: text/html; charset=' . $charset);
        }
    }

    /**
     * Determines whether the supplied language is valid.
     *
     * @access public
     *
     * @param string $language  The abbreviated name of the language.
     *
     * @return boolean  True if the language is valid, false if it's not
     *                  valid or unknown.
     */
    function isValid($language)
    {
        return !empty($GLOBALS['nls']['languages'][$language]);
    }

    /**
     * Maps languages with common two-letter codes (such as nl) to the
     * full gettext code (in this case, nl_NL). Returns the language
     * unmodified if it isn't an alias.
     *
     * @access private
     *
     * @param string $language  The language code to map.
     *
     * @return string  The mapped language code.
     */

    function _map($language)
    {
        require_once dirname(__FILE__) . '/String.php';

        $aliases = &$GLOBALS['nls']['aliases'];

        // First check if the untranslated language can be found
        if (array_key_exists($language, $aliases) &&
            !empty($aliases[$language])) {
            return $aliases[$language];
        }

        // Translate the $language to get broader matches.
        // (eg. de-DE should match de_DE)
        $trans_lang = str_replace('-', '_', $language);
        $lang_parts = explode('_', $trans_lang);
        $trans_lang = String::lower($lang_parts[0]);
        if (array_key_exists(1, $lang_parts)) {
            $trans_lang .= '_' . String::upper($lang_parts[1]);
        }

        // See if we get a match for this
        if (array_key_exists($trans_lang, $aliases) &&
            !empty($aliases[$trans_lang])) {
            return $aliases[$trans_lang];
        }

        // If we get that far down, the language cannot be found.
        // Return $trans_lang.
        return $trans_lang;
    }

    /**
     * Return the charset for the current language.
     *
     * @access public
     *
     * @param optional boolean $no_utf  Do not use UTF-8?
     *
     * @return string  The character set that should be used with the current
     *                 locale settings.
     */
    function getCharset($no_utf = false)
    {
        global $language, $nls;

        /* Get cached results. */
        $cacheKey = intval($no_utf);
        $charset = NLS::_cachedCharset($cacheKey);
        if (!is_null($charset)) {
            return $charset;
        }

        if (!$no_utf) {
            require_once HORDE_BASE . '/lib/Browser.php';
            $browser = &Browser::singleton();

            if ($browser->hasFeature('utf') &&
                (Horde::extensionExists('iconv') ||
                 Horde::extensionExists('mbstring'))) {
                NLS::_cachedCharset($cacheKey, 'UTF-8');
                return 'UTF-8';
            }
        }

        $lang_charset = setlocale(LC_ALL, 0);
        if (!strstr($lang_charset, ';')) {
            $lang_charset = explode('.', $lang_charset);
            if ((count($lang_charset) == 2) && !empty($lang_charset[1]) &&
                (!$no_utf || ($lang_charset[1] != 'UTF-8'))) {
                NLS::_cachedCharset($cacheKey, $lang_charset[1]);
                return $lang_charset[1];
            }
        }

        return (!empty($nls['charsets'][$language])) ? $nls['charsets'][$language] : $nls['defaults']['charset'];
    }
    
    function _cachedCharset($index, $charset = null)
    {
        static $cache;
        
        if (!isset($cache)) {
            $cache = array();
        }
        
        if ($charset == null) {
            return isset($cache[$index]) ? $cache[$index] : null;
        } else {
            $cache[$index] = $charset;
        }
    }

    /**
     * Returns the charset to use for outgoing emails.
     *
     * @return string  The preferred charset for outgoing mails based on
     *                 the user's preferences and the current language.
     */
    function getEmailCharset()
    {
        global $prefs, $language, $nls;

        $charset = $prefs->getValue('sending_charset');
        if (!empty($charset)) {
            return $charset;
        }
        return isset($nls['emails'][$language]) ? $nls['emails'][$language] :
               (isset($nls['charsets'][$language]) ? $nls['charsets'][$language] : $nls['defaults']['charset']);
    }

    /**
     * Check to see if character set is valid for htmlspecialchars() calls.
     *
     * @access public
     *
     * @param string $charset  The character set to check.
     *
     * @return boolean  Is charset valid for the current system?
     */
    function checkCharset($charset)
    {
        static $check;

        if (is_null($charset) || empty($charset)) {
            return false;
        }

        if (isset($check[$charset])) {
            return $check[$charset];
        } elseif (!isset($check)) {
            $check = array();
        }

        $valid = true;

        ini_set('track_errors', 1);
        @htmlspecialchars('', ENT_COMPAT, $charset);
        if (isset($php_errormsg)) {
            $valid = false;
        }
        ini_restore('track_errors');

        $check[$charset] = $valid;

        return $valid;
    }

    /**
     * Sets the current timezone, if available.
     *
     * @access public
     */
    function setTimeZone()
    {
        global $prefs;

        if ($prefs->getValue('timezone') != '') {
            putenv('TZ=' . $prefs->getValue('timezone'));
        }
    }

    /**
     * Get the locale info returned by localconv(), but cache it, to
     * avoid repeated calls.
     *
     * @access public
     *
     * @return array  The results of localeconv().
     */
    function getLocaleInfo()
    {
        static $lc_info;

        if (!isset($lc_info)) {
            $lc_info = localeconv();
        }

        return $lc_info;
    }

}
