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