1<?php 2/** 3 * Translation Plugin: Simple multilanguage plugin 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class helper_plugin_translation extends DokuWiki_Plugin { 13 var $trans = array(); 14 var $tns = ''; 15 var $defaultlang = ''; 16 var $LN = array(); // hold native names 17 var $opts = array(); // display options 18 19 /** 20 * Initialize 21 */ 22 function helper_plugin_translation(){ 23 global $conf; 24 require_once(DOKU_INC.'inc/pageutils.php'); 25 require_once(DOKU_INC.'inc/utf8.php'); 26 27 // load wanted translation into array 28 $this->trans = strtolower(str_replace(',',' ',$this->getConf('translations'))); 29 $this->trans = array_unique(array_filter(explode(' ',$this->trans))); 30 sort($this->trans); 31 32 // load language names 33 $this->LN = confToHash(dirname(__FILE__).'/lang/langnames.txt'); 34 35 // display options 36 $this->opts = $this->getConf('display'); 37 $this->opts = explode(',',$this->opts); 38 $this->opts = array_map('trim',$this->opts); 39 $this->opts = array_fill_keys($this->opts, true); 40 41 // get default translation 42 if(!$conf['lang_before_translation']){ 43 $dfl = $conf['lang']; 44 } else { 45 $dfl = $conf['lang_before_translation']; 46 } 47 if(in_array($dfl,$this->trans)){ 48 $this->defaultlang = $dfl; 49 }else{ 50 $this->defaultlang = ''; 51 array_unshift($this->trans,''); 52 } 53 54 $this->tns = cleanID($this->getConf('translationns')); 55 if($this->tns) $this->tns .= ':'; 56 } 57 58 /** 59 * Check if the given ID is a translation and return the language code. 60 */ 61 function getLangPart($id){ 62 list($lng) = $this->getTransParts($id); 63 return $lng; 64 } 65 66 /** 67 * Check if the given ID is a translation and return the language code and 68 * the id part. 69 */ 70 function getTransParts($id){ 71 $rx = '/^'.$this->tns.'('.join('|',$this->trans).'):(.*)/'; 72 if(preg_match($rx,$id,$match)){ 73 return array($match[1],$match[2]); 74 } 75 return array('',$id); 76 } 77 78 /** 79 * Returns the browser language if it matches with one of the configured 80 * languages 81 */ 82 function getBrowserLang(){ 83 $rx = '/(^|,|:|;|-)('.join('|',$this->trans).')($|,|:|;|-)/i'; 84 if(preg_match($rx,$_SERVER['HTTP_ACCEPT_LANGUAGE'],$match)){ 85 return strtolower($match[2]); 86 } 87 return false; 88 } 89 90 /** 91 * Returns the ID and name to the wanted translation, empty 92 * $lng is default lang 93 */ 94 function buildTransID($lng,$idpart){ 95 global $conf; 96 if($lng){ 97 $link = ':'.$this->tns.$lng.':'.$idpart; 98 $name = $lng; 99 }else{ 100 $link = ':'.$this->tns.$idpart; 101 $name = $this->realLC(''); 102 } 103 return array($link,$name); 104 } 105 106 /** 107 * Returns the real language code, even when an empty one is given 108 * (eg. resolves th default language) 109 */ 110 function realLC($lc){ 111 global $conf; 112 if($lc){ 113 return $lc; 114 }elseif(!$conf['lang_before_translation']){ 115 return $conf['lang']; 116 } else { 117 return $conf['lang_before_translation']; 118 } 119 } 120 121 /** 122 * Check if current ID should be translated and any GUI 123 * should be shown 124 */ 125 function istranslatable($id,$checkact=true){ 126 global $ACT; 127 128 if($checkact && $ACT != 'show') return false; 129 if($this->tns && strpos($id,$this->tns) !== 0) return false; 130 $skiptrans = trim($this->getConf('skiptrans')); 131 if($skiptrans && preg_match('/'.$skiptrans.'/ui',':'.$id)) return false; 132 $meta = p_get_metadata($id); 133 if($meta['plugin']['translation']['notrans']) return false; 134 135 return true; 136 } 137 138 /** 139 * Return the (localized) about link 140 */ 141 function showAbout() { 142 global $ID; 143 global $conf; 144 global $INFO; 145 146 $curlc = $this->getLangPart($ID); 147 148 $about = $this->getConf('about'); 149 if($this->getConf('localabout')){ 150 list($lc,$idpart) = $this->getTransParts($about); 151 list($about,$name) = $this->buildTransID($curlc,$idpart); 152 $about = cleanID($about); 153 } 154 155 $out = ''; 156 $out .= '<sup>'; 157 $out .= html_wikilink($about,'?'); 158 $out .= '</sup>'; 159 160 return $out; 161 } 162 163 /** 164 * Displays the available and configured translations. Needs to be placed in the template. 165 */ 166 function showTranslations(){ 167 global $ID; 168 global $conf; 169 global $INFO; 170 171 if(!$this->istranslatable($ID)) return; 172 $this->checkage(); 173 174 list($lc,$idpart) = $this->getTransParts($ID); 175 $lang = $this->realLC($lc); 176 177 178 $out = '<div class="plugin_translation">'; 179 180 //show title and about 181 if (isset($this->opts['title'])){ 182 $out .= '<span>'.$this->getLang('translations'); 183 if ($this->getConf('about')) $out .= $this->showAbout(); 184 $out .= ':</span> '; 185 if(isset($this->opts['twolines'])) $out .= '<br />'; 186 } 187 188 // open wrapper 189 if($this->getConf('dropdown')){ 190 // select needs its own styling 191 if($INFO['exists']){ 192 $class = 'wikilink1'; 193 }else{ 194 $class = 'wikilink2'; 195 } 196 if(isset($this->opts['flag'])){ 197 $flag = DOKU_BASE.'lib/plugins/translation/flags/'.hsc($lang).'.gif'; 198 } 199 $out .= '<form action="'.wl().'" id="translation__dropdown">'; 200 if($flag) $out .= '<img src="'.$flag.'" alt="'.hsc($lang).'" height="11" class="'.$class.'" /> '; 201 $out .= '<select name="id" class="'.$class.'">'; 202 }else{ 203 $out .= '<ul>'; 204 } 205 206 // insert items 207 foreach($this->trans as $t){ 208 $out .= $this->getTransItem($t, $idpart); 209 } 210 211 // close wrapper 212 if($this->getConf('dropdown')){ 213 $out .= '</select>'; 214 $out .= '<input name="go" type="submit" value="→" />'; 215 $out .= '</form>'; 216 }else{ 217 $out .= '</ul>'; 218 } 219 220 // show about if not already shown 221 if (!isset($this->opts['title']) && $this->getConf('about')) { 222 $out .= ' '; 223 $out .= $this->showAbout(); 224 } 225 226 $out .= '</div>'; 227 228 return $out; 229 } 230 231 /** 232 * Create the link or option for a single translation 233 * 234 * @param $lc string The language code 235 * @param $idpart string The ID of the translated page 236 * @returns string The item 237 */ 238 function getTransItem($lc, $idpart) { 239 global $ID; 240 global $conf; 241 242 list($link,$lang) = $this->buildTransID($lc,$idpart); 243 $link = cleanID($link); 244 245 // class 246 if(page_exists($link,'',false)){ 247 $class = 'wikilink1'; 248 }else{ 249 $class = 'wikilink2'; 250 } 251 252 // local language name 253 if ($this->LN[$lang]){ 254 $localname = $this->LN[$lang]; 255 } else{ 256 $localname = $lang; 257 } 258 259 // current? 260 if($ID == $link){ 261 $sel = ' selected="selected"'; 262 $class .= ' cur'; 263 }else{ 264 $sel = ''; 265 } 266 267 // flag 268 if(isset($this->opts['flag'])){ 269 $flag = DOKU_BASE.'lib/plugins/translation/flags/'.hsc($lang).'.gif'; 270 $style = ' style="background-image: url(\''.$flag.'\')"'; 271 $class .= ' flag'; 272 } 273 274 // what to display as name 275 if(isset($this->opts['name'])){ 276 $display = hsc($localname); 277 if(isset($this->opts['langcode'])) $display .= ' ('.hsc($lang).')'; 278 }elseif(isset($this->opts['langcode'])){ 279 $display = hsc($lang); 280 }else{ 281 $display = ' '; 282 } 283 284 // prepare output 285 $out = ''; 286 if($this->getConf('dropdown')){ 287 if($conf['useslash']) $link = str_replace(':', '/', $link); 288 289 $out .= '<option class="'.$class.'" title="'.hsc($localname).'" value="'.$link.'"'.$sel.$style.'>'; 290 $out .= $display; 291 $out .= '</option>'; 292 }else{ 293 $out .= '<li><div class="li">'; 294 $out .= '<a href="'.wl($link).'" class="'.$class.'" title="'.hsc($localname).'">'; 295 if($flag) $out .= '<img src="'.$flag.'" alt="'.hsc($lang).'" height="11" />'; 296 $out .= $display; 297 $out .= '</a>'; 298 $out .= '</div></li>'; 299 } 300 301 return $out; 302 } 303 304 /** 305 * Checks if the current page is a translation of a page 306 * in the default language. Displays a notice when it is 307 * older than the original page. Tries to lin to a diff 308 * with changes on the original since the translation 309 */ 310 function checkage(){ 311 global $ID; 312 global $INFO; 313 if(!$this->getConf('checkage')) return; 314 if(!$INFO['exists']) return; 315 $lng = $this->getLangPart($ID); 316 if($lng == $this->defaultlang) return; 317 318 $rx = '/^'.$this->tns.'(('.join('|',$this->trans).'):)?/'; 319 $idpart = preg_replace($rx,'',$ID); 320 321 // compare modification times 322 list($orig,$name) = $this->buildTransID($this->defaultlang,$idpart); 323 $origfn = wikiFN($orig); 324 if($INFO['lastmod'] >= @filemtime($origfn) ) return; 325 326 // get revision from before translation 327 $orev = 0; 328 $revs = getRevisions($orig,0,100); 329 foreach($revs as $rev){ 330 if($rev < $INFO['lastmod']){ 331 $orev = $rev; 332 break; 333 } 334 } 335 336 // see if the found revision still exists 337 if($orev && !page_exists($orig,$orev)) $orev=0; 338 339 // build the message and display it 340 $orig = cleanID($orig); 341 $msg = sprintf($this->getLang('outdated'),wl($orig)); 342 if($orev){ 343 $msg .= sprintf(' '.$this->getLang('diff'), 344 wl($orig,array('do'=>'diff','rev'=>$orev))); 345 } 346 347 echo '<div class="notify">'.$msg.'</div>'; 348 } 349} 350