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