<?php
/**
 * Translation Plugin: Simple multilanguage plugin
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Andreas Gohr <andi@splitbrain.org>
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

/**
 * Class helper_plugin_translation
 */
class helper_plugin_translation extends DokuWiki_Plugin {
    var $translations = array();
    var $translationNs = '';
    var $defaultlang = '';
    var $LN = array(); // hold native names
    var $opts = array(); // display options

    /**
     * Initialize
     */
    function __construct() {
        global $conf;
        require_once(DOKU_INC . 'inc/pageutils.php');
        require_once(DOKU_INC . 'inc/utf8.php');

        $this->loadTranslationNamespaces();

        // load language names
        $this->LN = confToHash(dirname(__FILE__) . '/lang/langnames.txt');

        // display options
        $this->opts = $this->getConf('display');
        $this->opts = explode(',', $this->opts);
        $this->opts = array_map('trim', $this->opts);
        $this->opts = array_fill_keys($this->opts, true);

        // get default translation
        if(empty($conf['lang_before_translation'])) {
            $dfl = $conf['lang'];
        } else {
            $dfl = $conf['lang_before_translation'];
        }
        if(in_array($dfl, $this->translations)) {
            $this->defaultlang = $dfl;
        } else {
            $this->defaultlang = '';
            array_unshift($this->translations, '');
        }

        $this->translationNs = cleanID($this->getConf('translationns'));
        if($this->translationNs) $this->translationNs .= ':';
    }

    /**
     * Parse 'translations'-setting into $this->translations
     */
    public function loadTranslationNamespaces() {
        // load wanted translation into array
        $this->translations = strtolower(str_replace(',', ' ', $this->getConf('translations')));
        $this->translations = array_unique(array_filter(explode(' ', $this->translations)));
        sort($this->translations);
    }

    /**
     * Check if the given ID is a translation and return the language code.
     *
     * @param string $id
     * @return string
     */
    function getLangPart($id) {
        list($lng) = $this->getTransParts($id);
        return $lng;
    }

    /**
     * Check if the given ID is a translation and return the language code and
     * the id part.
     *
     * @param string $id
     * @return array
     */
    function getTransParts($id) {
        $rx = '/^' . $this->translationNs . '(' . join('|', $this->translations) . '):(.*)/';
        if(preg_match($rx, $id, $match)) {
            return array($match[1], $match[2]);
        }
        return array('', $id);
    }

    /**
     * Returns the browser language if it matches with one of the configured
     * languages
     */
    function getBrowserLang() {
        global $conf;
        $langs = $this->translations;
        if (!in_array($conf['lang'], $langs)) {
            $langs[] = $conf['lang'];
        }
        $rx = '/(^|,|:|;|-)(' . join('|', $langs) . ')($|,|:|;|-)/i';
        if(preg_match($rx, $_SERVER['HTTP_ACCEPT_LANGUAGE'], $match)) {
            return strtolower($match[2]);
        }
        return false;
    }

    /**
     * Returns the ID and name to the wanted translation, empty
     * $lng is default lang
     *
     * @param string $lng
     * @param string $idpart
     * @return array
     */
    function buildTransID($lng, $idpart) {
        if($lng && in_array($lng, $this->translations)) {
            $link = ':' . $this->translationNs . $lng . ':' . $idpart;
            $name = $lng;
        } else {
            $link = ':' . $this->translationNs . $idpart;
            $name = $this->realLC('');
        }
        return array($link, $name);
    }

    /**
     * Returns the real language code, even when an empty one is given
     * (eg. resolves th default language)
     *
     * @param string $lc
     * @return string
     */
    function realLC($lc) {
        global $conf;
        if ($lc) {
            return $lc;
        } elseif (empty($conf['lang_before_translation'])) {
            return $conf['lang'];
        } else {
            return $conf['lang_before_translation'];
        }
    }

