<?php

/*****************************************************************************
 *
 * GlobalLanguage.php - Class for handling languages in NagVis
 *
 * Copyright (c) 2004-2016 NagVis Project (Contact: info@nagvis.org)
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

/**
 * @author	Lars Michelsen <lm@larsmichelsen.com>
 */
class GlobalLanguage
{
    /** @var CoreUserCfg */
    private $USERCFG = null;

    /** @var string */
    private $textDomain;

    /** @var string */
    private $sCurrentLanguage;

    /** @var string */
    private $sCurrentEncoding;

    /** @var array */
    private $cache = [];

    /**
     * Class Constructor
     *
     * @param string $textDomain
     * @throws NagVisException
     * @author    Lars Michelsen <lm@larsmichelsen.com>
     */
    public function __construct($textDomain = 'nagvis')
    {
        $this->textDomain = $textDomain;

        // Append encoding (UTF8)
        $this->sCurrentEncoding = 'UTF-8';

        // Check for gettext extension
        require_once('gettext.inc');

        $this->setLanguage();

        T_bindtextdomain($this->textDomain, cfg('paths', 'language'));
        T_bind_textdomain_codeset($this->textDomain, $this->sCurrentEncoding);

        T_textdomain($this->textDomain);

        // Check if native gettext or php-gettext is used
        if (DEBUG && DEBUGLEVEL & 2) {
            if (locale_emulation()) {
                debug('GlobalLanguage: Using php-gettext for translations');
            } else {
                debug('GlobalLanguage: Using native gettext for translations');
            }
        }
    }

    /**
     * Sets the language to be used for future localized strings
     * while processing the current page.
     *
     * @param bool $handleUserCfg
     * @return void
     * @throws NagVisException
     */
    public function setLanguage($handleUserCfg = false)
    {
        if ($handleUserCfg) {
            $this->USERCFG = new CoreUserCfg();
        }

        $this->sCurrentLanguage = $this->gatherCurrentLanguage();

        // Check if choosen language is available
        $this->checkLanguageAvailable($this->sCurrentLanguage, true, true);

        // Set the language to us
        putenv('LC_ALL=' . $this->sCurrentLanguage . '.' . $this->sCurrentEncoding);
        putenv('LANG=' . $this->sCurrentLanguage . '.' . $this->sCurrentEncoding);
        T_setlocale(LC_MESSAGES, $this->sCurrentLanguage . '.' . $this->sCurrentEncoding);
    }

