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 { 13af1904f9SAndreas Gohr var $trans = array(); 14af1904f9SAndreas Gohr var $tns = ''; 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 */ 22af1904f9SAndreas Gohr function helper_plugin_translation(){ 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 28af1904f9SAndreas Gohr $this->trans = strtolower(str_replace(',',' ',$this->getConf('translations'))); 29af1904f9SAndreas Gohr $this->trans = array_unique(array_filter(explode(' ',$this->trans))); 30af1904f9SAndreas Gohr sort($this->trans); 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 } 477c54a0a6SAndreas Gohr if(in_array($dfl,$this->trans)){ 487c54a0a6SAndreas Gohr $this->defaultlang = $dfl; 497c54a0a6SAndreas Gohr }else{ 507c54a0a6SAndreas Gohr $this->defaultlang = ''; 51af1904f9SAndreas Gohr array_unshift($this->trans,''); 527c54a0a6SAndreas Gohr } 537c54a0a6SAndreas Gohr 54af1904f9SAndreas Gohr $this->tns = cleanID($this->getConf('translationns')); 55af1904f9SAndreas Gohr if($this->tns) $this->tns .= ':'; 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){ 7126522e09SAndreas Gohr $rx = '/^'.$this->tns.'('.join('|',$this->trans).'):(.*)/'; 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(){ 837053cd66SAndreas Gohr $rx = '/(^|,|:|;|-)('.join('|',$this->trans).')($|,|:|;|-)/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){ 97af1904f9SAndreas Gohr $link = ':'.$this->tns.$lng.':'.$idpart; 98af1904f9SAndreas Gohr $name = $lng; 99af1904f9SAndreas Gohr }else{ 100af1904f9SAndreas Gohr $link = ':'.$this->tns.$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; 12984877e9bSAndreas Gohr if($this->tns && strpos($id,$this->tns) !== 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 /** 164*bbe70520SAndreas Gohr * Returns a list of (lc => link) for all existing translations of a page 165*bbe70520SAndreas Gohr * 166*bbe70520SAndreas Gohr * @param $id 167*bbe70520SAndreas Gohr * @return array 168*bbe70520SAndreas Gohr */ 169*bbe70520SAndreas Gohr function getAvailableTranslations($id){ 170*bbe70520SAndreas Gohr $result = array(); 171*bbe70520SAndreas Gohr 172*bbe70520SAndreas Gohr list($lc,$idpart) = $this->getTransParts($id); 173*bbe70520SAndreas Gohr $lang = $this->realLC($lc); 174*bbe70520SAndreas Gohr 175*bbe70520SAndreas Gohr foreach($this->trans as $t){ 176*bbe70520SAndreas Gohr if($t == $lc) continue; //skip self 177*bbe70520SAndreas Gohr list($link, $name) = $this->buildTransID($t, $idpart); 178*bbe70520SAndreas Gohr if(page_exists($link)){ 179*bbe70520SAndreas Gohr $result[$name] = $link; 180*bbe70520SAndreas Gohr } 181*bbe70520SAndreas Gohr } 182*bbe70520SAndreas Gohr 183*bbe70520SAndreas Gohr return $result; 184*bbe70520SAndreas Gohr } 185*bbe70520SAndreas Gohr 186*bbe70520SAndreas Gohr /** 1871469199dSAndreas Gohr * Displays the available and configured translations. Needs to be placed in the template. 1881469199dSAndreas Gohr */ 1891469199dSAndreas Gohr function showTranslations(){ 1901469199dSAndreas Gohr global $ID; 1911469199dSAndreas Gohr global $conf; 1921469199dSAndreas Gohr global $INFO; 1931469199dSAndreas Gohr 19484877e9bSAndreas Gohr if(!$this->istranslatable($ID)) return; 19584877e9bSAndreas Gohr $this->checkage(); 1961469199dSAndreas Gohr 19704971eeaSAndreas Gohr list($lc,$idpart) = $this->getTransParts($ID); 19804971eeaSAndreas Gohr $lang = $this->realLC($lc); 19939ecab8bSAndreas Gohr 2001469199dSAndreas 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'; 221c9640767STomasz Tomasik } 222fbc6382cSAndreas Gohr if($conf['userewrite']){ 223fbc6382cSAndreas Gohr $action = wl(); 224fbc6382cSAndreas Gohr }else{ 225fbc6382cSAndreas Gohr $action = script(); 226fbc6382cSAndreas Gohr } 227fbc6382cSAndreas Gohr 228fbc6382cSAndreas Gohr $out .= '<form action="'.$action.'" id="translation__dropdown">'; 22904971eeaSAndreas Gohr if($flag) $out .= '<img src="'.$flag.'" alt="'.hsc($lang).'" height="11" class="'.$class.'" /> '; 230c9640767STomasz Tomasik $out .= '<select name="id" class="'.$class.'">'; 23104971eeaSAndreas Gohr }else{ 23204971eeaSAndreas Gohr $out .= '<ul>'; 23304971eeaSAndreas Gohr } 23404971eeaSAndreas Gohr 23504971eeaSAndreas Gohr // insert items 23604971eeaSAndreas Gohr foreach($this->trans as $t){ 23704971eeaSAndreas Gohr $out .= $this->getTransItem($t, $idpart); 23804971eeaSAndreas Gohr } 23904971eeaSAndreas Gohr 24004971eeaSAndreas Gohr // close wrapper 24104971eeaSAndreas Gohr if($this->getConf('dropdown')){ 2421469199dSAndreas Gohr $out .= '</select>'; 2431469199dSAndreas Gohr $out .= '<input name="go" type="submit" value="→" />'; 2441469199dSAndreas Gohr $out .= '</form>'; 24504971eeaSAndreas Gohr }else{ 2461469199dSAndreas Gohr $out .= '</ul>'; 2471469199dSAndreas Gohr } 2481469199dSAndreas Gohr 24904971eeaSAndreas Gohr // show about if not already shown 250c730e7ddSAndreas Gohr if (!isset($this->opts['title']) && $this->getConf('about')) { 25104971eeaSAndreas Gohr $out .= ' '; 25204971eeaSAndreas Gohr $out .= $this->showAbout(); 25304971eeaSAndreas Gohr } 25404971eeaSAndreas Gohr 2551469199dSAndreas Gohr $out .= '</div>'; 2561469199dSAndreas Gohr 2571469199dSAndreas Gohr return $out; 2581469199dSAndreas Gohr } 259af1904f9SAndreas Gohr 26001dd7da9SAndreas Gohr /** 261*bbe70520SAndreas Gohr * Return the local name 262*bbe70520SAndreas Gohr * 263*bbe70520SAndreas Gohr * @param $lang 264*bbe70520SAndreas Gohr * @return string 265*bbe70520SAndreas Gohr */ 266*bbe70520SAndreas Gohr function getLocalName($lang){ 267*bbe70520SAndreas Gohr if ($this->LN[$lang]){ 268*bbe70520SAndreas Gohr return $this->LN[$lang]; 269*bbe70520SAndreas Gohr } 270*bbe70520SAndreas Gohr return $lang; 271*bbe70520SAndreas Gohr } 272*bbe70520SAndreas Gohr 273*bbe70520SAndreas Gohr /** 27401dd7da9SAndreas Gohr * Create the link or option for a single translation 27501dd7da9SAndreas Gohr * 27604971eeaSAndreas Gohr * @param $lc string The language code 27701dd7da9SAndreas Gohr * @param $idpart string The ID of the translated page 27804971eeaSAndreas Gohr * @returns string The item 27901dd7da9SAndreas Gohr */ 28004971eeaSAndreas Gohr function getTransItem($lc, $idpart) { 281c9640767STomasz Tomasik global $ID; 282c9640767STomasz Tomasik global $conf; 283c9640767STomasz Tomasik 28404971eeaSAndreas Gohr list($link,$lang) = $this->buildTransID($lc,$idpart); 285c9640767STomasz Tomasik $link = cleanID($link); 28604971eeaSAndreas Gohr 28704971eeaSAndreas Gohr // class 288c9640767STomasz Tomasik if(page_exists($link,'',false)){ 289c9640767STomasz Tomasik $class = 'wikilink1'; 290c9640767STomasz Tomasik }else{ 291c9640767STomasz Tomasik $class = 'wikilink2'; 292c9640767STomasz Tomasik } 293c9640767STomasz Tomasik 29404971eeaSAndreas Gohr // local language name 295*bbe70520SAndreas Gohr $localname = $this->getLocalName($lang); 296c9640767STomasz Tomasik 29704971eeaSAndreas Gohr // current? 29804971eeaSAndreas Gohr if($ID == $link){ 29904971eeaSAndreas Gohr $sel = ' selected="selected"'; 30004971eeaSAndreas Gohr $class .= ' cur'; 30104971eeaSAndreas Gohr }else{ 30204971eeaSAndreas Gohr $sel = ''; 30304971eeaSAndreas Gohr } 304c9640767STomasz Tomasik 30504971eeaSAndreas Gohr // flag 30604971eeaSAndreas Gohr if(isset($this->opts['flag'])){ 30704971eeaSAndreas Gohr $flag = DOKU_BASE.'lib/plugins/translation/flags/'.hsc($lang).'.gif'; 30804971eeaSAndreas Gohr $style = ' style="background-image: url(\''.$flag.'\')"'; 30904971eeaSAndreas Gohr $class .= ' flag'; 31004971eeaSAndreas Gohr } 31104971eeaSAndreas Gohr 31204971eeaSAndreas Gohr // what to display as name 31304971eeaSAndreas Gohr if(isset($this->opts['name'])){ 31404971eeaSAndreas Gohr $display = hsc($localname); 315c730e7ddSAndreas Gohr if(isset($this->opts['langcode'])) $display .= ' ('.hsc($lang).')'; 316c730e7ddSAndreas Gohr }elseif(isset($this->opts['langcode'])){ 31704971eeaSAndreas Gohr $display = hsc($lang); 31804971eeaSAndreas Gohr }else{ 31904971eeaSAndreas Gohr $display = ' '; 32004971eeaSAndreas Gohr } 32104971eeaSAndreas Gohr 32204971eeaSAndreas Gohr // prepare output 32304971eeaSAndreas Gohr $out = ''; 32404971eeaSAndreas Gohr if($this->getConf('dropdown')){ 3252546b043SAndreas Gohr if($conf['useslash']) $link = str_replace(':', '/', $link); 3262546b043SAndreas Gohr 32704971eeaSAndreas Gohr $out .= '<option class="'.$class.'" title="'.hsc($localname).'" value="'.$link.'"'.$sel.$style.'>'; 32804971eeaSAndreas Gohr $out .= $display; 32904971eeaSAndreas Gohr $out .= '</option>'; 33004971eeaSAndreas Gohr }else{ 331c9640767STomasz Tomasik $out .= '<li><div class="li">'; 33237140606SAndreas Gohr $out .= '<a href="'.wl($link).'" class="'.$class.'" title="'.hsc($localname).'">'; 33304971eeaSAndreas Gohr if($flag) $out .= '<img src="'.$flag.'" alt="'.hsc($lang).'" height="11" />'; 33404971eeaSAndreas Gohr $out .= $display; 335c9640767STomasz Tomasik $out .= '</a>'; 336c9640767STomasz Tomasik $out .= '</div></li>'; 337c9640767STomasz Tomasik } 338c9640767STomasz Tomasik 339c9640767STomasz Tomasik return $out; 340c9640767STomasz Tomasik } 341c9640767STomasz Tomasik 34284877e9bSAndreas Gohr /** 34384877e9bSAndreas Gohr * Checks if the current page is a translation of a page 34484877e9bSAndreas Gohr * in the default language. Displays a notice when it is 34584877e9bSAndreas Gohr * older than the original page. Tries to lin to a diff 34684877e9bSAndreas Gohr * with changes on the original since the translation 34784877e9bSAndreas Gohr */ 34884877e9bSAndreas Gohr function checkage(){ 34984877e9bSAndreas Gohr global $ID; 35084877e9bSAndreas Gohr global $INFO; 35184877e9bSAndreas Gohr if(!$this->getConf('checkage')) return; 35284877e9bSAndreas Gohr if(!$INFO['exists']) return; 35384877e9bSAndreas Gohr $lng = $this->getLangPart($ID); 35484877e9bSAndreas Gohr if($lng == $this->defaultlang) return; 355af1904f9SAndreas Gohr 35684877e9bSAndreas Gohr $rx = '/^'.$this->tns.'(('.join('|',$this->trans).'):)?/'; 35784877e9bSAndreas Gohr $idpart = preg_replace($rx,'',$ID); 35884877e9bSAndreas Gohr 35984877e9bSAndreas Gohr // compare modification times 36084877e9bSAndreas Gohr list($orig,$name) = $this->buildTransID($this->defaultlang,$idpart); 36184877e9bSAndreas Gohr $origfn = wikiFN($orig); 36284877e9bSAndreas Gohr if($INFO['lastmod'] >= @filemtime($origfn) ) return; 36384877e9bSAndreas Gohr 36484877e9bSAndreas Gohr // get revision from before translation 36584877e9bSAndreas Gohr $orev = 0; 36684877e9bSAndreas Gohr $revs = getRevisions($orig,0,100); 36784877e9bSAndreas Gohr foreach($revs as $rev){ 36884877e9bSAndreas Gohr if($rev < $INFO['lastmod']){ 36984877e9bSAndreas Gohr $orev = $rev; 37084877e9bSAndreas Gohr break; 37184877e9bSAndreas Gohr } 37284877e9bSAndreas Gohr } 37384877e9bSAndreas Gohr 37444552920SAndreas Gohr // see if the found revision still exists 37544552920SAndreas Gohr if($orev && !page_exists($orig,$orev)) $orev=0; 37644552920SAndreas Gohr 37784877e9bSAndreas Gohr // build the message and display it 378dc3fbdb9SOleksiy Zagorskyi $orig = cleanID($orig); 37984877e9bSAndreas Gohr $msg = sprintf($this->getLang('outdated'),wl($orig)); 38084877e9bSAndreas Gohr if($orev){ 38184877e9bSAndreas Gohr $msg .= sprintf(' '.$this->getLang('diff'), 38284877e9bSAndreas Gohr wl($orig,array('do'=>'diff','rev'=>$orev))); 38384877e9bSAndreas Gohr } 38400431e1eSAndreas Gohr 38500431e1eSAndreas Gohr echo '<div class="notify">'.$msg.'</div>'; 38684877e9bSAndreas Gohr } 387af1904f9SAndreas Gohr} 388