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