    /**
     * Check if current ID should be translated and any GUI
     * should be shown
     *
     * @param string $id
     * @param bool   $checkact
     * @return bool
     */
    function istranslatable($id, $checkact = true) {
        global $ACT;

        if($checkact && $ACT != 'show') return false;
        if($this->translationNs && strpos($id, $this->translationNs) !== 0) return false;
        $skiptrans = trim($this->getConf('skiptrans'));
        if($skiptrans && preg_match('/' . $skiptrans . '/ui', ':' . $id)) return false;
        $meta = p_get_metadata($id);
        if(!empty($meta['plugin']['translation']['notrans'])) return false;

        return true;
    }

    /**
     * Return the (localized) about link
     */
    function showAbout() {
        global $ID;

        $curlc = $this->getLangPart($ID);

        $about = $this->getConf('about');
        if($this->getConf('localabout')) {
            list(/* $lc */, $idpart) = $this->getTransParts($about);
            list($about, /* $name */) = $this->buildTransID($curlc, $idpart);
            $about = cleanID($about);
        }

        $out = '';
        $out .= '<sup>';
        $out .= html_wikilink($about, '?');
        $out .= '</sup>';

        return $out;
    }

    /**
     * Returns a list of (lc => link) for all existing translations of a page
     *
     * @param $id
     * @return array
     */
    function getAvailableTranslations($id) {
        $result = array();

        list($lc, $idpart) = $this->getTransParts($id);

        foreach($this->translations as $t) {
            if($t == $lc) continue; //skip self
            list($link, $name) = $this->buildTransID($t, $idpart);
            if(page_exists($link)) {
                $result[$name] = $link;
            }
        }

        return $result;
    }

    /**
     * Creates an UI for linking to the available and configured translations
     *
     * Can be called from the template or via the ~~TRANS~~ syntax component.
     */
    public function showTranslations() {
        global $conf;
        global $INFO;

        if(!$this->istranslatable($INFO['id'])) return '';
        $this->checkage();

        list($lc, $idpart) = $this->getTransParts($INFO['id']);
        $lang = $this->realLC($lc);

        $out = '<div class="plugin_translation">';

        //show title and about
        if(isset($this->opts['title'])) {
            $out .= '<span>' . $this->getLang('translations');
            if($this->getConf('about')) $out .= $this->showAbout();
            $out .= ':</span> ';
            if(isset($this->opts['twolines'])) $out .= '<br />';
        }

        // open wrapper
        if($this->getConf('dropdown')) {
            // select needs its own styling
            if($INFO['exists']) {
                $class = 'wikilink1';
            } else {
                $class = 'wikilink2';
            }
            if(isset($this->opts['flag'])) {
                $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
            }else{
                $flag = '';
            }

            if($conf['userewrite']) {
                $action = wl();
            } else {
                $action = script();
            }

            $out .= '<form action="' . $action . '" id="translation__dropdown">';
            if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" class="' . $class . '" /> ';
            $out .= '<select name="id" class="' . $class . '">';
        } else {
            $out .= '<ul>';
        }

        // insert items
        foreach($this->translations as $t) {
            $out .= $this->getTransItem($t, $idpart);
        }

        // close wrapper
        if($this->getConf('dropdown')) {
            $out .= '</select>';
            $out .= '<input name="go" type="submit" value="&rarr;" />';
            $out .= '</form>';
        } else {
            $out .= '</ul>';
        }

        // show about if not already shown
        if(!isset($this->opts['title']) && $this->getConf('about')) {
            $out .= '&nbsp';
            $out .= $this->showAbout();
        }

        $out .= '</div>';

        return $out;
    }

    /**
     * Return the local name
     *
     * @param $lang
     * @return string
     */
    function getLocalName($lang) {
        if($this->LN[$lang]) {
            return $this->LN[$lang];
        }
        return $lang;
    }

