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