<?php

/**
 * A minimalistic XHTML PHP based template system implemented for SimpleSAMLphp.
 *
 * @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
 * @package SimpleSAMLphp
 */
class SimpleSAML_XHTML_Template
{

    /**
     * The data associated with this template, accessible within the template itself.
     *
     * @var array
     */
    public $data = array();

    /**
     * A translator instance configured to work with this template.
     *
     * @var \SimpleSAML\Locale\Translate
     */
    private $translator;

    /**
     * The configuration to use in this template.
     *
     * @var SimpleSAML_Configuration
     */
    private $configuration;

    /**
     * The file to load in this template.
     *
     * @var string
     */
    private $template = 'default.php';


    /**
     * Constructor
     *
     * @param SimpleSAML_Configuration $configuration Configuration object
     * @param string                   $template Which template file to load
     * @param string|null              $defaultDictionary The default dictionary where tags will come from.
     */
    public function __construct(\SimpleSAML_Configuration $configuration, $template, $defaultDictionary = null)
    {
        $this->configuration = $configuration;
        $this->template = $template;
        $this->data['baseurlpath'] = $this->configuration->getBaseURL();
        $this->translator = new \SimpleSAML\Locale\Translate($configuration, $defaultDictionary);
        $this->useTwig =  $this->setupTwig();
    }

    /*
     * Normalize template-name
     * *param $templateName         Template
     */
    private function normalizeTemplateName($templateName)
    {
        if (strripos($templateName, '.twig.html')) { return $templateName; }
        $phppos = strripos($templateName, '.php');
        if ($phppos) {
            $templateName = substr($templateName, 0, $phppos);
        }
        $tplpos = strripos($templateName, '.tpl');
        if ($tplpos) {
            $templateName = substr($templateName, 0, $tplpos);
        }
        return $templateName.'.twig.html';
    }

    private function setupTwigTemplatepaths()
    {
        $filename = $this->normalizeTemplateName($this->template);
        // get namespace if any
        $namespace = '';
        $split = explode(':', $filename, 2);
        if (count($split)===2) {
            $namespace = $split[0];
            $filename = $split[1];
        }
        $this->twig_template = $namespace ? '@'.$namespace.'/'.$filename : $filename;
        $loader = new \Twig_Loader_Filesystem($this->configuration->resolvePath('templates'));
        foreach ($this->findModuleTemplateDirs() as $module => $templateDir) {
            $loader->prependPath($templateDir, $module);
        }
        if (!$loader->exists($this->twig_template)) { return false; }
        return $loader;
    }

    /**
     * Setup twig
     */
    private function setupTwig()
    {
        $cache = $this->configuration->getString('template.cache', $this->configuration->resolvePath('cache'));
        // check if template exists
        $loader = $this->setupTwigTemplatepaths();
        if (!$loader) { return false; }

        $auto_reload = $this->configuration->getBoolean('template.auto_reload', false);
        $this->twig = new \Twig_Environment($loader, array('cache' => $cache, 'auto_reload' => $auto_reload));
        return true;
    }

    private function findModuleTemplateDirs()
    {
        $all_modules = \SimpleSAML\Module::getModules();
        $modules = array();
        foreach ($all_modules as $module) {
            if (!\SimpleSAML\Module::isModuleEnabled($module)) { continue; }
            $moduledir = \SimpleSAML\Module::getModuleDir($module);
            // check if module has a /templates dir, if so, append
            $templatedir = $moduledir.'/templates';
            if (is_dir($templatedir)) {
                $modules[$module] = $templatedir;
            }
        }
        return $modules;
    }

    /**
     * Return the internal translator object used by this template.
     *
     * @return \SimpleSAML\Locale\Translate The translator that will be used with this template.
     */
    public function getTranslator()
    {
        return $this->translator;
    }

