xref: /plugin/autotranslation/helper.php (revision 5fd0d0d1c6354560d100af5315ad8ff2bd24c576)
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     * Returns a list of (lc => link) for all existing translations of a page
165     *
166     * @param $id
167     * @return array
168     */
169    function getAvailableTranslations($id) {
170        $result = array();
171
172        list($lc, $idpart) = $this->getTransParts($id);
173        $lang = $this->realLC($lc);
174
175        foreach($this->trans as $t) {
176            if($t == $lc) continue; //skip self
177            list($link, $name) = $this->buildTransID($t, $idpart);
178            if(page_exists($link)) {
179                $result[$name] = $link;
180            }
181        }
182
183        return $result;
184    }
185
186    /**
187     * Displays the available and configured translations. Needs to be placed in the template.
188     */
189    function showTranslations() {
190        global $ID;
191        global $conf;
192        global $INFO;
193
194        if(!$this->istranslatable($ID)) return;
195        $this->checkage();
196
197        list($lc, $idpart) = $this->getTransParts($ID);
198        $lang = $this->realLC($lc);
199
200        $out = '<div class="plugin_translation">';
201
202        //show title and about
203        if(isset($this->opts['title'])) {
204            $out .= '<span>' . $this->getLang('translations');
205            if($this->getConf('about')) $out .= $this->showAbout();
206            $out .= ':</span> ';
207            if(isset($this->opts['twolines'])) $out .= '<br />';
208        }
209
210        // open wrapper
211        if($this->getConf('dropdown')) {
212            // select needs its own styling
213            if($INFO['exists']) {
214                $class = 'wikilink1';
215            } else {
216                $class = 'wikilink2';
217            }
218            if(isset($this->opts['flag'])) {
219                $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
220            }
221            if($conf['userewrite']) {
222                $action = wl();
223            } else {
224                $action = script();
225            }
226
227            $out .= '<form action="' . $action . '" id="translation__dropdown">';
228            if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" class="' . $class . '" /> ';
229            $out .= '<select name="id" class="' . $class . '">';
230        } else {
231            $out .= '<ul>';
232        }
233
234        // insert items
235        foreach($this->trans as $t) {
236            $out .= $this->getTransItem($t, $idpart);
237        }
238
239        // close wrapper
240        if($this->getConf('dropdown')) {
241            $out .= '</select>';
242            $out .= '<input name="go" type="submit" value="&rarr;" />';
243            $out .= '</form>';
244        } else {
245            $out .= '</ul>';
246        }
247
248        // show about if not already shown
249        if(!isset($this->opts['title']) && $this->getConf('about')) {
250            $out .= '&nbsp';
251            $out .= $this->showAbout();
252        }
253
254        $out .= '</div>';
255
256        return $out;
257    }
258
259    /**
260     * Return the local name
261     *
262     * @param $lang
263     * @return string
264     */
265    function getLocalName($lang) {
266        if($this->LN[$lang]) {
267            return $this->LN[$lang];
268        }
269        return $lang;
270    }
271
272    /**
273     * Create the link or option for a single translation
274     *
275     * @param $lc string      The language code
276     * @param $idpart string  The ID of the translated page
277     * @returns string        The item
278     */
279    function getTransItem($lc, $idpart) {
280        global $ID;
281        global $conf;
282
283        list($link, $lang) = $this->buildTransID($lc, $idpart);
284        $link = cleanID($link);
285
286        // class
287        if(page_exists($link, '', false)) {
288            $class = 'wikilink1';
289        } else {
290            $class = 'wikilink2';
291        }
292
293        // local language name
294        $localname = $this->getLocalName($lang);
295
296        // current?
297        if($ID == $link) {
298            $sel = ' selected="selected"';
299            $class .= ' cur';
300        } else {
301            $sel = '';
302        }
303
304        // flag
305        if(isset($this->opts['flag'])) {
306            $flag = DOKU_BASE . 'lib/plugins/translation/flags/' . hsc($lang) . '.gif';
307            $style = ' style="background-image: url(\'' . $flag . '\')"';
308            $class .= ' flag';
309        }
310
311        // what to display as name
312        if(isset($this->opts['name'])) {
313            $display = hsc($localname);
314            if(isset($this->opts['langcode'])) $display .= ' (' . hsc($lang) . ')';
315        } elseif(isset($this->opts['langcode'])) {
316            $display = hsc($lang);
317        } else {
318            $display = '&nbsp;';
319        }
320
321        // prepare output
322        $out = '';
323        if($this->getConf('dropdown')) {
324            if($conf['useslash']) $link = str_replace(':', '/', $link);
325
326            $out .= '<option class="' . $class . '" title="' . hsc($localname) . '" value="' . $link . '"' . $sel . $style . '>';
327            $out .= $display;
328            $out .= '</option>';
329        } else {
330            $out .= '<li><div class="li">';
331            $out .= '<a href="' . wl($link) . '" class="' . $class . '" title="' . hsc($localname) . '">';
332            if($flag) $out .= '<img src="' . $flag . '" alt="' . hsc($lang) . '" height="11" />';
333            $out .= $display;
334            $out .= '</a>';
335            $out .= '</div></li>';
336        }
337
338        return $out;
339    }
340
341    /**
342     * Checks if the current page is a translation of a page
343     * in the default language. Displays a notice when it is
344     * older than the original page. Tries to lin to a diff
345     * with changes on the original since the translation
346     */
347    function checkage() {
348        global $ID;
349        global $INFO;
350        if(!$this->getConf('checkage')) return;
351        if(!$INFO['exists']) return;
352        $lng = $this->getLangPart($ID);
353        if($lng == $this->defaultlang) return;
354
355        $rx = '/^' . $this->tns . '((' . join('|', $this->trans) . '):)?/';
356        $idpart = preg_replace($rx, '', $ID);
357
358        // compare modification times
359        list($orig, $name) = $this->buildTransID($this->defaultlang, $idpart);
360        $origfn = wikiFN($orig);
361        if($INFO['lastmod'] >= @filemtime($origfn)) return;
362
363        // get revision from before translation
364        $orev = 0;
365        $revs = getRevisions($orig, 0, 100);
366        foreach($revs as $rev) {
367            if($rev < $INFO['lastmod']) {
368                $orev = $rev;
369                break;
370            }
371        }
372
373        // see if the found revision still exists
374        if($orev && !page_exists($orig, $orev)) $orev = 0;
375
376        // build the message and display it
377        $orig = cleanID($orig);
378        $msg = sprintf($this->getLang('outdated'), wl($orig));
379        if($orev) {
380            $msg .= sprintf(
381                ' ' . $this->getLang('diff'),
382                wl($orig, array('do' => 'diff', 'rev' => $orev))
383            );
384        }
385
386        echo '<div class="notify">' . $msg . '</div>';
387    }
388}
389