    /**
     * Create the link or option for a single translation
     *
     * @param $lc string      The language code
     * @param $idpart string  The ID of the translated page
     * @returns string        The item
     */
    function getTransItem($lc, $idpart) {
        global $ID;
        global $conf;

        list($link, $lang) = $this->buildTransID($lc, $idpart);
        $link = cleanID($link);

        // class
        if(page_exists($link, '', false)) {
            $class = 'wikilink1';
        } else {
            $class = 'wikilink2';
        }

        // local language name
        $localname = $this->getLocalName($lang);

        $divClass = 'li';
        // current?
        if($ID == $link) {
            $sel = ' selected="selected"';
            $class .= ' cur';
            $divClass .= ' cur';
        } else {
            $sel = '';
        }

        // flag
        $flag = false;
        $style = '';
        if(isset($this->opts['flag'])) {
            $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
            $style = ' style="background-image: url(\'' . $flag . '\')"';
            $class .= ' flag';
        }

        // what to display as name
        if(isset($this->opts['name'])) {
            $display = hsc($localname);
            if(isset($this->opts['langcode'])) $display .= ' (' . hsc($lang) . ')';
        } elseif(isset($this->opts['langcode'])) {
            $display = hsc($lang);
        } else {
            $display = '&nbsp;';
        }

        // prepare output
        $out = '';
        if($this->getConf('dropdown')) {
            if($conf['useslash']) $link = str_replace(':', '/', $link);

            $out .= '<option class="' . $class . '" title="' . hsc($localname) . '" value="' . $link . '"' . $sel . $style . '>';
            $out .= $display;
            $out .= '</option>';
        } else {
            $out .= "<li><div class='$divClass'>";
            $out .= '<a href="' . wl($link) . '" class="' . $class . '" title="' . hsc($localname) . '">';
            if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" />';
            $out .= $display;
            $out .= '</a>';
            $out .= '</div></li>';
        }

        return $out;
    }

    /**
     * Checks if the current page is a translation of a page
     * in the default language. Displays a notice when it is
     * older than the original page. Tries to link to a diff
     * with changes on the original since the translation
     */
    function checkage() {
        global $ID;
        global $INFO;
        if(!$this->getConf('checkage')) return;
        if(!$INFO['exists']) return;
        $lng = $this->getLangPart($ID);
        if($lng == $this->defaultlang) return;

        $rx = '/^' . $this->translationNs . '((' . join('|', $this->translations) . '):)?/';
        $idpart = preg_replace($rx, '', $ID);

        // compare modification times
        list($orig, /* $name */) = $this->buildTransID($this->defaultlang, $idpart);
        $origfn = wikiFN($orig);
        if($INFO['lastmod'] >= @filemtime($origfn)) return;

        // get revision from before translation
        $orev = 0;

        $changelog = new PageChangelog($orig);
        $revs = $changelog->getRevisions(0, 100);
        foreach($revs as $rev) {
            if($rev < $INFO['lastmod']) {
                $orev = $rev;
                break;
            }
        }

        // see if the found revision still exists
        if($orev && !page_exists($orig, $orev)) $orev = 0;

        // build the message and display it
        $orig = cleanID($orig);
        $msg = sprintf($this->getLang('outdated'), wl($orig));

        $difflink = $this->getOldDiffLink($orig, $INFO['lastmod']);
        if ($difflink) {
            $msg .= sprintf(' ' . $this->getLang('diff'), $difflink);
        }

        echo '<div class="notify">' . $msg . '</div>';
    }

    function getOldDiffLink($id, $lastmod) {
        // get revision from before translation
        $orev = false;
        $changelog = new PageChangelog($id);
        $revs = $changelog->getRevisions(0, 100);
        foreach($revs as $rev) {
            if($rev < $lastmod) {
                $orev = $rev;
                break;
            }
        }
        if($orev && !page_exists($id, $orev)) {
            return false;
        }
        $id = cleanID($id);
        return wl($id, array('do' => 'diff', 'rev' => $orev));

    }
}