    /**
     * Generate languagebar
     */
    private function generateLanguageBar()
    {
        $languages = $this->translator->getLanguage()->getLanguageList();
        $langmap = NULL;
        if ( count($languages) > 1 ) {
            // TODO: this array should not be defined here
            $langnames = array(
                'no' => 'Bokmål', // Norwegian Bokmål
                'nn' => 'Nynorsk', // Norwegian Nynorsk
                'se' => 'Sámegiella', // Northern Sami
                'sam' => 'Åarjelh-saemien giele', // Southern Sami
                'da' => 'Dansk', // Danish
                'en' => 'English',
                'de' => 'Deutsch', // German
                'sv' => 'Svenska', // Swedish
                'fi' => 'Suomeksi', // Finnish
                'es' => 'Español', // Spanish
                'fr' => 'Français', // French
                'it' => 'Italiano', // Italian
                'nl' => 'Nederlands', // Dutch
                'lb' => 'Lëtzebuergesch', // Luxembourgish
                'cs' => 'Čeština', // Czech
                'sl' => 'Slovenščina', // Slovensk
                'lt' => 'Lietuvių kalba', // Lithuanian
                'hr' => 'Hrvatski', // Croatian
                'hu' => 'Magyar', // Hungarian
                'pl' => 'Język polski', // Polish
                'pt' => 'Português', // Portuguese
                'pt-br' => 'Português brasileiro', // Portuguese
                'ru' => 'русский язык', // Russian
                'et' => 'eesti keel', // Estonian
                'tr' => 'Türkçe', // Turkish
                'el' => 'ελληνικά', // Greek
                'ja' => '日本語', // Japanese
                'zh' => '简体中文', // Chinese (simplified)
                'zh-tw' => '繁體中文', // Chinese (traditional)
                'ar' => 'العربية', // Arabic
                'fa' => 'پارسی', // Persian
                'ur' => 'اردو', // Urdu
                'he' => 'עִבְרִית', // Hebrew
                'id' => 'Bahasa Indonesia', // Indonesian
                'sr' => 'Srpski', // Serbian
                'lv' => 'Latviešu', // Latvian
                'ro' => 'Românește', // Romanian
                'eu' => 'Euskara', // Basque
            );
            $parameterName = $this->getTranslator()->getLanguage()->getLanguageParameterName();
            $langmap = array();
            foreach ($languages as $lang => $current) {
                $lang = strtolower($lang);
                $langname = $langnames[$lang];
                $url = false;
                if (!$current) {
                    $url = htmlspecialchars(\SimpleSAML\Utils\HTTP::addURLParameters('', array($parameterName => $lang)));
                }
                $langmap[$langname] = $url;
            }
        }
        return $langmap;
    }

    /**
     * Set some default context
     */
    private function twigDefaultContext()
    {
        $this->data['currentLanguage'] = $this->translator->getLanguage()->getLanguage();
        // show language bar by default
        if (!isset($this->data['hideLanguageBar'])) {
            $this->data['hideLanguageBar'] = false;
        }
        // get languagebar
        $this->data['languageBar'] = NULL;
        if ($this->data['hideLanguageBar'] === false) {
            $languageBar = $this->generateLanguageBar();
            if (is_null($languageBar)) {
                $this->data['hideLanguageBar'] = true;
            } else {
                $this->data['languageBar'] = $languageBar;
            }
        }

        // assure that there is a <title> and <h1>
        if (isset($this->data['header']) && !isset($this->data['pagetitle'])) {
            $this->data['pagetitle'] = $this->data['header'];
        }
        if (!isset($this->data['pagetitle'])) {
            $this->data['pagetitle'] = 'SimpleSAMLphp';
        }

        // set RTL
        $this->data['isRTL'] = false;
        if ($this->translator->getLanguage()->isLanguageRTL())
        {
            $this->data['isRTL'] = true;
        }
    }


    /**
     * Show the template to the user.
     */
    public function show()
    {
        if ($this->useTwig) {
            $this->twigDefaultContext();
            echo $this->twig->render($this->twig_template, $this->data);
        }
        else
        {
            $filename = $this->findTemplatePath($this->template);
            require($filename);
        }
    }


    /**
     * Find template path.
     *
     * This function locates the given template based on the template name. It will first search for the template in
     * the current theme directory, and then the default theme.
     *
     * The template name may be on the form <module name>:<template path>, in which case it will search for the
     * template file in the given module.
     *
     * @param string $template The relative path from the theme directory to the template file.
     *
     * @return string The absolute path to the template file.
     *
     * @throws Exception If the template file couldn't be found.
     */
    private function findTemplatePath($template, $throw_exception=true)
    {
        assert('is_string($template)');

        $tmp = explode(':', $template, 2);
        if (count($tmp) === 2) {
            $templateModule = $tmp[0];
            $templateName = $tmp[1];
        } else {
            $templateModule = 'default';
            $templateName = $tmp[0];
        }

        $tmp = explode(':', $this->configuration->getString('theme.use', 'default'), 2);
        if (count($tmp) === 2) {
            $themeModule = $tmp[0];
            $themeName = $tmp[1];
        } else {
            $themeModule = null;
            $themeName = $tmp[0];
        }

        // first check the current theme
        if ($themeModule !== null) {
            // .../module/<themeModule>/themes/<themeName>/<templateModule>/<templateName>

            $filename = \SimpleSAML\Module::getModuleDir($themeModule).
                '/themes/'.$themeName.'/'.$templateModule.'/'.$templateName;
        } elseif ($templateModule !== 'default') {
            // .../module/<templateModule>/templates/<templateName>
            $filename = \SimpleSAML\Module::getModuleDir($templateModule).'/templates/'.$templateName;
        } else {
            // .../templates/<theme>/<templateName>
            $filename = $this->configuration->getPathValue('templatedir', 'templates/').$templateName;
        }

        if (file_exists($filename)) {
            return $filename;
        }

        // not found in current theme
        \SimpleSAML_Logger::debug(
            $_SERVER['PHP_SELF'].' - Template: Could not find template file ['. $template.'] at ['.
            $filename.'] - now trying the base template'
        );

        // try default theme
        if ($templateModule !== 'default') {
            // .../module/<templateModule>/templates/<templateName>
            $filename = \SimpleSAML\Module::getModuleDir($templateModule).'/templates/'.$templateName;
        } else {
            // .../templates/<templateName>
            $filename = $this->configuration->getPathValue('templatedir', 'templates/').'/'.$templateName;
        }

        if (file_exists($filename)) {
            return $filename;
        }

        // not found in default template
        if ($throw_exception) {
            // log error and throw exception
            $error = 'Template: Could not find template file ['.$template.'] at ['.$filename.']';
            \SimpleSAML_Logger::critical($_SERVER['PHP_SELF'].' - '.$error);

            throw new Exception($error);
        }
        else
        {
            // missing template expected, return NULL
            return NULL;
        }
    }


