xref: /plugin/deeplautotranslate/action.php (revision 84cda41fb0c9f0aa3a42a9cd8c476c04c815e89d)
13832d0abSNetali<?php
23832d0abSNetali/**
33832d0abSNetali * Deepl Autotranslate Plugin
43832d0abSNetali *
53832d0abSNetali * @author     Jennifer Graul <me@netali.de>
63832d0abSNetali */
73832d0abSNetali
83832d0abSNetaliif(!defined('DOKU_INC')) die();
93832d0abSNetali
1081931e50SNetaliuse \dokuwiki\HTTP\DokuHTTPClient;
113c636ad3SNetaliuse \dokuwiki\plugin\deeplautotranslate\MenuItem;
1281931e50SNetali
133832d0abSNetaliclass action_plugin_deeplautotranslate extends DokuWiki_Action_Plugin {
143832d0abSNetali
153832d0abSNetali    // manual mapping of ISO-languages to DeepL-languages to fix inconsistent naming
163832d0abSNetali    private $langs = [
173832d0abSNetali        'bg' => 'BG',
183832d0abSNetali        'cs' => 'CS',
193832d0abSNetali        'da' => 'DA',
203832d0abSNetali        'de' => 'DE',
213832d0abSNetali        'de-informal' => 'DE',
223832d0abSNetali        'el' => 'EL',
233832d0abSNetali        'en' => 'EN-GB',
243832d0abSNetali        'es' => 'ES',
253832d0abSNetali        'et' => 'ET',
263832d0abSNetali        'fi' => 'FI',
273832d0abSNetali        'fr' => 'FR',
283832d0abSNetali        'hu' => 'HU',
293832d0abSNetali        'hu-formal' => 'HU',
303832d0abSNetali        'it' => 'IT',
313832d0abSNetali        'ja' => 'JA',
323832d0abSNetali        'lt' => 'LT',
333832d0abSNetali        'lv' => 'LV',
343832d0abSNetali        'nl' => 'NL',
353832d0abSNetali        'pl' => 'PL',
363832d0abSNetali        'pt' => 'PT-PT',
373832d0abSNetali        'ro' => 'RO',
383832d0abSNetali        'ru' => 'RU',
393832d0abSNetali        'sk' => 'SK',
403832d0abSNetali        'sl' => 'SL',
413832d0abSNetali        'sv' => 'SV',
423832d0abSNetali        'zh' => 'ZH'
433832d0abSNetali    ];
443832d0abSNetali
453832d0abSNetali    /**
463832d0abSNetali     * Register its handlers with the DokuWiki's event controller
473832d0abSNetali     */
483832d0abSNetali    public function register(Doku_Event_Handler $controller) {
49153e4498SNetali        $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'preprocess');
503832d0abSNetali        $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'autotrans_editor');
513c636ad3SNetali        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button');
523c636ad3SNetali    }
533c636ad3SNetali
540180404cSNetali    public function add_menu_button(Doku_Event $event): void {
55153e4498SNetali        global $ID;
56bbb1fba9SNetali        global $ACT;
57bbb1fba9SNetali
58bbb1fba9SNetali        if ($ACT != 'show') return;
59153e4498SNetali
603c636ad3SNetali        if ($event->data['view'] != 'page') return;
613c636ad3SNetali
623c636ad3SNetali        if (!$this->getConf('show_button')) return;
63153e4498SNetali
64153e4498SNetali        $split_id = explode(':', $ID);
65153e4498SNetali        $lang_ns = array_shift($split_id);
66153e4498SNetali        // check if we are in a language namespace
67153e4498SNetali        if (array_key_exists($lang_ns, $this->langs)) {
68ff327fe6SNetali            if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) {
6999da9a08SNetali                // if the default lang is in a namespace and we are in that namespace --> check for push translation
7099da9a08SNetali                if (!$this->check_do_push_translate()) return;
7199da9a08SNetali            } else {
72153e4498SNetali                // in language namespace --> check if we should translate
733c636ad3SNetali                if (!$this->check_do_translation(true)) return;
7499da9a08SNetali            }
75153e4498SNetali        } else {
7699da9a08SNetali            // do not show the button if we are not in a language namespace and the default language is in a namespace
7799da9a08SNetali            if($this->getConf('default_lang_in_ns')) return;
7899da9a08SNetali            // not in language namespace and default language is npt in a namespace --> check if we should show the push translate button
79153e4498SNetali            if (!$this->check_do_push_translate()) return;
80153e4498SNetali        }
813c636ad3SNetali
823c636ad3SNetali        array_splice($event->data['items'], -1, 0, [new MenuItem()]);
833832d0abSNetali    }
843832d0abSNetali
85153e4498SNetali    public function preprocess(Doku_Event  $event, $param): void {
863832d0abSNetali        global $ID;
873c636ad3SNetali
883c636ad3SNetali        // check if action is show or translate
893c636ad3SNetali        if ($event->data != 'show' and $event->data != 'translate') return;
903c636ad3SNetali
91153e4498SNetali        $split_id = explode(':', $ID);
92153e4498SNetali        $lang_ns = array_shift($split_id);
93153e4498SNetali        // check if we are in a language namespace
94153e4498SNetali        if (array_key_exists($lang_ns, $this->langs)) {
95ff327fe6SNetali            if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) {
9699da9a08SNetali                // if the default lang is in a namespace and we are in that namespace --> push translate
9799da9a08SNetali                $this->push_translate($event);
9899da9a08SNetali            } else {
9999da9a08SNetali                // in language namespace --> autotrans direct
100153e4498SNetali                $this->autotrans_direct($event);
10199da9a08SNetali            }
102153e4498SNetali        } else {
103153e4498SNetali            // not in language namespace --> push translate
104153e4498SNetali            $this->push_translate($event);
105153e4498SNetali        }
106153e4498SNetali    }
107153e4498SNetali
108153e4498SNetali    private function autotrans_direct(Doku_Event $event): void {
109153e4498SNetali        global $ID;
110153e4498SNetali
1113c636ad3SNetali        // abort if action is translate and the translate button is disabled
1123c636ad3SNetali        if ($event->data == 'translate' and !$this->getConf('show_button')) return;
1133c636ad3SNetali
1143c636ad3SNetali        // do nothing on show action when mode is not direct
1153c636ad3SNetali        if ($event->data == 'show' and $this->get_mode() != 'direct') return;
1163c636ad3SNetali
1173c636ad3SNetali        // allow translation of existing pages is we are in the translate action
1183c636ad3SNetali        $allow_existing = ($event->data == 'translate');
1193c636ad3SNetali
1203c636ad3SNetali        // reset action to show
1213c636ad3SNetali        $event->data = 'show';
1223c636ad3SNetali
123153e4498SNetali        if (!$this->check_do_translation($allow_existing)) {
124153e4498SNetali            return;
125153e4498SNetali        }
1263832d0abSNetali
1270180404cSNetali        $org_page_info = $this->get_org_page_info();
1280180404cSNetali        $translated_text = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]);
1293832d0abSNetali
130153e4498SNetali        if ($translated_text === '') {
131153e4498SNetali            return;
132153e4498SNetali        }
1333832d0abSNetali
1343832d0abSNetali        saveWikiText($ID, $translated_text, 'Automatic translation');
1353832d0abSNetali
136153e4498SNetali        msg($this->getLang('msg_translation_success'), 1);
137153e4498SNetali
1383c636ad3SNetali        // reload the page after translation
1393c636ad3SNetali        send_redirect(wl($ID));
1403832d0abSNetali    }
1413832d0abSNetali
142153e4498SNetali    public function autotrans_editor(Doku_Event $event, $param): void {
1433832d0abSNetali        if ($this->get_mode() != 'editor') return;
1443832d0abSNetali
1453832d0abSNetali        if (!$this->check_do_translation()) return;
1463832d0abSNetali
1470180404cSNetali        $org_page_info = $this->get_org_page_info();
1483832d0abSNetali
1490180404cSNetali        $event->data['tpl'] = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]);
1503832d0abSNetali    }
1513832d0abSNetali
152153e4498SNetali    private function push_translate(Doku_Event $event): void {
153153e4498SNetali        global $ID;
154153e4498SNetali
155153e4498SNetali        // check if action is translate
156153e4498SNetali        if ($event->data != 'translate') return;
157153e4498SNetali
158153e4498SNetali        // check if button is enabled
159153e4498SNetali        if (!$this->getConf('show_button')) {
160153e4498SNetali            send_redirect(wl($ID));
161153e4498SNetali            return;
162153e4498SNetali        }
163153e4498SNetali
164153e4498SNetali        if (!$this->check_do_push_translate()) {
165153e4498SNetali            send_redirect(wl($ID));
166153e4498SNetali            return;
167153e4498SNetali        }
168153e4498SNetali
169153e4498SNetali        // push translate
170153e4498SNetali        $push_langs = $this->get_push_langs();
171153e4498SNetali        $org_page_text = rawWiki($ID);
172153e4498SNetali        foreach ($push_langs as $lang) {
173153e4498SNetali            // skip invalid languages
174153e4498SNetali            if (!array_key_exists($lang, $this->langs)) {
175153e4498SNetali                msg($this->getLang('msg_translation_fail_invalid_lang') . $lang, -1);
176153e4498SNetali                continue;
177153e4498SNetali            }
178153e4498SNetali
17999da9a08SNetali            if ($this->getConf('default_lang_in_ns')) {
18099da9a08SNetali                // if default lang is in ns: replace language namespace in ID
18199da9a08SNetali                $split_id = explode(':', $ID);
18299da9a08SNetali                array_shift($split_id);
18399da9a08SNetali                $lang_id = implode(':', $split_id);
18499da9a08SNetali                $lang_id = $lang . ':' . $lang_id;
18599da9a08SNetali            } else {
18699da9a08SNetali                // if default lang is not in ns: add language namespace to ID
187153e4498SNetali                $lang_id = $lang . ':' . $ID;
18899da9a08SNetali            }
189153e4498SNetali
190153e4498SNetali            // check permissions
191a3a51507SNetali            $perm = auth_quickaclcheck($lang_id);
192153e4498SNetali            $exists = page_exists($lang_id);
193153e4498SNetali            if (($exists and $perm < AUTH_EDIT) or (!$exists and $perm < AUTH_CREATE)) {
194153e4498SNetali                msg($this->getLang('msg_translation_fail_no_permissions') . $lang_id, -1);
195153e4498SNetali                continue;
196153e4498SNetali            }
197153e4498SNetali
1980180404cSNetali            $translated_text = $this->deepl_translate($org_page_text, $lang, getNS($ID));
199153e4498SNetali            saveWikiText($lang_id, $translated_text, 'Automatic push translation');
200153e4498SNetali        }
201153e4498SNetali
202153e4498SNetali        msg($this->getLang('msg_translation_success'), 1);
203153e4498SNetali
204153e4498SNetali        // reload the page after translation to clear the action
205153e4498SNetali        send_redirect(wl($ID));
206153e4498SNetali    }
207153e4498SNetali
2083832d0abSNetali    private function get_mode(): string {
2093832d0abSNetali        global $ID;
2103832d0abSNetali        if ($this->getConf('editor_regex')) {
2113832d0abSNetali            if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor';
2123832d0abSNetali        }
2133832d0abSNetali        if ($this->getConf('direct_regex')) {
2143832d0abSNetali            if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct';
2153832d0abSNetali        }
2163832d0abSNetali        return $this->getConf('mode');
2173832d0abSNetali    }
2183832d0abSNetali
2193832d0abSNetali    private function get_target_lang(): string {
2203832d0abSNetali        global $ID;
2213832d0abSNetali        $split_id = explode(':', $ID);
2223832d0abSNetali        return array_shift($split_id);
2233832d0abSNetali    }
2243832d0abSNetali
225ff327fe6SNetali    private function get_default_lang(): string {
226ff327fe6SNetali        global $conf;
227ff327fe6SNetali
228ff327fe6SNetali        if (empty($conf['lang_before_translation'])) {
229ff327fe6SNetali            $default_lang = $conf['lang'];
230ff327fe6SNetali        } else {
231ff327fe6SNetali            $default_lang = $conf['lang_before_translation'];
232ff327fe6SNetali        }
233ff327fe6SNetali
234ff327fe6SNetali        return $default_lang;
235ff327fe6SNetali    }
236ff327fe6SNetali
2370180404cSNetali    private function get_org_page_info(): array {
2383832d0abSNetali        global $ID;
2393832d0abSNetali
2403832d0abSNetali        $split_id = explode(':', $ID);
2413832d0abSNetali        array_shift($split_id);
2423832d0abSNetali        $org_id = implode(':', $split_id);
2433832d0abSNetali
24499da9a08SNetali        // if default lang is in ns: add default ns in front of org id
24599da9a08SNetali        if ($this->getConf('default_lang_in_ns')) {
246ff327fe6SNetali            $org_id = $this->get_default_lang() . ':' . $org_id;
24799da9a08SNetali        }
24899da9a08SNetali
2490180404cSNetali        return array("ns" => getNS($org_id), "text" => rawWiki($org_id));
2503832d0abSNetali    }
2513832d0abSNetali
2523c636ad3SNetali    private function check_do_translation($allow_existing = false): bool {
2533832d0abSNetali        global $INFO;
2543832d0abSNetali        global $ID;
2553832d0abSNetali
2563c636ad3SNetali        // only translate if the current page does not exist
2573c636ad3SNetali        if ($INFO['exists'] and !$allow_existing) return false;
2583c636ad3SNetali
2593c636ad3SNetali        // permission check
2603c636ad3SNetali        $perm = auth_quickaclcheck($ID);
2613c636ad3SNetali        if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false;
2623c636ad3SNetali
2633832d0abSNetali        // skip blacklisted namespaces and pages
2643832d0abSNetali        if ($this->getConf('blacklist_regex')) {
2653832d0abSNetali            if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false;
2663832d0abSNetali        }
2673832d0abSNetali
2683832d0abSNetali        $split_id = explode(':', $ID);
2693832d0abSNetali        $lang_ns = array_shift($split_id);
2703832d0abSNetali        // only translate if the current page is in a language namespace
2713832d0abSNetali        if (!array_key_exists($lang_ns, $this->langs)) return false;
2723832d0abSNetali
2733832d0abSNetali        $org_id = implode(':', $split_id);
27499da9a08SNetali
27599da9a08SNetali        // if default lang is in ns: add default ns in front of org id
27699da9a08SNetali        if ($this->getConf('default_lang_in_ns')) {
277ff327fe6SNetali            $org_id = $this->get_default_lang() . ':' . $org_id;
27899da9a08SNetali        }
27999da9a08SNetali
2803832d0abSNetali        // check if the original page exists
2813832d0abSNetali        if (!page_exists($org_id)) return false;
2823832d0abSNetali
2833832d0abSNetali        return true;
2843832d0abSNetali    }
2853832d0abSNetali
286153e4498SNetali    private function check_do_push_translate(): bool {
287153e4498SNetali        global $ID;
28899da9a08SNetali        global $INFO;
28999da9a08SNetali
29099da9a08SNetali        if (!$INFO['exists']) return false;
29199da9a08SNetali
292a3a51507SNetali        // only allow push translation if the user can edit this page
293a3a51507SNetali        $perm = auth_quickaclcheck($ID);
294a3a51507SNetali        if ($perm < AUTH_EDIT) return false;
295a3a51507SNetali
29699da9a08SNetali        // if default language is in namespace: only allow push translation from that namespace
29799da9a08SNetali        if($this->getConf('default_lang_in_ns')) {
29899da9a08SNetali            $split_id = explode(':', $ID);
29999da9a08SNetali            $lang_ns = array_shift($split_id);
30099da9a08SNetali
301ff327fe6SNetali            if ($lang_ns !== $this->get_default_lang()) return false;
30299da9a08SNetali        }
303153e4498SNetali
304153e4498SNetali        $push_langs = $this->get_push_langs();
305153e4498SNetali        // push_langs empty --> push_translate disabled --> abort
306153e4498SNetali        if (empty($push_langs)) return false;
307153e4498SNetali
308153e4498SNetali        // skip blacklisted namespaces and pages
309153e4498SNetali        if ($this->getConf('blacklist_regex')) {
310153e4498SNetali            // blacklist regex match --> abort
311153e4498SNetali            if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false;
312153e4498SNetali        }
313153e4498SNetali
314153e4498SNetali        return true;
315153e4498SNetali    }
316153e4498SNetali
3170180404cSNetali    private function deepl_translate($text, $target_lang, $org_ns): string {
318153e4498SNetali        if (!trim($this->getConf('api_key'))) return '';
3193832d0abSNetali
3200180404cSNetali        $text = $this->patch_links($text, $target_lang, $org_ns);
3210180404cSNetali
3223832d0abSNetali        $text = $this->insert_ignore_tags($text);
3233832d0abSNetali
3243832d0abSNetali        $data = [
3253832d0abSNetali            'auth_key' => $this->getConf('api_key'),
3260180404cSNetali            'target_lang' => $this->langs[$target_lang],
3273832d0abSNetali            'tag_handling' => 'xml',
3280180404cSNetali            'ignore_tags' => 'ignore',
3293832d0abSNetali            'text' => $text
3303832d0abSNetali        ];
3313832d0abSNetali
3323832d0abSNetali        if ($this->getConf('api') == 'free') {
33381931e50SNetali            $url = 'https://api-free.deepl.com/v2/translate';
3343832d0abSNetali        } else {
33581931e50SNetali            $url = 'https://api.deepl.com/v2/translate';
3363832d0abSNetali        }
3373832d0abSNetali
33881931e50SNetali        $http = new DokuHTTPClient();
33981931e50SNetali        $raw_response = $http->post($url, $data);
3403832d0abSNetali
3415f8ab21dSNetali        if ($http->status >= 400) {
3425f8ab21dSNetali            // add error messages
3435f8ab21dSNetali            switch ($http->status) {
3445f8ab21dSNetali                case 403:
3455f8ab21dSNetali                    msg($this->getLang('msg_translation_fail_bad_key'), -1);
3465f8ab21dSNetali                    break;
3475f8ab21dSNetali                case 456:
3485f8ab21dSNetali                    msg($this->getLang('msg_translation_fail_quota_exceeded'), -1);
3495f8ab21dSNetali                    break;
3505f8ab21dSNetali                default:
3515f8ab21dSNetali                    msg($this->getLang('msg_translation_fail'), -1);
3525f8ab21dSNetali                    break;
3535f8ab21dSNetali            }
3545f8ab21dSNetali
3553832d0abSNetali            // if any error occurred return an empty string
3565f8ab21dSNetali            return '';
3575f8ab21dSNetali        }
3583832d0abSNetali
3593832d0abSNetali        $json_response = json_decode($raw_response, true);
3603832d0abSNetali        $translated_text = $json_response['translations'][0]['text'];
3613832d0abSNetali
3623832d0abSNetali        $translated_text = $this->remove_ignore_tags($translated_text);
3633832d0abSNetali
3643832d0abSNetali        return $translated_text;
3653832d0abSNetali    }
3663832d0abSNetali
367153e4498SNetali    private function get_push_langs(): array {
368153e4498SNetali        $push_langs = trim($this->getConf('push_langs'));
369153e4498SNetali
370153e4498SNetali        if ($push_langs === '') return array();
371153e4498SNetali
372153e4498SNetali        return explode(' ', $push_langs);
373153e4498SNetali    }
374153e4498SNetali
3750180404cSNetali    private function patch_links($text, $target_lang, $ns): string {
3760180404cSNetali        /*
3770180404cSNetali         * 1. Find links in [[ aa:bb ]] or [[ aa:bb | cc ]]
3780180404cSNetali         * 2. Extract aa:bb
3790180404cSNetali         * 3. Check if lang:aa:bb exists
3800180404cSNetali         * 3.1. --> Yes --> replace
3810180404cSNetali         * 3.2. --> No --> leave it as it is
3820180404cSNetali         */
3833832d0abSNetali
3840180404cSNetali
3850180404cSNetali        /*
3860180404cSNetali         * LINKS
3870180404cSNetali         */
3880180404cSNetali
3894b84d3cfSNetali        preg_match_all('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', $text, $matches, PREG_SET_ORDER);
3900180404cSNetali
3910180404cSNetali        foreach ($matches as $match) {
3920180404cSNetali
3930180404cSNetali            // external link --> skip
394a3a51507SNetali            if (strpos($match[1], '://') !== false) continue;
3950180404cSNetali
396*84cda41fSNetali            // skip interwiki links
397*84cda41fSNetali            if (strpos($match[1], '>') !== false) continue;
398*84cda41fSNetali
399*84cda41fSNetali            // skip windows share links
400*84cda41fSNetali            if (strpos($match[1], '\\\\') !== false) continue;
401*84cda41fSNetali
402*84cda41fSNetali            $resolved_id = trim($match[1]);
4030180404cSNetali
4040180404cSNetali            resolve_pageid($ns, $resolved_id, $exists);
4050180404cSNetali
40653f3766cSNetali            $resolved_id_full = $resolved_id;
4070180404cSNetali
4086663bcb5SNetali            // if the link already points to a target in a language namespace drop it and add the new language namespace
4096663bcb5SNetali            $split_id = explode(':', $resolved_id);
4106663bcb5SNetali            $lang_ns = array_shift($split_id);
4116663bcb5SNetali            if (array_key_exists($lang_ns, $this->langs)) {
4126663bcb5SNetali                $resolved_id = implode(':', $split_id);
4136663bcb5SNetali            }
4146663bcb5SNetali
4150180404cSNetali            $lang_id = $target_lang . ':' . $resolved_id;
4160180404cSNetali
4170180404cSNetali            if (!page_exists($lang_id)) {
41853f3766cSNetali                // Page in target lang does not exist --> replace with absolute ID in case it was a relative ID
41953f3766cSNetali                $new_link = '[[' . $resolved_id_full . $match[2] . $match[3] . ']]';
42053f3766cSNetali            } else {
42153f3766cSNetali                // Page in target lang exists --> replace link
4224b84d3cfSNetali                $new_link = '[[' . $lang_id . $match[2] . $match[3] . ']]';
42353f3766cSNetali            }
4240180404cSNetali
4250180404cSNetali            $text = str_replace($match[0], $new_link, $text);
4260180404cSNetali
4270180404cSNetali        }
4280180404cSNetali
4290180404cSNetali        /*
4300180404cSNetali         * MEDIA
4310180404cSNetali         */
4320180404cSNetali
433*84cda41fSNetali        preg_match_all('/\{\{(([\s\S]*?)(\?[\s\S]*?)?)(\|([\s\S]*?))?}}/', $text, $matches, PREG_SET_ORDER);
4340180404cSNetali
4350180404cSNetali        foreach ($matches as $match) {
4360180404cSNetali
4370180404cSNetali            // external image --> skip
438a3a51507SNetali            if (strpos($match[1], '://') !== false) continue;
439a3a51507SNetali
440a3a51507SNetali            // skip things like {{tag>...}}
441a3a51507SNetali            if (strpos($match[1], '>') !== false) continue;
4420180404cSNetali
443*84cda41fSNetali            // keep alignment
444*84cda41fSNetali            $align_left = "";
445*84cda41fSNetali            $align_right = "";
446*84cda41fSNetali
447*84cda41fSNetali            // align left --> space in front of ID
448*84cda41fSNetali            if (substr($match[1], 0, 1) == " ") $align_left = " ";
449*84cda41fSNetali            // align right --> space behind id
450*84cda41fSNetali            if (substr($match[1], -1) == " ") $align_right = " ";
451*84cda41fSNetali
452*84cda41fSNetali            $resolved_id = trim($match[2]);
453*84cda41fSNetali            $params = trim($match[3]);
4540180404cSNetali
4550180404cSNetali            resolve_mediaid($ns, $resolved_id, $exists);
4560180404cSNetali
45753f3766cSNetali            $resolved_id_full = $resolved_id;
4580180404cSNetali
4596663bcb5SNetali            // if the link already points to a target in a language namespace drop it and add the new language namespace
4606663bcb5SNetali            $split_id = explode(':', $resolved_id);
4616663bcb5SNetali            $lang_ns = array_shift($split_id);
4626663bcb5SNetali            if (array_key_exists($lang_ns, $this->langs)) {
4636663bcb5SNetali                $resolved_id = implode(':', $split_id);
4646663bcb5SNetali            }
4656663bcb5SNetali
4660180404cSNetali            $lang_id = $target_lang . ':' . $resolved_id;
4670180404cSNetali
4680180404cSNetali            $lang_id_fn = mediaFN($lang_id);
4690180404cSNetali
4700180404cSNetali            if (!file_exists($lang_id_fn)) {
47153f3766cSNetali                // media in target lang does not exist --> replace with absolute ID in case it was a relative ID
472*84cda41fSNetali                $new_link = '{{' . $align_left . $resolved_id_full . $params . $align_right . $match[4] . '}}';
47353f3766cSNetali            } else {
47453f3766cSNetali                // media in target lang exists --> replace it
475*84cda41fSNetali                $new_link = '{{' . $align_left . $lang_id . $params . $align_right . $match[4] . '}}';
47653f3766cSNetali            }
4770180404cSNetali
4780180404cSNetali            $text = str_replace($match[0], $new_link, $text);
4790180404cSNetali
4800180404cSNetali        }
4810180404cSNetali
4820180404cSNetali        return $text;
4830180404cSNetali    }
4840180404cSNetali
4850180404cSNetali    private function insert_ignore_tags($text): string {
4860180404cSNetali        // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting
4870180404cSNetali        $text = preg_replace('/<[\s\S]+?>/', '<ignore>${0}</ignore>', $text);
4880180404cSNetali
4891cd781c4SNetali        // prevent deepl from breaking headings
4901cd781c4SNetali        $text = preg_replace('/={1,6}/', '<ignore>${0}</ignore>', $text);
4911cd781c4SNetali
492a3a51507SNetali        // fix for plugins like tag or template
493a3a51507SNetali        $text = preg_replace('/\{\{[\s\w]+?>[\s\S]*?}}/', '<ignore>${0}</ignore>', $text);
4940180404cSNetali
4953b1ff295SNetali        // ignore links in wikitext (outside of dokuwiki-links)
4963b1ff295SNetali        $text = preg_replace('/\S+:\/\/\S+/', '<ignore>${0}</ignore>', $text);
4973b1ff295SNetali
4980180404cSNetali        // ignore link/media ids but translate the text (if existing)
4994b84d3cfSNetali        $text = preg_replace('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', '<ignore>[[${1}${2}${4}</ignore>${5}<ignore>]]</ignore>', $text);
5000180404cSNetali        $text = preg_replace('/\{\{([\s\S]*?)(\?[\s\S]*?)?((\|)([\s\S]*?))?}}/', '<ignore>{{${1}${2}${4}</ignore>${5}<ignore>}}</ignore>', $text);
5010180404cSNetali
5023b1ff295SNetali        // prevent deepl from messing with tables
5033b1ff295SNetali        $text = str_replace("^", "<ignore>^</ignore>", $text);
5043b1ff295SNetali        $text = str_replace("|", "<ignore>|</ignore>", $text);
5053b1ff295SNetali
5060180404cSNetali        // prevent deepl from doing strange things with dokuwiki syntax
5070180404cSNetali        $text = str_replace("''", "<ignore>''</ignore>", $text);
508f37d7ce7SNetali        $text = str_replace("//", "<ignore>//</ignore>", $text);
509f37d7ce7SNetali        $text = str_replace("**", "<ignore>**</ignore>", $text);
510f37d7ce7SNetali        $text = str_replace("__", "<ignore>__</ignore>", $text);
5110180404cSNetali        $text = str_replace("\\\\", "<ignore>\\\\</ignore>", $text);
5120180404cSNetali
51313221d46SNetali        // prevent deepl from messing with smileys
51413221d46SNetali        $smileys = array_keys(getSmileys());
51513221d46SNetali        foreach ($smileys as $smiley) {
51613221d46SNetali            $text = str_replace($smiley, "<ignore>" . $smiley . "</ignore>", $text);
51713221d46SNetali        }
51813221d46SNetali
5190180404cSNetali        // ignore code tags
5200180404cSNetali        $text = preg_replace('/(<php[\s\S]*?>[\s\S]*?<\/php>)/', '<ignore>${1}</ignore>', $text);
521057940f7SNetali        $text = preg_replace('/(<file[\s\S]*?>[\s\S]*?<\/file>)/', '<ignore>${1}</ignore>', $text);
522057940f7SNetali        $text = preg_replace('/(<code[\s\S]*?>[\s\S]*?<\/code>)/', '<ignore>${1}</ignore>', $text);
523057940f7SNetali
5240180404cSNetali        // ignore the expressions from the ignore list
5253832d0abSNetali        $ignored_expressions = explode(':', $this->getConf('ignored_expressions'));
5263832d0abSNetali
5273832d0abSNetali        foreach ($ignored_expressions as $expression) {
5283832d0abSNetali            $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text);
5293832d0abSNetali        }
5303832d0abSNetali
5313832d0abSNetali        return $text;
5323832d0abSNetali    }
5333832d0abSNetali
5343832d0abSNetali    private function remove_ignore_tags($text): string {
5353832d0abSNetali        $ignored_expressions = explode(':', $this->getConf('ignored_expressions'));
5363832d0abSNetali
5373832d0abSNetali        foreach ($ignored_expressions as $expression) {
5383832d0abSNetali            $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text);
5393832d0abSNetali        }
5403832d0abSNetali
5413b1ff295SNetali        // prevent deepl from messing with tables
5423b1ff295SNetali        $text = str_replace("<ignore>^</ignore>", "^", $text);
5433b1ff295SNetali        $text = str_replace("<ignore>|</ignore>", "|", $text);
5443b1ff295SNetali
5450180404cSNetali        $text = str_replace("<ignore>''</ignore>", "''", $text);
546f37d7ce7SNetali        $text = str_replace("<ignore>//</ignore>", "//", $text);
547f37d7ce7SNetali        $text = str_replace("<ignore>**</ignore>", "**", $text);
548f37d7ce7SNetali        $text = str_replace("<ignore>__</ignore>", "__", $text);
5490180404cSNetali        $text = str_replace("<ignore>\\\\</ignore>", "\\\\", $text);
5500180404cSNetali
5513b1ff295SNetali        // ignore links in wikitext (outside of dokuwiki-links)
5523b1ff295SNetali        $text = preg_replace('/<ignore>(\S+:\/\/\S+)<\/ignore>/', '${1}', $text);
5533b1ff295SNetali
5544b84d3cfSNetali        $text = preg_replace('/<ignore>\[\[([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>]]<\/ignore>/', '[[${1}${2}${4}]]', $text);
5554b84d3cfSNetali        $text = preg_replace('/<ignore>\{\{([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>}}<\/ignore>/', '{{${1}${2}${4}}}', $text);
5564b84d3cfSNetali
55713221d46SNetali        // prevent deepl from messing with smileys
55813221d46SNetali        $smileys = array_keys(getSmileys());
55913221d46SNetali        foreach ($smileys as $smiley) {
56013221d46SNetali            $text = str_replace("<ignore>" . $smiley . "</ignore>", $smiley, $text);
56113221d46SNetali        }
56213221d46SNetali
5630180404cSNetali        $text = preg_replace('/<ignore>(<php[\s\S]*?>[\s\S]*?<\/php>)<\/ignore>/', '${1}', $text);
5640180404cSNetali        $text = preg_replace('/<ignore>(<file[\s\S]*?>[\s\S]*?<\/file>)<\/ignore>/', '${1}', $text);
5650180404cSNetali        $text = preg_replace('/<ignore>(<code[\s\S]*?>[\s\S]*?<\/code>)<\/ignore>/', '${1}', $text);
5660180404cSNetali
567a3a51507SNetali        // fix for plugins like tag or template
568a3a51507SNetali        $text = preg_replace('/<ignore>(\{\{[\s\w]+?>[\s\S]*?}})<\/ignore>/', '${1}', $text);
5690180404cSNetali
5701cd781c4SNetali        // prevent deepl from breaking headings
5711cd781c4SNetali        $text = preg_replace('/<ignore>(={1,6})<\/ignore>/','${1}', $text);
5721cd781c4SNetali
5731cd781c4SNetali        // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting
5741cd781c4SNetali        $text = preg_replace('/<ignore>(<[\s\S]+?>)<\/ignore>/', '${1}', $text);
5751cd781c4SNetali
5760180404cSNetali        // restore < and > for example from arrows (-->) in wikitext
5770180404cSNetali        $text = str_replace('&gt;', '>', $text);
5780180404cSNetali        $text = str_replace('&lt;', '<', $text);
5790180404cSNetali
5803b1ff295SNetali        // restore & in wikitext
5813b1ff295SNetali        $text = str_replace('&amp;', '&', $text);
5823b1ff295SNetali
5833832d0abSNetali        return $text;
5843832d0abSNetali    }
5853832d0abSNetali}
5863832d0abSNetali
587