    /**
     * Reads the language to use in NagVis
     *
     * @return string
     * @throws NagVisException
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    private function gatherCurrentLanguage()
    {
        $sReturn = '';
        $aMethods = cfg('global', 'language_detection');

        foreach ($aMethods as $sMethod) {
            if ($sReturn == '') {
                switch ($sMethod) {
                    case 'session':
                        // Read the user choice from user options
                        if ($this->USERCFG !== null) {
                            $sReturn = $this->USERCFG->getValue('language', '');
                        }
                        break;
                    case 'browser':
                        // Read the prefered language from the users browser
                        $sReturn = $this->getBrowserLanguage();
                        break;
                    case 'ip':
                        //@todo: It is also possible to get the country via IP and
                        //       indirectly the language from that country.
                        break;
                    case 'user':
                        // Read the language from url or user config
                        $sReturn = $this->getUserLanguage();

                        // Save language to user config when user set one
                        if (
                            $sReturn != ''
                            && $this->USERCFG !== null
                            && $sReturn != $this->USERCFG->getValue('language', '')
                        ) {
                            $this->USERCFG->doSet(['language' => $sReturn]);
                        }
                        break;
                    case 'config':
                        // Read default language from configuration
                        $sReturn = cfg('global', 'language');
                        break;

                    default:
                        throw new NagVisException(
                            $this->getText(
                                'Invalid mode [MODE] in language_detection option.',
                                ['MODE' => $sMethod]
                            )
                        );
                }
            }
        }

        return $sReturn;
    }

    /**
     * Checks if the user requested a language by the url
     *
     * @return string
     * @throws NagVisException
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    private function getUserLanguage()
    {
        $sLang = '';

        $UHANDLER = new CoreUriHandler();

        // Load the specific params to the UriHandler
        $UHANDLER->parseModSpecificUri(['lang' => MATCH_LANGUAGE_EMPTY]);

        if (
            $UHANDLER->isSetAndNotEmpty('lang')
            // Check if language is available
            && $this->checkLanguageAvailable($UHANDLER->get('lang'), false)
        ) {

            // Get given language
            $sLang = $UHANDLER->get('lang');
        }

        return $sLang;
    }

    /**
     * Trys to detect the language of the browser by analyzing the
     * HTTP_ACCEPT_LANGUAGE var. Returns a language string when found one language
     * which is available
     *
     * @return string
     * @throws NagVisException
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    private function getBrowserLanguage()
    {
        $return = [];
        $langs = [];

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            // break up string into pieces (languages and q factors)
            preg_match_all(
                '/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
                $_SERVER['HTTP_ACCEPT_LANGUAGE'],
                $lang_parse
            );

            if (count($lang_parse[1])) {
                // create a list like "en" => 0.8
                $langs = array_combine($lang_parse[1], $lang_parse[4]);

                // set default to 1 for any without q factor
                foreach ($langs as $lang => $val) {
                    if ($val === '') {
                        $langs[$lang] = 1;
                    }
                }

                // sort list based on value
                arsort($langs, SORT_NUMERIC);
            }
        }

        // Check if the languages are available and then return the most important language which is available
        $sLang = '';
        foreach ($langs as $key => $val) {
            // Format the language keys
            if (str_contains($key, '-')) {
                $a = explode('-', $key);

                $key = $a[0] . '_' . strtoupper($a[1]);
            }

            if ($this->checkLanguageAvailable($key, false)) {
                $sLang = $key;
                break;
            }
        }

        return $sLang;
    }

    /**
     * Returns the string representing the current language
     *
     * @return string
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    public function getCurrentLanguage()
    {
        return $this->sCurrentLanguage;
    }

    /**
     * Checks if the choosen language is available
     *
     * @param string $sLang Language definition string
     * @param bool|int $printErr Print error message or not
     * @param bool $ignoreConf Check language_available config or not
     * @return bool
     * @throws NagVisException
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    private function checkLanguageAvailable($sLang, $printErr = 1, $ignoreConf = false)
    {
        global $CORE;
        // Checks two things:
        // a) The language availabilty in the filesyste,
        // b) Listed language in global/language_available config option
        if (
            in_array($sLang, $CORE->getAvailableLanguages())
            && (
                $ignoreConf
                || in_array($sLang, cfg('global', 'language_available'))
            )
        ) {
            return true;
        } else {
            if ($printErr) {
                throw new NagVisException($this->getText('languageNotFound', ['LANG' => $sLang]));
            }
            return false;
        }
    }

    /**
     * Calls the real getText method and replaces some macros after fetching the
     * text
     *
     * @param	string $id String to be localized
     * @param	string $replace Replace options
     * @return	string	Localized String
     * @author 	Lars Michelsen <lm@larsmichelsen.com>
     */
    public function getText($id, $replace = null)
    {
        // Use cache if available
        // FIXME: At the moment the cache can only be used without macros
        if ($replace === null && isset($this->cache[$id])) {
            return $this->cache[$id];
        }

        $ret = $this->getTextOfId($id);

        if ($replace !== null) {
            $ret = self::getReplacedString($ret, $replace);
        }

        // When the translated string is equal to the requested id and some macros
        // should be replaced it is possible that there is a problem with the
        // gettext/translation mechanism. Then append the imploded
        if ($id === $ret && $replace !== null) {
            if (!is_array($replace)) {
                $ret .= 'Opts: ' . $replace;
            } else {
                // Implode does not return the keys. So simply use json_encode here
                // to show the keys to the user
                $ret .= 'Opts: ' . json_encode($replace);
            }
        }

        // Store in cache for this page processing
        if ($replace === null && !isset($this->cache[$id])) {
            $this->cache[$id] = $ret;
        }

        return $ret;
    }

    /**
     * Gets the text of an id
     *
     * @param	string $s String to be localized
     * @return	string	Localized String
     * @author 	Lars Michelsen <lm@larsmichelsen.com>
     */
    private function getTextOfId($s)
    {
        return T_gettext($s);
    }

    /**
     * Gets the text of an id
     *
     * @param   string $sLang String Plain language string
     * @param   string $replace /Array  String or Array with macros to replace
     * @return  string        String Replaced language string
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    public static function getReplacedString($sLang, $replace)
    {
        if (!is_array($replace)) {
            $aReplace = explode(',', $replace);
            for ($i = 0, $size = count($aReplace); $i < $size; $i++) {
                if (isset($aReplace[$i])) {
                    $var = explode('~', $aReplace[$i]);
                    $sLang = str_replace('[' . $var[0] . ']', $var[1], $sLang);
                }
            }
        } else {
            foreach ($replace as $key => $val) {
                $sLang = str_replace('[' . $key . ']', $val, $sLang);
            }
        }

        // Return string with replaced text
        return $sLang;
    }
}