    /*
     * Deprecated methods of this interface, all of them should go away.
     */


    /**
     * @param $name
     *
     * @return string
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguage()
     * instead.
     */
    public function getAttributeTranslation($name)
    {
        return $this->translator->getAttributeTranslation($name);
    }


    /**
     * @return string
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguage()
     * instead.
     */
    public function getLanguage()
    {
        return $this->translator->getLanguage()->getLanguage();
    }


    /**
     * @param      $language
     * @param bool $setLanguageCookie
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::setLanguage()
     * instead.
     */
    public function setLanguage($language, $setLanguageCookie = true)
    {
        $this->translator->getLanguage()->setLanguage($language, $setLanguageCookie);
    }


    /**
     * @return null|string
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::getLanguageCookie()
     * instead.
     */
    public static function getLanguageCookie()
    {
        return \SimpleSAML\Locale\Language::getLanguageCookie();
    }


    /**
     * @param $language
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Language::setLanguageCookie()
     * instead.
     */
    public static function setLanguageCookie($language)
    {
        \SimpleSAML\Locale\Language::setLanguageCookie($language);
    }

    /**
     * Wraps Language->getLanguageList
     */
    private function getLanguageList() {
        return $this->translator->getLanguage()->getLanguageList();
    }


    /**
     * @param $tag
     *
     * @return array
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::getTag() instead.
     */
    public function getTag($tag)
    {
        return $this->translator->getTag($tag);
    }


    /**
     * Temporary wrapper for \SimpleSAML\Locale\Translate::getPreferredTranslation().
     *
     * @deprecated This method will be removed in SSP 2.0. Please use
     * \SimpleSAML\Locale\Translate::getPreferredTranslation() instead.
     */
    public function getTranslation($translations)
    {
        return $this->translator->getPreferredTranslation($translations);
    }


    /**
     * Includes a file relative to the template base directory.
     * This function can be used to include headers and footers etc.
     *
     */
    private function includeAtTemplateBase($file) {
        $data = $this->data;

        $filename = $this->findTemplatePath($file);

        include($filename);
    }


    /**
     * Wraps Translate->includeInlineTranslation()
     *
     * @see \SimpleSAML\Locale\Translate::includeInlineTranslation()
     * @deprecated This method will be removed in SSP 2.0. Please use
     * \SimpleSAML\Locale\Translate::includeInlineTranslation() instead.
     */
    public function includeInlineTranslation($tag, $translation)
    {
        $this->translator->includeInlineTranslation($tag, $translation);
    }


    /**
     * @param      $file
     * @param null $otherConfig
     * @deprecated This method will be removed in SSP 2.0. Please use
     * \SimpleSAML\Locale\Translate::includeLanguageFile() instead.
     */
    public function includeLanguageFile($file, $otherConfig = null)
    {
        $this->translator->includeLanguageFile($file, $otherConfig);
    }


    /**
     * Wrap Language->isLanguageRTL
     */
    private function isLanguageRTL() {
        return $this->translator->getLanguage()->isLanguageRTL();
    }


    /**
     * Merge two translation arrays.
     *
     * @param array $def The array holding string definitions.
     * @param array $lang The array holding translations for every string.
     * @return array The recursive merge of both arrays.
     * @deprecated This method will be removed in SimpleSAMLphp 2.0. Please use array_merge_recursive() instead.
     */
    public static function lang_merge($def, $lang)
    {
        foreach ($def as $key => $value) {
            if (array_key_exists($key, $lang)) {
                $def[$key] = array_merge($value, $lang[$key]);
            }
        }
        return $def;
    }


    /**
     * Wrap Language->t to translate tag into the current language, with a fallback to english.
     *
     * @see \SimpleSAML\Locale\Translate::t()
     * @deprecated This method will be removed in SSP 2.0. Please use \SimpleSAML\Locale\Translate::t() instead.
     */
    public function t(
        $tag,
        $replacements = array(),
        $fallbackdefault = true,
        $oldreplacements = array(),
        $striptags = false
    ) {
        return $this->translator->t($tag, $replacements, $fallbackdefault, $oldreplacements, $striptags);
    }
}