1af1904f9SAndreas Gohr<?php 2af1904f9SAndreas Gohr/** 3af1904f9SAndreas Gohr * Translation Plugin: Simple multilanguage plugin 4af1904f9SAndreas Gohr * 5af1904f9SAndreas Gohr * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6af1904f9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 7af1904f9SAndreas Gohr */ 8af1904f9SAndreas Gohr 9af1904f9SAndreas Gohr// must be run within Dokuwiki 10af1904f9SAndreas Gohrif(!defined('DOKU_INC')) die(); 11af1904f9SAndreas Gohr 12af1904f9SAndreas Gohrclass helper_plugin_translation extends DokuWiki_Plugin { 135e1f71acSGuillaume Turri var $translations = array(); 145e1f71acSGuillaume Turri var $translationNs = ''; 157c54a0a6SAndreas Gohr var $defaultlang = ''; 1649a71a89SAndreas Gohr var $LN = array(); // hold native names 1704971eeaSAndreas Gohr var $opts = array(); // display options 18af1904f9SAndreas Gohr 19af1904f9SAndreas Gohr /** 20af1904f9SAndreas Gohr * Initialize 21af1904f9SAndreas Gohr */ 22*488e4722SAndreas Gohr function __construct() { 237c54a0a6SAndreas Gohr global $conf; 24af1904f9SAndreas Gohr require_once(DOKU_INC . 'inc/pageutils.php'); 25af1904f9SAndreas Gohr require_once(DOKU_INC . 'inc/utf8.php'); 26af1904f9SAndreas Gohr 27af1904f9SAndreas Gohr // load wanted translation into array 285e1f71acSGuillaume Turri $this->translations = strtolower(str_replace(',', ' ', $this->getConf('translations'))); 295e1f71acSGuillaume Turri $this->translations = array_unique(array_filter(explode(' ', $this->translations))); 305e1f71acSGuillaume Turri sort($this->translations); 317c54a0a6SAndreas Gohr 3249a71a89SAndreas Gohr // load language names 3349a71a89SAndreas Gohr $this->LN = confToHash(dirname(__FILE__) . '/lang/langnames.txt'); 3449a71a89SAndreas Gohr 3504971eeaSAndreas Gohr // display options 3604971eeaSAndreas Gohr $this->opts = $this->getConf('display'); 3704971eeaSAndreas Gohr $this->opts = explode(',', $this->opts); 3804971eeaSAndreas Gohr $this->opts = array_map('trim', $this->opts); 3904971eeaSAndreas Gohr $this->opts = array_fill_keys($this->opts, true); 4004971eeaSAndreas Gohr 417c54a0a6SAndreas Gohr // get default translation 427c54a0a6SAndreas Gohr if(!$conf['lang_before_translation']) { 437c54a0a6SAndreas Gohr $dfl = $conf['lang']; 447c54a0a6SAndreas Gohr } else { 457c54a0a6SAndreas Gohr $dfl = $conf['lang_before_translation']; 467c54a0a6SAndreas Gohr } 475e1f71acSGuillaume Turri if(in_array($dfl, $this->translations)) { 487c54a0a6SAndreas Gohr $this->defaultlang = $dfl; 497c54a0a6SAndreas Gohr } else { 507c54a0a6SAndreas Gohr $this->defaultlang = ''; 515e1f71acSGuillaume Turri array_unshift($this->translations, ''); 527c54a0a6SAndreas Gohr } 537c54a0a6SAndreas Gohr 545e1f71acSGuillaume Turri $this->translationNs = cleanID($this->getConf('translationns')); 555e1f71acSGuillaume Turri if($this->translationNs) $this->translationNs .= ':'; 56af1904f9SAndreas Gohr } 57af1904f9SAndreas Gohr 58af1904f9SAndreas Gohr /** 59af1904f9SAndreas Gohr * Check if the given ID is a translation and return the language code. 60af1904f9SAndreas Gohr */ 61af1904f9SAndreas Gohr function getLangPart($id) { 6226522e09SAndreas Gohr list($lng) = $this->getTransParts($id); 6326522e09SAndreas Gohr return $lng; 64af1904f9SAndreas Gohr } 6526522e09SAndreas Gohr 6626522e09SAndreas Gohr /** 6726522e09SAndreas Gohr * Check if the given ID is a translation and return the language code and 6826522e09SAndreas Gohr * the id part. 6926522e09SAndreas Gohr */ 7026522e09SAndreas Gohr function getTransParts($id) { 715e1f71acSGuillaume Turri $rx = '/^' . $this->translationNs . '(' . join('|', $this->translations) . '):(.*)/'; 7226522e09SAndreas Gohr if(preg_match($rx, $id, $match)) { 7326522e09SAndreas Gohr return array($match[1], $match[2]); 7426522e09SAndreas Gohr } 7526522e09SAndreas Gohr return array('', $id); 76af1904f9SAndreas Gohr } 77af1904f9SAndreas Gohr 78af1904f9SAndreas Gohr /** 797053cd66SAndreas Gohr * Returns the browser language if it matches with one of the configured 807053cd66SAndreas Gohr * languages 817053cd66SAndreas Gohr */ 827053cd66SAndreas Gohr function getBrowserLang() { 835e1f71acSGuillaume Turri $rx = '/(^|,|:|;|-)(' . join('|', $this->translations) . ')($|,|:|;|-)/i'; 847053cd66SAndreas Gohr if(preg_match($rx, $_SERVER['HTTP_ACCEPT_LANGUAGE'], $match)) { 857053cd66SAndreas Gohr return strtolower($match[2]); 867053cd66SAndreas Gohr } 877053cd66SAndreas Gohr return false; 887053cd66SAndreas Gohr } 897053cd66SAndreas Gohr 907053cd66SAndreas Gohr /** 917c54a0a6SAndreas Gohr * Returns the ID and name to the wanted translation, empty 927c54a0a6SAndreas Gohr * $lng is default lang 93af1904f9SAndreas Gohr */ 94af1904f9SAndreas Gohr function buildTransID($lng, $idpart) { 95af1904f9SAndreas Gohr global $conf; 96af1904f9SAndreas Gohr if($lng) { 975e1f71acSGuillaume Turri $link = ':' . $this->translationNs . $lng . ':' . $idpart; 98af1904f9SAndreas Gohr $name = $lng; 99af1904f9SAndreas Gohr } else { 1005e1f71acSGuillaume Turri $link = ':' . $this->translationNs . $idpart; 10104971eeaSAndreas Gohr $name = $this->realLC(''); 102af1904f9SAndreas Gohr } 103af1904f9SAndreas Gohr return array($link, $name); 104af1904f9SAndreas Gohr } 105af1904f9SAndreas Gohr 1061469199dSAndreas Gohr /** 10704971eeaSAndreas Gohr * Returns the real language code, even when an empty one is given 10804971eeaSAndreas Gohr * (eg. resolves th default language) 10904971eeaSAndreas Gohr */ 11004971eeaSAndreas Gohr function realLC($lc) { 11104971eeaSAndreas Gohr global $conf; 11204971eeaSAndreas Gohr if($lc) { 11304971eeaSAndreas Gohr return $lc; 11404971eeaSAndreas Gohr } elseif(!$conf['lang_before_translation']) { 11504971eeaSAndreas Gohr return $conf['lang']; 11604971eeaSAndreas Gohr } else { 11704971eeaSAndreas Gohr return $conf['lang_before_translation']; 11804971eeaSAndreas Gohr } 11904971eeaSAndreas Gohr } 12004971eeaSAndreas Gohr 12104971eeaSAndreas Gohr /** 12284877e9bSAndreas Gohr * Check if current ID should be translated and any GUI 12384877e9bSAndreas Gohr * should be shown 12484877e9bSAndreas Gohr */ 12584877e9bSAndreas Gohr function istranslatable($id, $checkact = true) { 12684877e9bSAndreas Gohr global $ACT; 12784877e9bSAndreas Gohr 12884877e9bSAndreas Gohr if($checkact && $ACT != 'show') return false; 1295e1f71acSGuillaume Turri if($this->translationNs && strpos($id, $this->translationNs) !== 0) return false; 13084877e9bSAndreas Gohr $skiptrans = trim($this->getConf('skiptrans')); 13184877e9bSAndreas Gohr if($skiptrans && preg_match('/' . $skiptrans . '/ui', ':' . $id)) return false; 13284877e9bSAndreas Gohr $meta = p_get_metadata($id); 13384877e9bSAndreas Gohr if($meta['plugin']['translation']['notrans']) return false; 13484877e9bSAndreas Gohr 13584877e9bSAndreas Gohr return true; 13684877e9bSAndreas Gohr } 13784877e9bSAndreas Gohr 13801dd7da9SAndreas Gohr /** 13901dd7da9SAndreas Gohr * Return the (localized) about link 14001dd7da9SAndreas Gohr */ 14101dd7da9SAndreas Gohr function showAbout() { 142c9640767STomasz Tomasik global $ID; 143c9640767STomasz Tomasik global $conf; 144c9640767STomasz Tomasik global $INFO; 145c9640767STomasz Tomasik 1465ad1c278SAndreas Gohr $curlc = $this->getLangPart($ID); 147f34c9eb2SAndreas Gohr 148d0bdb959SAndreas Gohr $about = $this->getConf('about'); 149d0bdb959SAndreas Gohr if($this->getConf('localabout')) { 150d0bdb959SAndreas Gohr list($lc, $idpart) = $this->getTransParts($about); 151f34c9eb2SAndreas Gohr list($about, $name) = $this->buildTransID($curlc, $idpart); 152d0bdb959SAndreas Gohr $about = cleanID($about); 153d0bdb959SAndreas Gohr } 154c9640767STomasz Tomasik 155c9640767STomasz Tomasik $out = ''; 156c9640767STomasz Tomasik $out .= '<sup>'; 157d0bdb959SAndreas Gohr $out .= html_wikilink($about, '?'); 158c9640767STomasz Tomasik $out .= '</sup>'; 159c9640767STomasz Tomasik 160c9640767STomasz Tomasik return $out; 161c9640767STomasz Tomasik } 162c9640767STomasz Tomasik 16384877e9bSAndreas Gohr /** 164bbe70520SAndreas Gohr * Returns a list of (lc => link) for all existing translations of a page 165bbe70520SAndreas Gohr * 166bbe70520SAndreas Gohr * @param $id 167bbe70520SAndreas Gohr * @return array 168bbe70520SAndreas Gohr */ 169bbe70520SAndreas Gohr function getAvailableTranslations($id) { 170bbe70520SAndreas Gohr $result = array(); 171bbe70520SAndreas Gohr 172bbe70520SAndreas Gohr list($lc, $idpart) = $this->getTransParts($id); 173bbe70520SAndreas Gohr $lang = $this->realLC($lc); 174bbe70520SAndreas Gohr 1755e1f71acSGuillaume Turri foreach($this->translations as $t) { 176bbe70520SAndreas Gohr if($t == $lc) continue; //skip self 177bbe70520SAndreas Gohr list($link, $name) = $this->buildTransID($t, $idpart); 178bbe70520SAndreas Gohr if(page_exists($link)) { 179bbe70520SAndreas Gohr $result[$name] = $link; 180bbe70520SAndreas Gohr } 181bbe70520SAndreas Gohr } 182bbe70520SAndreas Gohr 183bbe70520SAndreas Gohr return $result; 184bbe70520SAndreas Gohr } 185bbe70520SAndreas Gohr 186bbe70520SAndreas Gohr /** 187649de279SAndreas Gohr * Creates an UI for linking to the available and configured translations 188649de279SAndreas Gohr * 189649de279SAndreas Gohr * Can be called from the template or via the ~~TRANS~~ syntax component. 1901469199dSAndreas Gohr */ 191649de279SAndreas Gohr public function showTranslations() { 1921469199dSAndreas Gohr global $conf; 1931469199dSAndreas Gohr global $INFO; 1941469199dSAndreas Gohr 195649de279SAndreas Gohr if(!$this->istranslatable($INFO['id'])) return ''; 19684877e9bSAndreas Gohr $this->checkage(); 1971469199dSAndreas Gohr 198649de279SAndreas Gohr list($lc, $idpart) = $this->getTransParts($INFO['id']); 19904971eeaSAndreas Gohr $lang = $this->realLC($lc); 20039ecab8bSAndreas Gohr 2011469199dSAndreas Gohr $out = '<div class="plugin_translation">'; 202c9640767STomasz Tomasik 20304971eeaSAndreas Gohr //show title and about 204c730e7ddSAndreas Gohr if(isset($this->opts['title'])) { 2051469199dSAndreas Gohr $out .= '<span>' . $this->getLang('translations'); 2068bd452a3SAndreas Gohr if($this->getConf('about')) $out .= $this->showAbout(); 2071469199dSAndreas Gohr $out .= ':</span> '; 208c730e7ddSAndreas Gohr if(isset($this->opts['twolines'])) $out .= '<br />'; 209c9640767STomasz Tomasik } 2101469199dSAndreas Gohr 21104971eeaSAndreas Gohr // open wrapper 21204971eeaSAndreas Gohr if($this->getConf('dropdown')) { 21304971eeaSAndreas Gohr // select needs its own styling 2141469199dSAndreas Gohr if($INFO['exists']) { 2151469199dSAndreas Gohr $class = 'wikilink1'; 2161469199dSAndreas Gohr } else { 2171469199dSAndreas Gohr $class = 'wikilink2'; 2181469199dSAndreas Gohr } 21904971eeaSAndreas Gohr if(isset($this->opts['flag'])) { 22004971eeaSAndreas Gohr $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif'; 221649de279SAndreas Gohr }else{ 222649de279SAndreas Gohr $flag = ''; 223c9640767STomasz Tomasik } 224649de279SAndreas Gohr 225fbc6382cSAndreas Gohr if($conf['userewrite']) { 226fbc6382cSAndreas Gohr $action = wl(); 227fbc6382cSAndreas Gohr } else { 228fbc6382cSAndreas Gohr $action = script(); 229fbc6382cSAndreas Gohr } 230fbc6382cSAndreas Gohr 231fbc6382cSAndreas Gohr $out .= '<form action="' . $action . '" id="translation__dropdown">'; 23204971eeaSAndreas Gohr if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" class="' . $class . '" /> '; 233c9640767STomasz Tomasik $out .= '<select name="id" class="' . $class . '">'; 23404971eeaSAndreas Gohr } else { 23504971eeaSAndreas Gohr $out .= '<ul>'; 23604971eeaSAndreas Gohr } 23704971eeaSAndreas Gohr 23804971eeaSAndreas Gohr // insert items 2395e1f71acSGuillaume Turri foreach($this->translations as $t) { 24004971eeaSAndreas Gohr $out .= $this->getTransItem($t, $idpart); 24104971eeaSAndreas Gohr } 24204971eeaSAndreas Gohr 24304971eeaSAndreas Gohr // close wrapper 24404971eeaSAndreas Gohr if($this->getConf('dropdown')) { 2451469199dSAndreas Gohr $out .= '</select>'; 2461469199dSAndreas Gohr $out .= '<input name="go" type="submit" value="→" />'; 2471469199dSAndreas Gohr $out .= '</form>'; 24804971eeaSAndreas Gohr } else { 2491469199dSAndreas Gohr $out .= '</ul>'; 2501469199dSAndreas Gohr } 2511469199dSAndreas Gohr 25204971eeaSAndreas Gohr // show about if not already shown 253c730e7ddSAndreas Gohr if(!isset($this->opts['title']) && $this->getConf('about')) { 25404971eeaSAndreas Gohr $out .= ' '; 25504971eeaSAndreas Gohr $out .= $this->showAbout(); 25604971eeaSAndreas Gohr } 25704971eeaSAndreas Gohr 2581469199dSAndreas Gohr $out .= '</div>'; 2591469199dSAndreas Gohr 2601469199dSAndreas Gohr return $out; 2611469199dSAndreas Gohr } 262af1904f9SAndreas Gohr 26301dd7da9SAndreas Gohr /** 264bbe70520SAndreas Gohr * Return the local name 265bbe70520SAndreas Gohr * 266bbe70520SAndreas Gohr * @param $lang 267bbe70520SAndreas Gohr * @return string 268bbe70520SAndreas Gohr */ 269bbe70520SAndreas Gohr function getLocalName($lang) { 270bbe70520SAndreas Gohr if($this->LN[$lang]) { 271bbe70520SAndreas Gohr return $this->LN[$lang]; 272bbe70520SAndreas Gohr } 273bbe70520SAndreas Gohr return $lang; 274bbe70520SAndreas Gohr } 275bbe70520SAndreas Gohr 276bbe70520SAndreas Gohr /** 27701dd7da9SAndreas Gohr * Create the link or option for a single translation 27801dd7da9SAndreas Gohr * 27904971eeaSAndreas Gohr * @param $lc string The language code 28001dd7da9SAndreas Gohr * @param $idpart string The ID of the translated page 28104971eeaSAndreas Gohr * @returns string The item 28201dd7da9SAndreas Gohr */ 28304971eeaSAndreas Gohr function getTransItem($lc, $idpart) { 284c9640767STomasz Tomasik global $ID; 285c9640767STomasz Tomasik global $conf; 286c9640767STomasz Tomasik 28704971eeaSAndreas Gohr list($link, $lang) = $this->buildTransID($lc, $idpart); 288c9640767STomasz Tomasik $link = cleanID($link); 28904971eeaSAndreas Gohr 29004971eeaSAndreas Gohr // class 291c9640767STomasz Tomasik if(page_exists($link, '', false)) { 292c9640767STomasz Tomasik $class = 'wikilink1'; 293c9640767STomasz Tomasik } else { 294c9640767STomasz Tomasik $class = 'wikilink2'; 295c9640767STomasz Tomasik } 296c9640767STomasz Tomasik 29704971eeaSAndreas Gohr // local language name 298bbe70520SAndreas Gohr $localname = $this->getLocalName($lang); 299c9640767STomasz Tomasik 30004971eeaSAndreas Gohr // current? 30104971eeaSAndreas Gohr if($ID == $link) { 30204971eeaSAndreas Gohr $sel = ' selected="selected"'; 30304971eeaSAndreas Gohr $class .= ' cur'; 30404971eeaSAndreas Gohr } else { 30504971eeaSAndreas Gohr $sel = ''; 30604971eeaSAndreas Gohr } 307c9640767STomasz Tomasik 30804971eeaSAndreas Gohr // flag 30904971eeaSAndreas Gohr if(isset($this->opts['flag'])) { 31004971eeaSAndreas Gohr $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif'; 31104971eeaSAndreas Gohr $style = ' style="background-image: url(\'' . $flag . '\')"'; 31204971eeaSAndreas Gohr $class .= ' flag'; 31304971eeaSAndreas Gohr } 31404971eeaSAndreas Gohr 31504971eeaSAndreas Gohr // what to display as name 31604971eeaSAndreas Gohr if(isset($this->opts['name'])) { 31704971eeaSAndreas Gohr $display = hsc($localname); 318c730e7ddSAndreas Gohr if(isset($this->opts['langcode'])) $display .= ' (' . hsc($lang) . ')'; 319c730e7ddSAndreas Gohr } elseif(isset($this->opts['langcode'])) { 32004971eeaSAndreas Gohr $display = hsc($lang); 32104971eeaSAndreas Gohr } else { 32204971eeaSAndreas Gohr $display = ' '; 32304971eeaSAndreas Gohr } 32404971eeaSAndreas Gohr 32504971eeaSAndreas Gohr // prepare output 32604971eeaSAndreas Gohr $out = ''; 32704971eeaSAndreas Gohr if($this->getConf('dropdown')) { 3282546b043SAndreas Gohr if($conf['useslash']) $link = str_replace(':', '/', $link); 3292546b043SAndreas Gohr 33004971eeaSAndreas Gohr $out .= '<option class="' . $class . '" title="' . hsc($localname) . '" value="' . $link . '"' . $sel . $style . '>'; 33104971eeaSAndreas Gohr $out .= $display; 33204971eeaSAndreas Gohr $out .= '</option>'; 33304971eeaSAndreas Gohr } else { 334c9640767STomasz Tomasik $out .= '<li><div class="li">'; 33537140606SAndreas Gohr $out .= '<a href="' . wl($link) . '" class="' . $class . '" title="' . hsc($localname) . '">'; 33604971eeaSAndreas Gohr if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" />'; 33704971eeaSAndreas Gohr $out .= $display; 338c9640767STomasz Tomasik $out .= '</a>'; 339c9640767STomasz Tomasik $out .= '</div></li>'; 340c9640767STomasz Tomasik } 341c9640767STomasz Tomasik 342c9640767STomasz Tomasik return $out; 343c9640767STomasz Tomasik } 344c9640767STomasz Tomasik 34584877e9bSAndreas Gohr /** 34684877e9bSAndreas Gohr * Checks if the current page is a translation of a page 34784877e9bSAndreas Gohr * in the default language. Displays a notice when it is 34884877e9bSAndreas Gohr * older than the original page. Tries to lin to a diff 34984877e9bSAndreas Gohr * with changes on the original since the translation 35084877e9bSAndreas Gohr */ 35184877e9bSAndreas Gohr function checkage() { 35284877e9bSAndreas Gohr global $ID; 35384877e9bSAndreas Gohr global $INFO; 35484877e9bSAndreas Gohr if(!$this->getConf('checkage')) return; 35584877e9bSAndreas Gohr if(!$INFO['exists']) return; 35684877e9bSAndreas Gohr $lng = $this->getLangPart($ID); 35784877e9bSAndreas Gohr if($lng == $this->defaultlang) return; 358af1904f9SAndreas Gohr 3595e1f71acSGuillaume Turri $rx = '/^' . $this->translationNs . '((' . join('|', $this->translations) . '):)?/'; 36084877e9bSAndreas Gohr $idpart = preg_replace($rx, '', $ID); 36184877e9bSAndreas Gohr 36284877e9bSAndreas Gohr // compare modification times 36384877e9bSAndreas Gohr list($orig, $name) = $this->buildTransID($this->defaultlang, $idpart); 36484877e9bSAndreas Gohr $origfn = wikiFN($orig); 36584877e9bSAndreas Gohr if($INFO['lastmod'] >= @filemtime($origfn)) return; 36684877e9bSAndreas Gohr 36784877e9bSAndreas Gohr // get revision from before translation 36884877e9bSAndreas Gohr $orev = 0; 36984877e9bSAndreas Gohr $revs = getRevisions($orig, 0, 100); 37084877e9bSAndreas Gohr foreach($revs as $rev) { 37184877e9bSAndreas Gohr if($rev < $INFO['lastmod']) { 37284877e9bSAndreas Gohr $orev = $rev; 37384877e9bSAndreas Gohr break; 37484877e9bSAndreas Gohr } 37584877e9bSAndreas Gohr } 37684877e9bSAndreas Gohr 37744552920SAndreas Gohr // see if the found revision still exists 37844552920SAndreas Gohr if($orev && !page_exists($orig, $orev)) $orev = 0; 37944552920SAndreas Gohr 38084877e9bSAndreas Gohr // build the message and display it 381dc3fbdb9SOleksiy Zagorskyi $orig = cleanID($orig); 38284877e9bSAndreas Gohr $msg = sprintf($this->getLang('outdated'), wl($orig)); 38384877e9bSAndreas Gohr if($orev) { 3845fd0d0d1SAndreas Gohr $msg .= sprintf( 3855fd0d0d1SAndreas Gohr ' ' . $this->getLang('diff'), 3865fd0d0d1SAndreas Gohr wl($orig, array('do' => 'diff', 'rev' => $orev)) 3875fd0d0d1SAndreas Gohr ); 38884877e9bSAndreas Gohr } 38900431e1eSAndreas Gohr 39000431e1eSAndreas Gohr echo '<div class="notify">' . $msg . '</div>'; 39184877e9bSAndreas Gohr } 392af1904f9SAndreas Gohr} 393