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 16*b33135efSNetali private $langs = array( 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' 43*b33135efSNetali ); 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'); 50*b33135efSNetali $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'pagetpl_load'); 51*b33135efSNetali $controller->register_hook('COMMON_WIKIPAGE_SAVE','AFTER', $this, 'update_glossary'); 523c636ad3SNetali $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button'); 533c636ad3SNetali } 543c636ad3SNetali 550180404cSNetali public function add_menu_button(Doku_Event $event): void { 56153e4498SNetali global $ID; 57bbb1fba9SNetali global $ACT; 58bbb1fba9SNetali 59bbb1fba9SNetali if ($ACT != 'show') return; 60153e4498SNetali 613c636ad3SNetali if ($event->data['view'] != 'page') return; 623c636ad3SNetali 633c636ad3SNetali if (!$this->getConf('show_button')) return; 64153e4498SNetali 65*b33135efSNetali // no translations for the glossary namespace 66*b33135efSNetali if ($this->check_in_glossary_ns()) return; 67*b33135efSNetali 68153e4498SNetali $split_id = explode(':', $ID); 69153e4498SNetali $lang_ns = array_shift($split_id); 70153e4498SNetali // check if we are in a language namespace 71153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 72ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 7399da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> check for push translation 7499da9a08SNetali if (!$this->check_do_push_translate()) return; 7599da9a08SNetali } else { 76153e4498SNetali // in language namespace --> check if we should translate 773c636ad3SNetali if (!$this->check_do_translation(true)) return; 7899da9a08SNetali } 79153e4498SNetali } else { 8099da9a08SNetali // do not show the button if we are not in a language namespace and the default language is in a namespace 8199da9a08SNetali if($this->getConf('default_lang_in_ns')) return; 82*b33135efSNetali // not in language namespace and default language is not in a namespace --> check if we should show the push translate button 83153e4498SNetali if (!$this->check_do_push_translate()) return; 84153e4498SNetali } 853c636ad3SNetali 863c636ad3SNetali array_splice($event->data['items'], -1, 0, [new MenuItem()]); 873832d0abSNetali } 883832d0abSNetali 89153e4498SNetali public function preprocess(Doku_Event $event, $param): void { 903832d0abSNetali global $ID; 913c636ad3SNetali 923c636ad3SNetali // check if action is show or translate 933c636ad3SNetali if ($event->data != 'show' and $event->data != 'translate') return; 943c636ad3SNetali 95*b33135efSNetali // redirect to glossary ns start if glossary ns is called 96*b33135efSNetali if ($this->check_in_glossary_ns() and $event->data == 'show' and $ID == $this->get_glossary_ns()) { 97*b33135efSNetali send_redirect(wl($this->get_glossary_ns() . ':start')); 98*b33135efSNetali } 99*b33135efSNetali 100153e4498SNetali $split_id = explode(':', $ID); 101153e4498SNetali $lang_ns = array_shift($split_id); 102153e4498SNetali // check if we are in a language namespace 103153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 104ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 10599da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> push translate 10699da9a08SNetali $this->push_translate($event); 10799da9a08SNetali } else { 10899da9a08SNetali // in language namespace --> autotrans direct 109153e4498SNetali $this->autotrans_direct($event); 11099da9a08SNetali } 111153e4498SNetali } else { 112153e4498SNetali // not in language namespace --> push translate 113153e4498SNetali $this->push_translate($event); 114153e4498SNetali } 115153e4498SNetali } 116153e4498SNetali 117*b33135efSNetali public function pagetpl_load(Doku_Event $event, $param): void { 118*b33135efSNetali // handle glossary namespace init when we are in it 119*b33135efSNetali if ($this->check_in_glossary_ns()) { 120*b33135efSNetali $this->handle_glossary_init($event); 121*b33135efSNetali return; 122*b33135efSNetali } 123*b33135efSNetali 124*b33135efSNetali $this->autotrans_editor($event); 125*b33135efSNetali } 126*b33135efSNetali 127*b33135efSNetali public function update_glossary(Doku_Event $event, $param): void { 128*b33135efSNetali global $ID; 129*b33135efSNetali // this also checks if the glossary feature is enabled 130*b33135efSNetali if (!$this->check_in_glossary_ns()) return; 131*b33135efSNetali 132*b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 133*b33135efSNetali 134*b33135efSNetali // check if we are in a glossary definition 135*b33135efSNetali if(preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $id_match)) { 136*b33135efSNetali $old_glossary_id = $this->get_glossary_id($id_match[1], $id_match[2]); 137*b33135efSNetali if ($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE) { 138*b33135efSNetali // page deleted --> delete glossary 139*b33135efSNetali if ($old_glossary_id) { 140*b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 141*b33135efSNetali if ($result) { 142*b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 143*b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 144*b33135efSNetali } 145*b33135efSNetali } 146*b33135efSNetali return; 147*b33135efSNetali } 148*b33135efSNetali 149*b33135efSNetali $entries = ''; 150*b33135efSNetali 151*b33135efSNetali // grep entries from definition table 152*b33135efSNetali preg_match_all('/[ \t]*\|(.*?)\|(.*?)\|/', $event->data['newContent'], $matches, PREG_SET_ORDER); 153*b33135efSNetali foreach ($matches as $match) { 154*b33135efSNetali $src = trim($match[1]); 155*b33135efSNetali $target = trim($match[2]); 156*b33135efSNetali if ($src == '' or $target == '') { 157*b33135efSNetali msg($this->getLang('msg_glossary_empty_key'), -1); 158*b33135efSNetali return; 159*b33135efSNetali } 160*b33135efSNetali $entries .= $src . "\t" . $target . "\n"; 161*b33135efSNetali } 162*b33135efSNetali 163*b33135efSNetali if (empty($matches)) { 164*b33135efSNetali // no matches --> delete glossary 165*b33135efSNetali if ($old_glossary_id) { 166*b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 167*b33135efSNetali if ($result) { 168*b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 169*b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 170*b33135efSNetali } 171*b33135efSNetali } 172*b33135efSNetali return; 173*b33135efSNetali } 174*b33135efSNetali 175*b33135efSNetali $new_glossary_id = $this->create_glossary($id_match[1], $id_match[2], $entries); 176*b33135efSNetali 177*b33135efSNetali if ($new_glossary_id) { 178*b33135efSNetali msg($this->getLang('msg_glossary_create_success'), 1); 179*b33135efSNetali $this->set_glossary_id($id_match[1], $id_match[2], $new_glossary_id); 180*b33135efSNetali } else { 181*b33135efSNetali return; 182*b33135efSNetali } 183*b33135efSNetali 184*b33135efSNetali if ($old_glossary_id) $this->delete_glossary($old_glossary_id); 185*b33135efSNetali } 186*b33135efSNetali } 187*b33135efSNetali 188153e4498SNetali private function autotrans_direct(Doku_Event $event): void { 189153e4498SNetali global $ID; 190153e4498SNetali 1913c636ad3SNetali // abort if action is translate and the translate button is disabled 1923c636ad3SNetali if ($event->data == 'translate' and !$this->getConf('show_button')) return; 1933c636ad3SNetali 1943c636ad3SNetali // do nothing on show action when mode is not direct 1953c636ad3SNetali if ($event->data == 'show' and $this->get_mode() != 'direct') return; 1963c636ad3SNetali 1973c636ad3SNetali // allow translation of existing pages is we are in the translate action 1983c636ad3SNetali $allow_existing = ($event->data == 'translate'); 1993c636ad3SNetali 2003c636ad3SNetali // reset action to show 2013c636ad3SNetali $event->data = 'show'; 2023c636ad3SNetali 203153e4498SNetali if (!$this->check_do_translation($allow_existing)) { 204153e4498SNetali return; 205153e4498SNetali } 2063832d0abSNetali 2070180404cSNetali $org_page_info = $this->get_org_page_info(); 2080180404cSNetali $translated_text = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2093832d0abSNetali 210153e4498SNetali if ($translated_text === '') { 211153e4498SNetali return; 212153e4498SNetali } 2133832d0abSNetali 2143832d0abSNetali saveWikiText($ID, $translated_text, 'Automatic translation'); 2153832d0abSNetali 216153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 217153e4498SNetali 2183c636ad3SNetali // reload the page after translation 2193c636ad3SNetali send_redirect(wl($ID)); 2203832d0abSNetali } 2213832d0abSNetali 222*b33135efSNetali private function autotrans_editor(Doku_Event $event): void { 2233832d0abSNetali if ($this->get_mode() != 'editor') return; 2243832d0abSNetali 2253832d0abSNetali if (!$this->check_do_translation()) return; 2263832d0abSNetali 2270180404cSNetali $org_page_info = $this->get_org_page_info(); 2283832d0abSNetali 2290180404cSNetali $event->data['tpl'] = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2303832d0abSNetali } 2313832d0abSNetali 232153e4498SNetali private function push_translate(Doku_Event $event): void { 233153e4498SNetali global $ID; 234153e4498SNetali 235153e4498SNetali // check if action is translate 236153e4498SNetali if ($event->data != 'translate') return; 237153e4498SNetali 238153e4498SNetali // check if button is enabled 239153e4498SNetali if (!$this->getConf('show_button')) { 240153e4498SNetali send_redirect(wl($ID)); 241153e4498SNetali return; 242153e4498SNetali } 243153e4498SNetali 244153e4498SNetali if (!$this->check_do_push_translate()) { 245153e4498SNetali send_redirect(wl($ID)); 246153e4498SNetali return; 247153e4498SNetali } 248153e4498SNetali 249153e4498SNetali // push translate 250153e4498SNetali $push_langs = $this->get_push_langs(); 251153e4498SNetali $org_page_text = rawWiki($ID); 252153e4498SNetali foreach ($push_langs as $lang) { 253153e4498SNetali // skip invalid languages 254153e4498SNetali if (!array_key_exists($lang, $this->langs)) { 255153e4498SNetali msg($this->getLang('msg_translation_fail_invalid_lang') . $lang, -1); 256153e4498SNetali continue; 257153e4498SNetali } 258153e4498SNetali 25999da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 26099da9a08SNetali // if default lang is in ns: replace language namespace in ID 26199da9a08SNetali $split_id = explode(':', $ID); 26299da9a08SNetali array_shift($split_id); 26399da9a08SNetali $lang_id = implode(':', $split_id); 26499da9a08SNetali $lang_id = $lang . ':' . $lang_id; 26599da9a08SNetali } else { 26699da9a08SNetali // if default lang is not in ns: add language namespace to ID 267153e4498SNetali $lang_id = $lang . ':' . $ID; 26899da9a08SNetali } 269153e4498SNetali 270153e4498SNetali // check permissions 271a3a51507SNetali $perm = auth_quickaclcheck($lang_id); 272153e4498SNetali $exists = page_exists($lang_id); 273153e4498SNetali if (($exists and $perm < AUTH_EDIT) or (!$exists and $perm < AUTH_CREATE)) { 274153e4498SNetali msg($this->getLang('msg_translation_fail_no_permissions') . $lang_id, -1); 275153e4498SNetali continue; 276153e4498SNetali } 277153e4498SNetali 2780180404cSNetali $translated_text = $this->deepl_translate($org_page_text, $lang, getNS($ID)); 279153e4498SNetali saveWikiText($lang_id, $translated_text, 'Automatic push translation'); 280153e4498SNetali } 281153e4498SNetali 282153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 283153e4498SNetali 284153e4498SNetali // reload the page after translation to clear the action 285153e4498SNetali send_redirect(wl($ID)); 286153e4498SNetali } 287153e4498SNetali 288*b33135efSNetali private function handle_glossary_init(Doku_Event $event): void { 289*b33135efSNetali global $ID; 290*b33135efSNetali 291*b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 292*b33135efSNetali 293*b33135efSNetali // create glossary landing page 294*b33135efSNetali if ($ID == $glossary_ns . ':start') { 295*b33135efSNetali $landing_page_text = '====== ' . $this->getLang('glossary_landing_heading') . ' ======' . "\n"; 296*b33135efSNetali $landing_page_text .= $this->getLang('glossary_landing_info_msg') . "\n"; 297*b33135efSNetali 298*b33135efSNetali $src_lang = substr($this->get_default_lang(), 0, 2); 299*b33135efSNetali 300*b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 301*b33135efSNetali foreach ($available_glossaries as $glossary) { 302*b33135efSNetali if ($glossary['source_lang'] != $src_lang) continue; 303*b33135efSNetali // generate links to the available glossary pages 304*b33135efSNetali $landing_page_text .= ' * [[.:' . $glossary['source_lang'] . '_' . $glossary['target_lang'] . '|' . strtoupper($glossary['source_lang']) . ' -> ' . strtoupper($glossary['target_lang']) . ']]' . "\n"; 305*b33135efSNetali } 306*b33135efSNetali $event->data['tpl'] = $landing_page_text; 307*b33135efSNetali return; 308*b33135efSNetali } 309*b33135efSNetali 310*b33135efSNetali if (preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $match)) { 311*b33135efSNetali // check if glossaries are supported for this language pair 312*b33135efSNetali if (!$this->check_glossary_supported($match[1], $match[2])) { 313*b33135efSNetali msg($this->getLang('msg_glossary_unsupported'), -1); 314*b33135efSNetali return; 315*b33135efSNetali } 316*b33135efSNetali 317*b33135efSNetali $page_text = '====== ' . $this->getLang('glossary_definition_heading') . ': ' . strtoupper($match[1]) . ' -> ' . strtoupper($match[2]) . ' ======' . "\n"; 318*b33135efSNetali $page_text .= $this->getLang('glossary_definition_help') . "\n\n"; 319*b33135efSNetali $page_text .= '^ ' . strtoupper($match[1]) . ' ^ ' . strtoupper($match[2]) . ' ^' . "\n"; 320*b33135efSNetali 321*b33135efSNetali $event->data['tpl'] = $page_text; 322*b33135efSNetali return; 323*b33135efSNetali } 324*b33135efSNetali } 325*b33135efSNetali 326*b33135efSNetali private function get_glossary_ns(): string { 327*b33135efSNetali return trim(strtolower($this->getConf('glossary_ns'))); 328*b33135efSNetali } 329*b33135efSNetali 3303832d0abSNetali private function get_mode(): string { 3313832d0abSNetali global $ID; 3323832d0abSNetali if ($this->getConf('editor_regex')) { 3333832d0abSNetali if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 3343832d0abSNetali } 3353832d0abSNetali if ($this->getConf('direct_regex')) { 3363832d0abSNetali if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 3373832d0abSNetali } 3383832d0abSNetali return $this->getConf('mode'); 3393832d0abSNetali } 3403832d0abSNetali 3413832d0abSNetali private function get_target_lang(): string { 3423832d0abSNetali global $ID; 3433832d0abSNetali $split_id = explode(':', $ID); 3443832d0abSNetali return array_shift($split_id); 3453832d0abSNetali } 3463832d0abSNetali 347ff327fe6SNetali private function get_default_lang(): string { 348ff327fe6SNetali global $conf; 349ff327fe6SNetali 350ff327fe6SNetali if (empty($conf['lang_before_translation'])) { 351ff327fe6SNetali $default_lang = $conf['lang']; 352ff327fe6SNetali } else { 353ff327fe6SNetali $default_lang = $conf['lang_before_translation']; 354ff327fe6SNetali } 355ff327fe6SNetali 356ff327fe6SNetali return $default_lang; 357ff327fe6SNetali } 358ff327fe6SNetali 3590180404cSNetali private function get_org_page_info(): array { 3603832d0abSNetali global $ID; 3613832d0abSNetali 3623832d0abSNetali $split_id = explode(':', $ID); 3633832d0abSNetali array_shift($split_id); 3643832d0abSNetali $org_id = implode(':', $split_id); 3653832d0abSNetali 36699da9a08SNetali // if default lang is in ns: add default ns in front of org id 36799da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 368ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 36999da9a08SNetali } 37099da9a08SNetali 3710180404cSNetali return array("ns" => getNS($org_id), "text" => rawWiki($org_id)); 3723832d0abSNetali } 3733832d0abSNetali 374*b33135efSNetali private function get_available_glossaries(): array { 375*b33135efSNetali if (!trim($this->getConf('api_key'))) { 376*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 377*b33135efSNetali return array(); 378*b33135efSNetali } 379*b33135efSNetali 380*b33135efSNetali if ($this->getConf('api') == 'free') { 381*b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossary-language-pairs'; 382*b33135efSNetali } else { 383*b33135efSNetali $url = 'https://api.deepl.com/v2/glossary-language-pairs'; 384*b33135efSNetali } 385*b33135efSNetali 386*b33135efSNetali $http = new DokuHTTPClient(); 387*b33135efSNetali 388*b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 389*b33135efSNetali 390*b33135efSNetali $raw_response = $http->get($url); 391*b33135efSNetali 392*b33135efSNetali if ($http->status >= 400) { 393*b33135efSNetali // add error messages 394*b33135efSNetali switch ($http->status) { 395*b33135efSNetali case 403: 396*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 397*b33135efSNetali break; 398*b33135efSNetali default: 399*b33135efSNetali msg($this->getLang('msg_glossary_fetch_fail'), -1); 400*b33135efSNetali break; 401*b33135efSNetali } 402*b33135efSNetali 403*b33135efSNetali // if any error occurred return an empty array 404*b33135efSNetali return array(); 405*b33135efSNetali } 406*b33135efSNetali 407*b33135efSNetali $json_response = json_decode($raw_response, true); 408*b33135efSNetali 409*b33135efSNetali return $json_response['supported_languages']; 410*b33135efSNetali } 411*b33135efSNetali 412*b33135efSNetali private function get_glossary_id($src, $target): string { 413*b33135efSNetali if (!file_exists(DOKU_CONF . 'deepl-glossaries.json')) return ''; 414*b33135efSNetali 415*b33135efSNetali $key = $src . "_" . $target; 416*b33135efSNetali 417*b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 418*b33135efSNetali $content = json_decode($raw_json, true); 419*b33135efSNetali 420*b33135efSNetali if (array_key_exists($key, $content)) { 421*b33135efSNetali return $content[$key]; 422*b33135efSNetali } else { 423*b33135efSNetali return ''; 424*b33135efSNetali } 425*b33135efSNetali } 426*b33135efSNetali 427*b33135efSNetali private function set_glossary_id($src, $target, $glossary_id): void { 428*b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 429*b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 430*b33135efSNetali $content = json_decode($raw_json, true); 431*b33135efSNetali } else { 432*b33135efSNetali $content = array(); 433*b33135efSNetali } 434*b33135efSNetali 435*b33135efSNetali $key = $src . "_" . $target; 436*b33135efSNetali 437*b33135efSNetali $content[$key] = $glossary_id; 438*b33135efSNetali 439*b33135efSNetali $raw_json = json_encode($content); 440*b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 441*b33135efSNetali } 442*b33135efSNetali 443*b33135efSNetali private function unset_glossary_id($src, $target): void { 444*b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 445*b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 446*b33135efSNetali $content = json_decode($raw_json, true); 447*b33135efSNetali } else { 448*b33135efSNetali return; 449*b33135efSNetali } 450*b33135efSNetali 451*b33135efSNetali $key = $src . "_" . $target; 452*b33135efSNetali 453*b33135efSNetali unset($content[$key]); 454*b33135efSNetali 455*b33135efSNetali $raw_json = json_encode($content); 456*b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 457*b33135efSNetali } 458*b33135efSNetali 459*b33135efSNetali private function check_in_glossary_ns(): bool { 460*b33135efSNetali global $ID; 461*b33135efSNetali 462*b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 463*b33135efSNetali 464*b33135efSNetali // check if the glossary namespace is defined 465*b33135efSNetali if (!$glossary_ns) return false; 466*b33135efSNetali 467*b33135efSNetali // check if we are in the glossary namespace 468*b33135efSNetali if (substr($ID, 0, strlen($glossary_ns)) == $glossary_ns) { 469*b33135efSNetali return true; 470*b33135efSNetali } else { 471*b33135efSNetali return false; 472*b33135efSNetali } 473*b33135efSNetali } 474*b33135efSNetali 475*b33135efSNetali private function check_glossary_supported($src, $target): bool { 476*b33135efSNetali if(strlen($src) != 2 or strlen($target) != 2) return false; 477*b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 478*b33135efSNetali foreach ($available_glossaries as $glossary) { 479*b33135efSNetali if ($src == $glossary['source_lang'] and $target == $glossary['target_lang']) return true; 480*b33135efSNetali } 481*b33135efSNetali return false; 482*b33135efSNetali } 483*b33135efSNetali 4843c636ad3SNetali private function check_do_translation($allow_existing = false): bool { 4853832d0abSNetali global $INFO; 4863832d0abSNetali global $ID; 4873832d0abSNetali 4883c636ad3SNetali // only translate if the current page does not exist 4893c636ad3SNetali if ($INFO['exists'] and !$allow_existing) return false; 4903c636ad3SNetali 4913c636ad3SNetali // permission check 4923c636ad3SNetali $perm = auth_quickaclcheck($ID); 4933c636ad3SNetali if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false; 4943c636ad3SNetali 4953832d0abSNetali // skip blacklisted namespaces and pages 4963832d0abSNetali if ($this->getConf('blacklist_regex')) { 4973832d0abSNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 4983832d0abSNetali } 4993832d0abSNetali 5003832d0abSNetali $split_id = explode(':', $ID); 5013832d0abSNetali $lang_ns = array_shift($split_id); 5023832d0abSNetali // only translate if the current page is in a language namespace 5033832d0abSNetali if (!array_key_exists($lang_ns, $this->langs)) return false; 5043832d0abSNetali 5053832d0abSNetali $org_id = implode(':', $split_id); 50699da9a08SNetali 50799da9a08SNetali // if default lang is in ns: add default ns in front of org id 50899da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 509ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 51099da9a08SNetali } 51199da9a08SNetali 512*b33135efSNetali // no translations for the glossary namespace 513*b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 514*b33135efSNetali if ($glossary_ns and substr($org_id, 0, strlen($glossary_ns)) == $glossary_ns) return false; 515*b33135efSNetali 5163832d0abSNetali // check if the original page exists 5173832d0abSNetali if (!page_exists($org_id)) return false; 5183832d0abSNetali 5193832d0abSNetali return true; 5203832d0abSNetali } 5213832d0abSNetali 522153e4498SNetali private function check_do_push_translate(): bool { 523153e4498SNetali global $ID; 52499da9a08SNetali global $INFO; 52599da9a08SNetali 52699da9a08SNetali if (!$INFO['exists']) return false; 52799da9a08SNetali 528a3a51507SNetali // only allow push translation if the user can edit this page 529a3a51507SNetali $perm = auth_quickaclcheck($ID); 530a3a51507SNetali if ($perm < AUTH_EDIT) return false; 531a3a51507SNetali 53299da9a08SNetali // if default language is in namespace: only allow push translation from that namespace 53399da9a08SNetali if($this->getConf('default_lang_in_ns')) { 53499da9a08SNetali $split_id = explode(':', $ID); 53599da9a08SNetali $lang_ns = array_shift($split_id); 53699da9a08SNetali 537ff327fe6SNetali if ($lang_ns !== $this->get_default_lang()) return false; 53899da9a08SNetali } 539153e4498SNetali 540*b33135efSNetali // no translations for the glossary namespace 541*b33135efSNetali if ($this->check_in_glossary_ns()) return false; 542*b33135efSNetali 543153e4498SNetali $push_langs = $this->get_push_langs(); 544153e4498SNetali // push_langs empty --> push_translate disabled --> abort 545153e4498SNetali if (empty($push_langs)) return false; 546153e4498SNetali 547153e4498SNetali // skip blacklisted namespaces and pages 548153e4498SNetali if ($this->getConf('blacklist_regex')) { 549153e4498SNetali // blacklist regex match --> abort 550153e4498SNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 551153e4498SNetali } 552153e4498SNetali 553153e4498SNetali return true; 554153e4498SNetali } 555153e4498SNetali 556*b33135efSNetali private function create_glossary($src, $target, $entries): string { 557*b33135efSNetali if (!trim($this->getConf('api_key'))) { 558*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 559*b33135efSNetali return ''; 560*b33135efSNetali } 561*b33135efSNetali 562*b33135efSNetali if ($this->getConf('api') == 'free') { 563*b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 564*b33135efSNetali } else { 565*b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 566*b33135efSNetali } 567*b33135efSNetali 568*b33135efSNetali $data = array( 569*b33135efSNetali 'name' => 'DokuWiki-Autotranslate-' . $src . '_' . $target, 570*b33135efSNetali 'source_lang' => $src, 571*b33135efSNetali 'target_lang' => $target, 572*b33135efSNetali 'entries' => $entries, 573*b33135efSNetali 'entries_format' => 'tsv' 574*b33135efSNetali ); 575*b33135efSNetali 576*b33135efSNetali $http = new DokuHTTPClient(); 577*b33135efSNetali 578*b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 579*b33135efSNetali 580*b33135efSNetali $raw_response = $http->post($url, $data); 581*b33135efSNetali 582*b33135efSNetali if ($http->status >= 400) { 583*b33135efSNetali // add error messages 584*b33135efSNetali switch ($http->status) { 585*b33135efSNetali case 403: 586*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 587*b33135efSNetali break; 588*b33135efSNetali case 400: 589*b33135efSNetali msg($this->getLang('msg_glossary_content_invalid'), -1); 590*b33135efSNetali break; 591*b33135efSNetali default: 592*b33135efSNetali msg($this->getLang('msg_glossary_create_fail'), -1); 593*b33135efSNetali break; 594*b33135efSNetali } 595*b33135efSNetali 596*b33135efSNetali // if any error occurred return an empty string 597*b33135efSNetali return ''; 598*b33135efSNetali } 599*b33135efSNetali 600*b33135efSNetali $json_response = json_decode($raw_response, true); 601*b33135efSNetali 602*b33135efSNetali return $json_response['glossary_id']; 603*b33135efSNetali } 604*b33135efSNetali 605*b33135efSNetali private function delete_glossary($glossary_id): bool { 606*b33135efSNetali if (!trim($this->getConf('api_key'))) { 607*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 608*b33135efSNetali return false; 609*b33135efSNetali } 610*b33135efSNetali 611*b33135efSNetali if ($this->getConf('api') == 'free') { 612*b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 613*b33135efSNetali } else { 614*b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 615*b33135efSNetali } 616*b33135efSNetali 617*b33135efSNetali $url .= '/' . $glossary_id; 618*b33135efSNetali 619*b33135efSNetali $http = new DokuHTTPClient(); 620*b33135efSNetali 621*b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 622*b33135efSNetali 623*b33135efSNetali $http->sendRequest($url, '', 'DELETE'); 624*b33135efSNetali 625*b33135efSNetali if ($http->status >= 400) { 626*b33135efSNetali // add error messages 627*b33135efSNetali switch ($http->status) { 628*b33135efSNetali case 403: 629*b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 630*b33135efSNetali break; 631*b33135efSNetali default: 632*b33135efSNetali msg($this->getLang('msg_glossary_delete_fail'), -1); 633*b33135efSNetali break; 634*b33135efSNetali } 635*b33135efSNetali 636*b33135efSNetali // if any error occurred return false 637*b33135efSNetali return false; 638*b33135efSNetali } 639*b33135efSNetali 640*b33135efSNetali return true; 641*b33135efSNetali } 642*b33135efSNetali 6430180404cSNetali private function deepl_translate($text, $target_lang, $org_ns): string { 644*b33135efSNetali if (!trim($this->getConf('api_key'))) { 645*b33135efSNetali msg($this->getLang('msg_translation_fail_bad_key'), -1); 646*b33135efSNetali return ''; 647*b33135efSNetali } 6483832d0abSNetali 6490180404cSNetali $text = $this->patch_links($text, $target_lang, $org_ns); 6500180404cSNetali 6513832d0abSNetali $text = $this->insert_ignore_tags($text); 6523832d0abSNetali 653*b33135efSNetali $data = array( 654*b33135efSNetali 'source_lang' => strtoupper(substr($this->get_default_lang(), 0, 2)), // cut of things like "-informal" 6550180404cSNetali 'target_lang' => $this->langs[$target_lang], 6563832d0abSNetali 'tag_handling' => 'xml', 6570180404cSNetali 'ignore_tags' => 'ignore', 6583832d0abSNetali 'text' => $text 659*b33135efSNetali ); 660*b33135efSNetali 661*b33135efSNetali // check if glossaries are enabled 662*b33135efSNetali if ($this->get_glossary_ns()) { 663*b33135efSNetali $src = substr($this->get_default_lang(), 0, 2); 664*b33135efSNetali $target = substr($target_lang, 0, 2); 665*b33135efSNetali $glossary_id = $this->get_glossary_id($src, $target); 666*b33135efSNetali if ($glossary_id) { 667*b33135efSNetali // use glossary if it is defined 668*b33135efSNetali $data['glossary_id'] = $glossary_id; 669*b33135efSNetali } 670*b33135efSNetali } 6713832d0abSNetali 6723832d0abSNetali if ($this->getConf('api') == 'free') { 67381931e50SNetali $url = 'https://api-free.deepl.com/v2/translate'; 6743832d0abSNetali } else { 67581931e50SNetali $url = 'https://api.deepl.com/v2/translate'; 6763832d0abSNetali } 6773832d0abSNetali 67881931e50SNetali $http = new DokuHTTPClient(); 679*b33135efSNetali 680*b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 681*b33135efSNetali 68281931e50SNetali $raw_response = $http->post($url, $data); 6833832d0abSNetali 6845f8ab21dSNetali if ($http->status >= 400) { 6855f8ab21dSNetali // add error messages 6865f8ab21dSNetali switch ($http->status) { 6875f8ab21dSNetali case 403: 6885f8ab21dSNetali msg($this->getLang('msg_translation_fail_bad_key'), -1); 6895f8ab21dSNetali break; 690*b33135efSNetali case 404: 691*b33135efSNetali msg($this->getLang('msg_translation_fail_invalid_glossary'), -1); 692*b33135efSNetali break; 6935f8ab21dSNetali case 456: 6945f8ab21dSNetali msg($this->getLang('msg_translation_fail_quota_exceeded'), -1); 6955f8ab21dSNetali break; 6965f8ab21dSNetali default: 6975f8ab21dSNetali msg($this->getLang('msg_translation_fail'), -1); 6985f8ab21dSNetali break; 6995f8ab21dSNetali } 7005f8ab21dSNetali 7013832d0abSNetali // if any error occurred return an empty string 7025f8ab21dSNetali return ''; 7035f8ab21dSNetali } 7043832d0abSNetali 7053832d0abSNetali $json_response = json_decode($raw_response, true); 7063832d0abSNetali $translated_text = $json_response['translations'][0]['text']; 7073832d0abSNetali 7083832d0abSNetali $translated_text = $this->remove_ignore_tags($translated_text); 7093832d0abSNetali 7103832d0abSNetali return $translated_text; 7113832d0abSNetali } 7123832d0abSNetali 713153e4498SNetali private function get_push_langs(): array { 714153e4498SNetali $push_langs = trim($this->getConf('push_langs')); 715153e4498SNetali 716153e4498SNetali if ($push_langs === '') return array(); 717153e4498SNetali 718153e4498SNetali return explode(' ', $push_langs); 719153e4498SNetali } 720153e4498SNetali 7210180404cSNetali private function patch_links($text, $target_lang, $ns): string { 7220180404cSNetali /* 7230180404cSNetali * 1. Find links in [[ aa:bb ]] or [[ aa:bb | cc ]] 7240180404cSNetali * 2. Extract aa:bb 7250180404cSNetali * 3. Check if lang:aa:bb exists 7260180404cSNetali * 3.1. --> Yes --> replace 7270180404cSNetali * 3.2. --> No --> leave it as it is 7280180404cSNetali */ 7293832d0abSNetali 7300180404cSNetali 7310180404cSNetali /* 7320180404cSNetali * LINKS 7330180404cSNetali */ 7340180404cSNetali 7354b84d3cfSNetali preg_match_all('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', $text, $matches, PREG_SET_ORDER); 7360180404cSNetali 7370180404cSNetali foreach ($matches as $match) { 7380180404cSNetali 7390180404cSNetali // external link --> skip 740a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 7410180404cSNetali 74284cda41fSNetali // skip interwiki links 74384cda41fSNetali if (strpos($match[1], '>') !== false) continue; 74484cda41fSNetali 74584cda41fSNetali // skip windows share links 74684cda41fSNetali if (strpos($match[1], '\\\\') !== false) continue; 74784cda41fSNetali 74884cda41fSNetali $resolved_id = trim($match[1]); 7490180404cSNetali 7500180404cSNetali resolve_pageid($ns, $resolved_id, $exists); 7510180404cSNetali 75253f3766cSNetali $resolved_id_full = $resolved_id; 7530180404cSNetali 7546663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 7556663bcb5SNetali $split_id = explode(':', $resolved_id); 7566663bcb5SNetali $lang_ns = array_shift($split_id); 7576663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 7586663bcb5SNetali $resolved_id = implode(':', $split_id); 7596663bcb5SNetali } 7606663bcb5SNetali 7610180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 7620180404cSNetali 7630180404cSNetali if (!page_exists($lang_id)) { 76453f3766cSNetali // Page in target lang does not exist --> replace with absolute ID in case it was a relative ID 76553f3766cSNetali $new_link = '[[' . $resolved_id_full . $match[2] . $match[3] . ']]'; 76653f3766cSNetali } else { 76753f3766cSNetali // Page in target lang exists --> replace link 7684b84d3cfSNetali $new_link = '[[' . $lang_id . $match[2] . $match[3] . ']]'; 76953f3766cSNetali } 7700180404cSNetali 7710180404cSNetali $text = str_replace($match[0], $new_link, $text); 7720180404cSNetali 7730180404cSNetali } 7740180404cSNetali 7750180404cSNetali /* 7760180404cSNetali * MEDIA 7770180404cSNetali */ 7780180404cSNetali 77984cda41fSNetali preg_match_all('/\{\{(([\s\S]*?)(\?[\s\S]*?)?)(\|([\s\S]*?))?}}/', $text, $matches, PREG_SET_ORDER); 7800180404cSNetali 7810180404cSNetali foreach ($matches as $match) { 7820180404cSNetali 7830180404cSNetali // external image --> skip 784a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 785a3a51507SNetali 786a3a51507SNetali // skip things like {{tag>...}} 787a3a51507SNetali if (strpos($match[1], '>') !== false) continue; 7880180404cSNetali 78984cda41fSNetali // keep alignment 79084cda41fSNetali $align_left = ""; 79184cda41fSNetali $align_right = ""; 79284cda41fSNetali 79384cda41fSNetali // align left --> space in front of ID 79484cda41fSNetali if (substr($match[1], 0, 1) == " ") $align_left = " "; 79584cda41fSNetali // align right --> space behind id 79684cda41fSNetali if (substr($match[1], -1) == " ") $align_right = " "; 79784cda41fSNetali 79884cda41fSNetali $resolved_id = trim($match[2]); 79984cda41fSNetali $params = trim($match[3]); 8000180404cSNetali 8010180404cSNetali resolve_mediaid($ns, $resolved_id, $exists); 8020180404cSNetali 80353f3766cSNetali $resolved_id_full = $resolved_id; 8040180404cSNetali 8056663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 8066663bcb5SNetali $split_id = explode(':', $resolved_id); 8076663bcb5SNetali $lang_ns = array_shift($split_id); 8086663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 8096663bcb5SNetali $resolved_id = implode(':', $split_id); 8106663bcb5SNetali } 8116663bcb5SNetali 8120180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 8130180404cSNetali 8140180404cSNetali $lang_id_fn = mediaFN($lang_id); 8150180404cSNetali 8160180404cSNetali if (!file_exists($lang_id_fn)) { 81753f3766cSNetali // media in target lang does not exist --> replace with absolute ID in case it was a relative ID 81884cda41fSNetali $new_link = '{{' . $align_left . $resolved_id_full . $params . $align_right . $match[4] . '}}'; 81953f3766cSNetali } else { 82053f3766cSNetali // media in target lang exists --> replace it 82184cda41fSNetali $new_link = '{{' . $align_left . $lang_id . $params . $align_right . $match[4] . '}}'; 82253f3766cSNetali } 8230180404cSNetali 8240180404cSNetali $text = str_replace($match[0], $new_link, $text); 8250180404cSNetali 8260180404cSNetali } 8270180404cSNetali 8280180404cSNetali return $text; 8290180404cSNetali } 8300180404cSNetali 8310180404cSNetali private function insert_ignore_tags($text): string { 8320180404cSNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 8330180404cSNetali $text = preg_replace('/<[\s\S]+?>/', '<ignore>${0}</ignore>', $text); 8340180404cSNetali 8351cd781c4SNetali // prevent deepl from breaking headings 8361cd781c4SNetali $text = preg_replace('/={1,6}/', '<ignore>${0}</ignore>', $text); 8371cd781c4SNetali 838a3a51507SNetali // fix for plugins like tag or template 839a3a51507SNetali $text = preg_replace('/\{\{[\s\w]+?>[\s\S]*?}}/', '<ignore>${0}</ignore>', $text); 8400180404cSNetali 8413b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 8423b1ff295SNetali $text = preg_replace('/\S+:\/\/\S+/', '<ignore>${0}</ignore>', $text); 8433b1ff295SNetali 8440180404cSNetali // ignore link/media ids but translate the text (if existing) 8454b84d3cfSNetali $text = preg_replace('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', '<ignore>[[${1}${2}${4}</ignore>${5}<ignore>]]</ignore>', $text); 8460180404cSNetali $text = preg_replace('/\{\{([\s\S]*?)(\?[\s\S]*?)?((\|)([\s\S]*?))?}}/', '<ignore>{{${1}${2}${4}</ignore>${5}<ignore>}}</ignore>', $text); 8470180404cSNetali 8483b1ff295SNetali // prevent deepl from messing with tables 8493b1ff295SNetali $text = str_replace("^", "<ignore>^</ignore>", $text); 8503b1ff295SNetali $text = str_replace("|", "<ignore>|</ignore>", $text); 8513b1ff295SNetali 8520180404cSNetali // prevent deepl from doing strange things with dokuwiki syntax 8530180404cSNetali $text = str_replace("''", "<ignore>''</ignore>", $text); 854f37d7ce7SNetali $text = str_replace("//", "<ignore>//</ignore>", $text); 855f37d7ce7SNetali $text = str_replace("**", "<ignore>**</ignore>", $text); 856f37d7ce7SNetali $text = str_replace("__", "<ignore>__</ignore>", $text); 8570180404cSNetali $text = str_replace("\\\\", "<ignore>\\\\</ignore>", $text); 8580180404cSNetali 85913221d46SNetali // prevent deepl from messing with smileys 86013221d46SNetali $smileys = array_keys(getSmileys()); 86113221d46SNetali foreach ($smileys as $smiley) { 86213221d46SNetali $text = str_replace($smiley, "<ignore>" . $smiley . "</ignore>", $text); 86313221d46SNetali } 86413221d46SNetali 8650180404cSNetali // ignore code tags 8660180404cSNetali $text = preg_replace('/(<php[\s\S]*?>[\s\S]*?<\/php>)/', '<ignore>${1}</ignore>', $text); 867057940f7SNetali $text = preg_replace('/(<file[\s\S]*?>[\s\S]*?<\/file>)/', '<ignore>${1}</ignore>', $text); 868057940f7SNetali $text = preg_replace('/(<code[\s\S]*?>[\s\S]*?<\/code>)/', '<ignore>${1}</ignore>', $text); 869057940f7SNetali 8700180404cSNetali // ignore the expressions from the ignore list 8713832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 8723832d0abSNetali 8733832d0abSNetali foreach ($ignored_expressions as $expression) { 8743832d0abSNetali $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 8753832d0abSNetali } 8763832d0abSNetali 8773832d0abSNetali return $text; 8783832d0abSNetali } 8793832d0abSNetali 8803832d0abSNetali private function remove_ignore_tags($text): string { 8813832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 8823832d0abSNetali 8833832d0abSNetali foreach ($ignored_expressions as $expression) { 8843832d0abSNetali $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 8853832d0abSNetali } 8863832d0abSNetali 8873b1ff295SNetali // prevent deepl from messing with tables 8883b1ff295SNetali $text = str_replace("<ignore>^</ignore>", "^", $text); 8893b1ff295SNetali $text = str_replace("<ignore>|</ignore>", "|", $text); 8903b1ff295SNetali 8910180404cSNetali $text = str_replace("<ignore>''</ignore>", "''", $text); 892f37d7ce7SNetali $text = str_replace("<ignore>//</ignore>", "//", $text); 893f37d7ce7SNetali $text = str_replace("<ignore>**</ignore>", "**", $text); 894f37d7ce7SNetali $text = str_replace("<ignore>__</ignore>", "__", $text); 8950180404cSNetali $text = str_replace("<ignore>\\\\</ignore>", "\\\\", $text); 8960180404cSNetali 8973b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 8983b1ff295SNetali $text = preg_replace('/<ignore>(\S+:\/\/\S+)<\/ignore>/', '${1}', $text); 8993b1ff295SNetali 9004b84d3cfSNetali $text = preg_replace('/<ignore>\[\[([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>]]<\/ignore>/', '[[${1}${2}${4}]]', $text); 9014b84d3cfSNetali $text = preg_replace('/<ignore>\{\{([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>}}<\/ignore>/', '{{${1}${2}${4}}}', $text); 9024b84d3cfSNetali 90313221d46SNetali // prevent deepl from messing with smileys 90413221d46SNetali $smileys = array_keys(getSmileys()); 90513221d46SNetali foreach ($smileys as $smiley) { 90613221d46SNetali $text = str_replace("<ignore>" . $smiley . "</ignore>", $smiley, $text); 90713221d46SNetali } 90813221d46SNetali 9090180404cSNetali $text = preg_replace('/<ignore>(<php[\s\S]*?>[\s\S]*?<\/php>)<\/ignore>/', '${1}', $text); 9100180404cSNetali $text = preg_replace('/<ignore>(<file[\s\S]*?>[\s\S]*?<\/file>)<\/ignore>/', '${1}', $text); 9110180404cSNetali $text = preg_replace('/<ignore>(<code[\s\S]*?>[\s\S]*?<\/code>)<\/ignore>/', '${1}', $text); 9120180404cSNetali 913a3a51507SNetali // fix for plugins like tag or template 914a3a51507SNetali $text = preg_replace('/<ignore>(\{\{[\s\w]+?>[\s\S]*?}})<\/ignore>/', '${1}', $text); 9150180404cSNetali 9161cd781c4SNetali // prevent deepl from breaking headings 9171cd781c4SNetali $text = preg_replace('/<ignore>(={1,6})<\/ignore>/','${1}', $text); 9181cd781c4SNetali 9191cd781c4SNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 9201cd781c4SNetali $text = preg_replace('/<ignore>(<[\s\S]+?>)<\/ignore>/', '${1}', $text); 9211cd781c4SNetali 9220180404cSNetali // restore < and > for example from arrows (-->) in wikitext 9230180404cSNetali $text = str_replace('>', '>', $text); 9240180404cSNetali $text = str_replace('<', '<', $text); 9250180404cSNetali 9263b1ff295SNetali // restore & in wikitext 9273b1ff295SNetali $text = str_replace('&', '&', $text); 9283b1ff295SNetali 9293832d0abSNetali return $text; 9303832d0abSNetali } 9313832d0abSNetali} 9323832d0abSNetali 933