Permalink
Fetching contributors…
Cannot retrieve contributors at this time
445 lines (383 sloc) 12.9 KB
<?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(!$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(!$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));
}
}