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 * @author Guy Brand <gb@isis.u-strasbg.fr> 8 */ 9 10// must be run within Dokuwiki 11if(!defined('DOKU_INC')) die(); 12 13if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 14require_once(DOKU_PLUGIN . 'action.php'); 15 16class action_plugin_autotranslation extends DokuWiki_Action_Plugin { 17 18 /** 19 * For the helper plugin 20 * @var helper_plugin_autotranslation 21 */ 22 private $helper = null; 23 24 private $locale; 25 26 /** 27 * Constructor. Load helper plugin 28 */ 29 function __construct() { 30 $this->helper = plugin_load('helper', 'autotranslation'); 31 } 32 33 /** 34 * Register the events 35 */ 36 function register(Doku_Event_Handler $controller) { 37 $scriptName = basename($_SERVER['PHP_SELF']); 38 39 // should the lang be applied to UI? 40 if($this->getConf('translateui')) { 41 switch($scriptName) { 42 case 'js.php': 43 $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'translation_js'); 44 $controller->register_hook('JS_CACHE_USE', 'BEFORE', $this, 'translation_jscache'); 45 break; 46 47 case 'ajax.php': 48 $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'translate_media_manager'); 49 break; 50 51 case 'mediamanager.php': 52 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'setJsCacheKey'); 53 break; 54 55 default: 56 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'setJsCacheKey'); 57 } 58 } 59 60 if($scriptName !== 'js.php' && $scriptName !== 'ajax.php') { 61 $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'translation_hook'); 62 $controller->register_hook('MEDIAMANAGER_STARTED', 'BEFORE', $this, 'translation_hook'); 63 } 64 65 $controller->register_hook('SEARCH_QUERY_PAGELOOKUP', 'AFTER', $this, 'translation_search'); 66 $controller->register_hook('COMMON_PAGETPL_LOAD', 'AFTER', $this, 'page_template_replacement'); 67 } 68 69 /** 70 * Hook Callback. Make current language available as page template placeholder and handle 71 * original language copying 72 * 73 * @param $event 74 * @param $args 75 */ 76 function page_template_replacement(Doku_Event $event, $args) { 77 global $ID; 78 79 // load orginal content as template? 80 if($this->getConf('copytrans') && $this->helper->istranslatable($ID, false)) { 81 // look for existing translations 82 $translations = $this->helper->getAvailableTranslations($ID); 83 if($translations) { 84 // find original language (might've been provided via parameter or use first translation) 85 $orig = (string) $_REQUEST['fromlang']; 86 if(!$orig) $orig = array_shift(array_keys($translations)); 87 88 // load file 89 $origfile = $translations[$orig]; 90 $event->data['tpl'] = io_readFile(wikiFN($origfile)); 91 92 // prefix with warning 93 $warn = io_readFile($this->localFN('totranslate')); 94 if($warn) $warn .= "\n\n"; 95 $event->data['tpl'] = $warn . $event->data['tpl']; 96 97 // show user a choice of translations if any 98 if(count($translations) > 1) { 99 $links = array(); 100 foreach($translations as $t => $l) { 101 $links[] = '<a href="' . wl($ID, array('do' => 'edit', 'fromlang' => $t)) . '">' . $this->helper->getLocalName($t) . '</a>'; 102 } 103 104 msg( 105 sprintf( 106 $this->getLang('transloaded'), 107 $this->helper->getLocalName($orig), 108 join(', ', $links) 109 ) 110 ); 111 } 112 113 } 114 } 115 116 // apply placeholders 117 $event->data['tpl'] = str_replace('@LANG@', $this->helper->realLC(''), $event->data['tpl']); 118 $event->data['tpl'] = str_replace('@TRANS@', $this->helper->getLangPart($ID), $event->data['tpl']); 119 } 120 121 /** 122 * Hook Callback. Load correct translation when loading JavaScript 123 * 124 * @param $event 125 * @param $args 126 */ 127 function translation_js(Doku_Event $event, $args) { 128 global $conf; 129 if(!isset($_GET['lang'])) return; 130 if(!in_array($_GET['lang'], $this->helper->translations)) return; 131 $lang = $_GET['lang']; 132 $event->data = $lang; 133 $conf['lang'] = $lang; 134 } 135 136 /** 137 * Hook Callback. Pass language code to JavaScript dispatcher 138 * 139 * @param $event 140 * @param $args 141 * @return bool 142 */ 143 function setJsCacheKey(Doku_Event $event, $args) { 144 if(!isset($this->locale)) return false; 145 $count = count($event->data['script']); 146 for($i = 0; $i < $count; $i++) { 147 if(!empty($event->data['script'][$i]['src']) && strpos($event->data['script'][$i]['src'], '/lib/exe/js.php') !== false) { 148 $event->data['script'][$i]['src'] .= '&lang=' . hsc($this->locale); 149 } 150 } 151 152 return false; 153 } 154 155 /** 156 * Hook Callback. Make sure the JavaScript is translation dependent 157 * 158 * @param $event 159 * @param $args 160 */ 161 function translation_jscache(Doku_Event $event, $args) { 162 if(!isset($_GET['lang'])) return; 163 if(!in_array($_GET['lang'], $this->helper->translations)) return; 164 165 $lang = $_GET['lang']; 166 // reuse the constructor to reinitialize the cache key 167 if(method_exists($event->data, '__construct')) { 168 // New PHP 5 style constructor 169 $event->data->__construct( 170 $event->data->key . $lang, 171 $event->data->ext 172 ); 173 } else { 174 // Old PHP 4 style constructor - deprecated 175 $event->data->cache( 176 $event->data->key . $lang, 177 $event->data->ext 178 ); 179 } 180 } 181 182 /** 183 * Hook Callback. Translate the AJAX loaded media manager 184 * 185 * @param $event 186 * @param $args 187 */ 188 function translate_media_manager(Doku_Event $event, $args) { 189 global $conf; 190 if(isset($_REQUEST['ID'])) { 191 $id = getID(); 192 $lc = $this->helper->getLangPart($id); 193 } elseif(isset($_SESSION[DOKU_COOKIE]['translationlc'])) { 194 $lc = $_SESSION[DOKU_COOKIE]['translationlc']; 195 } else { 196 return; 197 } 198 if(!$lc) return; 199 200 $conf['lang'] = $lc; 201 $event->data = $lc; 202 } 203 204 /** 205 * Hook Callback. Change the UI language in foreign language namespaces 206 */ 207 function translation_hook(Doku_Event $event, $args) { 208 global $ID; 209 global $lang; 210 global $conf; 211 global $ACT; 212 // redirect away from start page? 213 if($this->conf['redirectstart'] && $ID == $conf['start'] && $ACT == 'show') { 214 $lc = $this->helper->getBrowserLang(); 215 if(!$lc) $lc = $conf['lang']; 216 $this->_redirect($lc.':'.$conf['start']); 217 exit; 218 } 219 220 // Check if we can redirect 221 if($this->getConf('redirectlocalized')){ 222 $this->translation_redirect_localized(); 223 } 224 225 // check if we are in a foreign language namespace 226 $lc = $this->helper->getLangPart($ID); 227 228 // store language in session (for page related views only) 229 if(in_array($ACT, array('show', 'recent', 'diff', 'edit', 'preview', 'source', 'subscribe'))) { 230 $_SESSION[DOKU_COOKIE]['translationlc'] = $lc; 231 } 232 if(!$lc) $lc = $_SESSION[DOKU_COOKIE]['translationlc']; 233 if(!$lc) return; 234 $this->locale = $lc; 235 236 if(!$this->getConf('translateui')) { 237 return true; 238 } 239 240 if(file_exists(DOKU_INC . 'inc/lang/' . $lc . '/lang.php')) { 241 require(DOKU_INC . 'inc/lang/' . $lc . '/lang.php'); 242 } 243 $conf['lang_before_translation'] = $conf['lang']; //store for later access in syntax plugin 244 $conf['lang'] = $lc; 245 246 return true; 247 } 248 249 /** 250 * Hook Callback. Resort page match results so that results are ordered by translation, having the 251 * default language first 252 */ 253 function translation_search(Doku_Event $event, $args) { 254 255 if($event->data['has_titles']) { 256 // sort into translation slots 257 $res = array(); 258 foreach($event->result as $r => $t) { 259 $tr = $this->helper->getLangPart($r); 260 if(!is_array($res["x$tr"])) $res["x$tr"] = array(); 261 $res["x$tr"][] = array($r, $t); 262 } 263 // sort by translations 264 ksort($res); 265 // combine 266 $event->result = array(); 267 foreach($res as $r) { 268 foreach($r as $l) { 269 $event->result[$l[0]] = $l[1]; 270 } 271 } 272 } else { 273 # legacy support for old DokuWiki hooks 274 275 // sort into translation slots 276 $res = array(); 277 foreach($event->result as $r) { 278 $tr = $this->helper->getLangPart($r); 279 if(!is_array($res["x$tr"])) $res["x$tr"] = array(); 280 $res["x$tr"][] = $r; 281 } 282 // sort by translations 283 ksort($res); 284 // combine 285 $event->result = array(); 286 foreach($res as $r) { 287 $event->result = array_merge($event->result, $r); 288 } 289 } 290 } 291 292 /** 293 * Redirects to the localized version of the page when showing and browser says so and translation was explicitly requested 294 **/ 295 function translation_redirect_localized() { 296 global $ID; 297 global $conf; 298 global $ACT; 299 300 // redirect to localized page? 301 if( $ACT != 'show' ) { return; } 302 303 $override = isset($_REQUEST['tns']); // override enabled - comes from the bottom bar. 304 $lang = !empty($conf['lang_before_translation']) ? $conf['lang_before_translation'] : $conf['lang']; // Check for original language 305 306 // get current page language - if empty then default; 307 $currentSessionLanguage = $_SESSION[DOKU_COOKIE]['translationcur']; 308 $pageLang = $this->helper->getLangPart($ID); 309 310 if ( empty($pageLang) ) { 311 $pageLang = $lang; 312 } 313 314 // If both match, we're fine. 315 if ( $currentSessionLanguage == $pageLang ) { 316 return; 317 } 318 319 // check current translation 320 if ( empty( $currentSessionLanguage ) && !$override ) { 321 322 // If not set - we must just have entered - set the browser language 323 $currentSessionLanguage = $this->helper->getBrowserLang(); 324 325 // if no browser Language set, take entered namespace language - empty for default. 326 if ( !$currentSessionLanguage ) { 327 $currentSessionLanguage = $pageLang; 328 } 329 330 // Set new Language 331 $_SESSION[DOKU_COOKIE]['translationcur'] = $currentSessionLanguage; 332 333 // Write Language back 334 $pageLang = $currentSessionLanguage; 335 } 336 337 338 if ( $override && $pageLang != $currentSessionLanguage ) { 339 // Set new Language 340 $currentSessionLanguage = $pageLang; 341 $_SESSION[DOKU_COOKIE]['translationcur'] = $currentSessionLanguage; 342 } else if ( !$override ) { 343 // Write Language back 344 $pageLang = $currentSessionLanguage; 345 } 346 347 // If this is the default language, make empty 348 if ( $pageLang == $lang ) { 349 $pageLang = ''; 350 } 351 352 // Generate new Page ID 353 list($newPage,$name) = $this->helper->buildTransID($pageLang,$this->helper->getIDPart($ID)); 354 $newPage = cleanID($newPage); 355 356 // Check if Page exists 357 if ( $newPage != $ID && page_exists($newPage, '', false) ) { 358 // $newPage redirect 359 360 if ( auth_quickaclcheck($newPage) < AUTH_READ ) { return; } 361 362 session_write_close(); 363 $this->_redirect($newPage); 364 } 365 else 366 if ( $override ) { 367 // cleanup redirect 368 session_write_close(); 369 370 if ( auth_quickaclcheck($newPage) < AUTH_READ ) { return; } 371 372 $this->_redirect($ID); 373 } 374 375 // no redirect; 376 } 377 378 379 function _redirect($url) 380 { 381 unset($_GET['id']); 382 $more = array(); 383 384 if ( !empty($_GET) ) { 385 $params = ''; 386 foreach( $_GET as $key => $value ) { 387 // Possible multiple encodings. 388 $more[$key] = $value; 389 } 390 } 391 392 if ( wl( $url, $more, true, '&') != DOKU_URL . substr($_SERVER['REQUEST_URI'], 1) ) { 393 header('Location: ' . wl( $url, $more, true, '&'), 302); 394 exit; 395 } 396 } 397} 398 399//Setup VIM: ex: et ts=4 : 400