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 16b33135efSNetali 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', 42*8311ddaaSnetali 'uk' => 'UK', 433832d0abSNetali 'zh' => 'ZH' 44b33135efSNetali ); 453832d0abSNetali 463832d0abSNetali /** 473832d0abSNetali * Register its handlers with the DokuWiki's event controller 483832d0abSNetali */ 493832d0abSNetali public function register(Doku_Event_Handler $controller) { 50153e4498SNetali $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'preprocess'); 51b33135efSNetali $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'pagetpl_load'); 52b33135efSNetali $controller->register_hook('COMMON_WIKIPAGE_SAVE','AFTER', $this, 'update_glossary'); 533c636ad3SNetali $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button'); 543c636ad3SNetali } 553c636ad3SNetali 560180404cSNetali public function add_menu_button(Doku_Event $event): void { 57153e4498SNetali global $ID; 58bbb1fba9SNetali global $ACT; 59bbb1fba9SNetali 60bbb1fba9SNetali if ($ACT != 'show') return; 61153e4498SNetali 623c636ad3SNetali if ($event->data['view'] != 'page') return; 633c636ad3SNetali 643c636ad3SNetali if (!$this->getConf('show_button')) return; 65153e4498SNetali 66b33135efSNetali // no translations for the glossary namespace 67b33135efSNetali if ($this->check_in_glossary_ns()) return; 68b33135efSNetali 69153e4498SNetali $split_id = explode(':', $ID); 70153e4498SNetali $lang_ns = array_shift($split_id); 71153e4498SNetali // check if we are in a language namespace 72153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 73ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 7499da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> check for push translation 7599da9a08SNetali if (!$this->check_do_push_translate()) return; 7699da9a08SNetali } else { 77153e4498SNetali // in language namespace --> check if we should translate 783c636ad3SNetali if (!$this->check_do_translation(true)) return; 7999da9a08SNetali } 80153e4498SNetali } else { 8199da9a08SNetali // do not show the button if we are not in a language namespace and the default language is in a namespace 8299da9a08SNetali if($this->getConf('default_lang_in_ns')) return; 83b33135efSNetali // not in language namespace and default language is not in a namespace --> check if we should show the push translate button 84153e4498SNetali if (!$this->check_do_push_translate()) return; 85153e4498SNetali } 863c636ad3SNetali 873c636ad3SNetali array_splice($event->data['items'], -1, 0, [new MenuItem()]); 883832d0abSNetali } 893832d0abSNetali 90153e4498SNetali public function preprocess(Doku_Event $event, $param): void { 913832d0abSNetali global $ID; 923c636ad3SNetali 933c636ad3SNetali // check if action is show or translate 943c636ad3SNetali if ($event->data != 'show' and $event->data != 'translate') return; 953c636ad3SNetali 96b33135efSNetali // redirect to glossary ns start if glossary ns is called 97b33135efSNetali if ($this->check_in_glossary_ns() and $event->data == 'show' and $ID == $this->get_glossary_ns()) { 98b33135efSNetali send_redirect(wl($this->get_glossary_ns() . ':start')); 99b33135efSNetali } 100b33135efSNetali 101153e4498SNetali $split_id = explode(':', $ID); 102153e4498SNetali $lang_ns = array_shift($split_id); 103153e4498SNetali // check if we are in a language namespace 104153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 105ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 10699da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> push translate 10799da9a08SNetali $this->push_translate($event); 10899da9a08SNetali } else { 10999da9a08SNetali // in language namespace --> autotrans direct 110153e4498SNetali $this->autotrans_direct($event); 11199da9a08SNetali } 112153e4498SNetali } else { 113153e4498SNetali // not in language namespace --> push translate 114153e4498SNetali $this->push_translate($event); 115153e4498SNetali } 116153e4498SNetali } 117153e4498SNetali 118b33135efSNetali public function pagetpl_load(Doku_Event $event, $param): void { 119b33135efSNetali // handle glossary namespace init when we are in it 120b33135efSNetali if ($this->check_in_glossary_ns()) { 121b33135efSNetali $this->handle_glossary_init($event); 122b33135efSNetali return; 123b33135efSNetali } 124b33135efSNetali 125b33135efSNetali $this->autotrans_editor($event); 126b33135efSNetali } 127b33135efSNetali 128b33135efSNetali public function update_glossary(Doku_Event $event, $param): void { 129b33135efSNetali global $ID; 130b33135efSNetali // this also checks if the glossary feature is enabled 131b33135efSNetali if (!$this->check_in_glossary_ns()) return; 132b33135efSNetali 133b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 134b33135efSNetali 135b33135efSNetali // check if we are in a glossary definition 136b33135efSNetali if(preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $id_match)) { 137b33135efSNetali $old_glossary_id = $this->get_glossary_id($id_match[1], $id_match[2]); 138b33135efSNetali if ($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE) { 139b33135efSNetali // page deleted --> delete glossary 140b33135efSNetali if ($old_glossary_id) { 141b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 142b33135efSNetali if ($result) { 143b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 144b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 145b33135efSNetali } 146b33135efSNetali } 147b33135efSNetali return; 148b33135efSNetali } 149b33135efSNetali 150b33135efSNetali $entries = ''; 151b33135efSNetali 152b33135efSNetali // grep entries from definition table 153b33135efSNetali preg_match_all('/[ \t]*\|(.*?)\|(.*?)\|/', $event->data['newContent'], $matches, PREG_SET_ORDER); 154b33135efSNetali foreach ($matches as $match) { 155b33135efSNetali $src = trim($match[1]); 156b33135efSNetali $target = trim($match[2]); 157b33135efSNetali if ($src == '' or $target == '') { 158b33135efSNetali msg($this->getLang('msg_glossary_empty_key'), -1); 159b33135efSNetali return; 160b33135efSNetali } 161b33135efSNetali $entries .= $src . "\t" . $target . "\n"; 162b33135efSNetali } 163b33135efSNetali 164b33135efSNetali if (empty($matches)) { 165b33135efSNetali // no matches --> delete glossary 166b33135efSNetali if ($old_glossary_id) { 167b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 168b33135efSNetali if ($result) { 169b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 170b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 171b33135efSNetali } 172b33135efSNetali } 173b33135efSNetali return; 174b33135efSNetali } 175b33135efSNetali 176b33135efSNetali $new_glossary_id = $this->create_glossary($id_match[1], $id_match[2], $entries); 177b33135efSNetali 178b33135efSNetali if ($new_glossary_id) { 179b33135efSNetali msg($this->getLang('msg_glossary_create_success'), 1); 180b33135efSNetali $this->set_glossary_id($id_match[1], $id_match[2], $new_glossary_id); 181b33135efSNetali } else { 182b33135efSNetali return; 183b33135efSNetali } 184b33135efSNetali 185b33135efSNetali if ($old_glossary_id) $this->delete_glossary($old_glossary_id); 186b33135efSNetali } 187b33135efSNetali } 188b33135efSNetali 189153e4498SNetali private function autotrans_direct(Doku_Event $event): void { 190153e4498SNetali global $ID; 191153e4498SNetali 1923c636ad3SNetali // abort if action is translate and the translate button is disabled 1933c636ad3SNetali if ($event->data == 'translate' and !$this->getConf('show_button')) return; 1943c636ad3SNetali 1953c636ad3SNetali // do nothing on show action when mode is not direct 1963c636ad3SNetali if ($event->data == 'show' and $this->get_mode() != 'direct') return; 1973c636ad3SNetali 1983c636ad3SNetali // allow translation of existing pages is we are in the translate action 1993c636ad3SNetali $allow_existing = ($event->data == 'translate'); 2003c636ad3SNetali 2013c636ad3SNetali // reset action to show 2023c636ad3SNetali $event->data = 'show'; 2033c636ad3SNetali 204153e4498SNetali if (!$this->check_do_translation($allow_existing)) { 205153e4498SNetali return; 206153e4498SNetali } 2073832d0abSNetali 2080180404cSNetali $org_page_info = $this->get_org_page_info(); 2090180404cSNetali $translated_text = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2103832d0abSNetali 211153e4498SNetali if ($translated_text === '') { 212153e4498SNetali return; 213153e4498SNetali } 2143832d0abSNetali 2153832d0abSNetali saveWikiText($ID, $translated_text, 'Automatic translation'); 2163832d0abSNetali 217153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 218153e4498SNetali 2193c636ad3SNetali // reload the page after translation 2203c636ad3SNetali send_redirect(wl($ID)); 2213832d0abSNetali } 2223832d0abSNetali 223b33135efSNetali private function autotrans_editor(Doku_Event $event): void { 2243832d0abSNetali if ($this->get_mode() != 'editor') return; 2253832d0abSNetali 2263832d0abSNetali if (!$this->check_do_translation()) return; 2273832d0abSNetali 2280180404cSNetali $org_page_info = $this->get_org_page_info(); 2293832d0abSNetali 2300180404cSNetali $event->data['tpl'] = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2313832d0abSNetali } 2323832d0abSNetali 233153e4498SNetali private function push_translate(Doku_Event $event): void { 234153e4498SNetali global $ID; 235153e4498SNetali 236153e4498SNetali // check if action is translate 237153e4498SNetali if ($event->data != 'translate') return; 238153e4498SNetali 239153e4498SNetali // check if button is enabled 240153e4498SNetali if (!$this->getConf('show_button')) { 241153e4498SNetali send_redirect(wl($ID)); 242153e4498SNetali return; 243153e4498SNetali } 244153e4498SNetali 245153e4498SNetali if (!$this->check_do_push_translate()) { 246153e4498SNetali send_redirect(wl($ID)); 247153e4498SNetali return; 248153e4498SNetali } 249153e4498SNetali 250153e4498SNetali // push translate 251153e4498SNetali $push_langs = $this->get_push_langs(); 252153e4498SNetali $org_page_text = rawWiki($ID); 253153e4498SNetali foreach ($push_langs as $lang) { 254153e4498SNetali // skip invalid languages 255153e4498SNetali if (!array_key_exists($lang, $this->langs)) { 256153e4498SNetali msg($this->getLang('msg_translation_fail_invalid_lang') . $lang, -1); 257153e4498SNetali continue; 258153e4498SNetali } 259153e4498SNetali 26099da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 26199da9a08SNetali // if default lang is in ns: replace language namespace in ID 26299da9a08SNetali $split_id = explode(':', $ID); 26399da9a08SNetali array_shift($split_id); 26499da9a08SNetali $lang_id = implode(':', $split_id); 26599da9a08SNetali $lang_id = $lang . ':' . $lang_id; 26699da9a08SNetali } else { 26799da9a08SNetali // if default lang is not in ns: add language namespace to ID 268153e4498SNetali $lang_id = $lang . ':' . $ID; 26999da9a08SNetali } 270153e4498SNetali 271153e4498SNetali // check permissions 272a3a51507SNetali $perm = auth_quickaclcheck($lang_id); 273153e4498SNetali $exists = page_exists($lang_id); 274153e4498SNetali if (($exists and $perm < AUTH_EDIT) or (!$exists and $perm < AUTH_CREATE)) { 275153e4498SNetali msg($this->getLang('msg_translation_fail_no_permissions') . $lang_id, -1); 276153e4498SNetali continue; 277153e4498SNetali } 278153e4498SNetali 2790180404cSNetali $translated_text = $this->deepl_translate($org_page_text, $lang, getNS($ID)); 280153e4498SNetali saveWikiText($lang_id, $translated_text, 'Automatic push translation'); 281153e4498SNetali } 282153e4498SNetali 283153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 284153e4498SNetali 285153e4498SNetali // reload the page after translation to clear the action 286153e4498SNetali send_redirect(wl($ID)); 287153e4498SNetali } 288153e4498SNetali 289b33135efSNetali private function handle_glossary_init(Doku_Event $event): void { 290b33135efSNetali global $ID; 291b33135efSNetali 292b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 293b33135efSNetali 294b33135efSNetali // create glossary landing page 295b33135efSNetali if ($ID == $glossary_ns . ':start') { 296b33135efSNetali $landing_page_text = '====== ' . $this->getLang('glossary_landing_heading') . ' ======' . "\n"; 297b33135efSNetali $landing_page_text .= $this->getLang('glossary_landing_info_msg') . "\n"; 298b33135efSNetali 299b33135efSNetali $src_lang = substr($this->get_default_lang(), 0, 2); 300b33135efSNetali 301b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 302b33135efSNetali foreach ($available_glossaries as $glossary) { 303b33135efSNetali if ($glossary['source_lang'] != $src_lang) continue; 304b33135efSNetali // generate links to the available glossary pages 305b33135efSNetali $landing_page_text .= ' * [[.:' . $glossary['source_lang'] . '_' . $glossary['target_lang'] . '|' . strtoupper($glossary['source_lang']) . ' -> ' . strtoupper($glossary['target_lang']) . ']]' . "\n"; 306b33135efSNetali } 307b33135efSNetali $event->data['tpl'] = $landing_page_text; 308b33135efSNetali return; 309b33135efSNetali } 310b33135efSNetali 311b33135efSNetali if (preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $match)) { 312b33135efSNetali // check if glossaries are supported for this language pair 313b33135efSNetali if (!$this->check_glossary_supported($match[1], $match[2])) { 314b33135efSNetali msg($this->getLang('msg_glossary_unsupported'), -1); 315b33135efSNetali return; 316b33135efSNetali } 317b33135efSNetali 318b33135efSNetali $page_text = '====== ' . $this->getLang('glossary_definition_heading') . ': ' . strtoupper($match[1]) . ' -> ' . strtoupper($match[2]) . ' ======' . "\n"; 319b33135efSNetali $page_text .= $this->getLang('glossary_definition_help') . "\n\n"; 320b33135efSNetali $page_text .= '^ ' . strtoupper($match[1]) . ' ^ ' . strtoupper($match[2]) . ' ^' . "\n"; 321b33135efSNetali 322b33135efSNetali $event->data['tpl'] = $page_text; 323b33135efSNetali return; 324b33135efSNetali } 325b33135efSNetali } 326b33135efSNetali 327b33135efSNetali private function get_glossary_ns(): string { 328b33135efSNetali return trim(strtolower($this->getConf('glossary_ns'))); 329b33135efSNetali } 330b33135efSNetali 3313832d0abSNetali private function get_mode(): string { 3323832d0abSNetali global $ID; 3333832d0abSNetali if ($this->getConf('editor_regex')) { 3343832d0abSNetali if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 3353832d0abSNetali } 3363832d0abSNetali if ($this->getConf('direct_regex')) { 3373832d0abSNetali if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 3383832d0abSNetali } 3393832d0abSNetali return $this->getConf('mode'); 3403832d0abSNetali } 3413832d0abSNetali 3423832d0abSNetali private function get_target_lang(): string { 3433832d0abSNetali global $ID; 3443832d0abSNetali $split_id = explode(':', $ID); 3453832d0abSNetali return array_shift($split_id); 3463832d0abSNetali } 3473832d0abSNetali 348ff327fe6SNetali private function get_default_lang(): string { 349ff327fe6SNetali global $conf; 350ff327fe6SNetali 351ff327fe6SNetali if (empty($conf['lang_before_translation'])) { 352ff327fe6SNetali $default_lang = $conf['lang']; 353ff327fe6SNetali } else { 354ff327fe6SNetali $default_lang = $conf['lang_before_translation']; 355ff327fe6SNetali } 356ff327fe6SNetali 357ff327fe6SNetali return $default_lang; 358ff327fe6SNetali } 359ff327fe6SNetali 3600180404cSNetali private function get_org_page_info(): array { 3613832d0abSNetali global $ID; 3623832d0abSNetali 3633832d0abSNetali $split_id = explode(':', $ID); 3643832d0abSNetali array_shift($split_id); 3653832d0abSNetali $org_id = implode(':', $split_id); 3663832d0abSNetali 36799da9a08SNetali // if default lang is in ns: add default ns in front of org id 36899da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 369ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 37099da9a08SNetali } 37199da9a08SNetali 3720180404cSNetali return array("ns" => getNS($org_id), "text" => rawWiki($org_id)); 3733832d0abSNetali } 3743832d0abSNetali 375b33135efSNetali private function get_available_glossaries(): array { 376b33135efSNetali if (!trim($this->getConf('api_key'))) { 377b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 378b33135efSNetali return array(); 379b33135efSNetali } 380b33135efSNetali 381b33135efSNetali if ($this->getConf('api') == 'free') { 382b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossary-language-pairs'; 383b33135efSNetali } else { 384b33135efSNetali $url = 'https://api.deepl.com/v2/glossary-language-pairs'; 385b33135efSNetali } 386b33135efSNetali 387b33135efSNetali $http = new DokuHTTPClient(); 388b33135efSNetali 389b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 390b33135efSNetali 391b33135efSNetali $raw_response = $http->get($url); 392b33135efSNetali 393b33135efSNetali if ($http->status >= 400) { 394b33135efSNetali // add error messages 395b33135efSNetali switch ($http->status) { 396b33135efSNetali case 403: 397b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 398b33135efSNetali break; 399b33135efSNetali default: 400b33135efSNetali msg($this->getLang('msg_glossary_fetch_fail'), -1); 401b33135efSNetali break; 402b33135efSNetali } 403b33135efSNetali 404b33135efSNetali // if any error occurred return an empty array 405b33135efSNetali return array(); 406b33135efSNetali } 407b33135efSNetali 408b33135efSNetali $json_response = json_decode($raw_response, true); 409b33135efSNetali 410b33135efSNetali return $json_response['supported_languages']; 411b33135efSNetali } 412b33135efSNetali 413b33135efSNetali private function get_glossary_id($src, $target): string { 414b33135efSNetali if (!file_exists(DOKU_CONF . 'deepl-glossaries.json')) return ''; 415b33135efSNetali 416b33135efSNetali $key = $src . "_" . $target; 417b33135efSNetali 418b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 419b33135efSNetali $content = json_decode($raw_json, true); 420b33135efSNetali 421b33135efSNetali if (array_key_exists($key, $content)) { 422b33135efSNetali return $content[$key]; 423b33135efSNetali } else { 424b33135efSNetali return ''; 425b33135efSNetali } 426b33135efSNetali } 427b33135efSNetali 428b33135efSNetali private function set_glossary_id($src, $target, $glossary_id): void { 429b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 430b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 431b33135efSNetali $content = json_decode($raw_json, true); 432b33135efSNetali } else { 433b33135efSNetali $content = array(); 434b33135efSNetali } 435b33135efSNetali 436b33135efSNetali $key = $src . "_" . $target; 437b33135efSNetali 438b33135efSNetali $content[$key] = $glossary_id; 439b33135efSNetali 440b33135efSNetali $raw_json = json_encode($content); 441b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 442b33135efSNetali } 443b33135efSNetali 444b33135efSNetali private function unset_glossary_id($src, $target): void { 445b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 446b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 447b33135efSNetali $content = json_decode($raw_json, true); 448b33135efSNetali } else { 449b33135efSNetali return; 450b33135efSNetali } 451b33135efSNetali 452b33135efSNetali $key = $src . "_" . $target; 453b33135efSNetali 454b33135efSNetali unset($content[$key]); 455b33135efSNetali 456b33135efSNetali $raw_json = json_encode($content); 457b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 458b33135efSNetali } 459b33135efSNetali 460b33135efSNetali private function check_in_glossary_ns(): bool { 461b33135efSNetali global $ID; 462b33135efSNetali 463b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 464b33135efSNetali 465b33135efSNetali // check if the glossary namespace is defined 466b33135efSNetali if (!$glossary_ns) return false; 467b33135efSNetali 468b33135efSNetali // check if we are in the glossary namespace 469b33135efSNetali if (substr($ID, 0, strlen($glossary_ns)) == $glossary_ns) { 470b33135efSNetali return true; 471b33135efSNetali } else { 472b33135efSNetali return false; 473b33135efSNetali } 474b33135efSNetali } 475b33135efSNetali 476b33135efSNetali private function check_glossary_supported($src, $target): bool { 477b33135efSNetali if(strlen($src) != 2 or strlen($target) != 2) return false; 478b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 479b33135efSNetali foreach ($available_glossaries as $glossary) { 480b33135efSNetali if ($src == $glossary['source_lang'] and $target == $glossary['target_lang']) return true; 481b33135efSNetali } 482b33135efSNetali return false; 483b33135efSNetali } 484b33135efSNetali 4853c636ad3SNetali private function check_do_translation($allow_existing = false): bool { 4863832d0abSNetali global $INFO; 4873832d0abSNetali global $ID; 4883832d0abSNetali 4893c636ad3SNetali // only translate if the current page does not exist 4903c636ad3SNetali if ($INFO['exists'] and !$allow_existing) return false; 4913c636ad3SNetali 4923c636ad3SNetali // permission check 4933c636ad3SNetali $perm = auth_quickaclcheck($ID); 4943c636ad3SNetali if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false; 4953c636ad3SNetali 4963832d0abSNetali // skip blacklisted namespaces and pages 4973832d0abSNetali if ($this->getConf('blacklist_regex')) { 4983832d0abSNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 4993832d0abSNetali } 5003832d0abSNetali 5013832d0abSNetali $split_id = explode(':', $ID); 5023832d0abSNetali $lang_ns = array_shift($split_id); 5033832d0abSNetali // only translate if the current page is in a language namespace 5043832d0abSNetali if (!array_key_exists($lang_ns, $this->langs)) return false; 5053832d0abSNetali 5063832d0abSNetali $org_id = implode(':', $split_id); 50799da9a08SNetali 50899da9a08SNetali // if default lang is in ns: add default ns in front of org id 50999da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 510ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 51199da9a08SNetali } 51299da9a08SNetali 513b33135efSNetali // no translations for the glossary namespace 514b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 515b33135efSNetali if ($glossary_ns and substr($org_id, 0, strlen($glossary_ns)) == $glossary_ns) return false; 516b33135efSNetali 5173832d0abSNetali // check if the original page exists 5183832d0abSNetali if (!page_exists($org_id)) return false; 5193832d0abSNetali 5203832d0abSNetali return true; 5213832d0abSNetali } 5223832d0abSNetali 523153e4498SNetali private function check_do_push_translate(): bool { 524153e4498SNetali global $ID; 52599da9a08SNetali global $INFO; 52699da9a08SNetali 52799da9a08SNetali if (!$INFO['exists']) return false; 52899da9a08SNetali 529a3a51507SNetali // only allow push translation if the user can edit this page 530a3a51507SNetali $perm = auth_quickaclcheck($ID); 531a3a51507SNetali if ($perm < AUTH_EDIT) return false; 532a3a51507SNetali 53399da9a08SNetali // if default language is in namespace: only allow push translation from that namespace 53499da9a08SNetali if($this->getConf('default_lang_in_ns')) { 53599da9a08SNetali $split_id = explode(':', $ID); 53699da9a08SNetali $lang_ns = array_shift($split_id); 53799da9a08SNetali 538ff327fe6SNetali if ($lang_ns !== $this->get_default_lang()) return false; 53999da9a08SNetali } 540153e4498SNetali 541b33135efSNetali // no translations for the glossary namespace 542b33135efSNetali if ($this->check_in_glossary_ns()) return false; 543b33135efSNetali 544153e4498SNetali $push_langs = $this->get_push_langs(); 545153e4498SNetali // push_langs empty --> push_translate disabled --> abort 546153e4498SNetali if (empty($push_langs)) return false; 547153e4498SNetali 548153e4498SNetali // skip blacklisted namespaces and pages 549153e4498SNetali if ($this->getConf('blacklist_regex')) { 550153e4498SNetali // blacklist regex match --> abort 551153e4498SNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 552153e4498SNetali } 553153e4498SNetali 554153e4498SNetali return true; 555153e4498SNetali } 556153e4498SNetali 557b33135efSNetali private function create_glossary($src, $target, $entries): string { 558b33135efSNetali if (!trim($this->getConf('api_key'))) { 559b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 560b33135efSNetali return ''; 561b33135efSNetali } 562b33135efSNetali 563b33135efSNetali if ($this->getConf('api') == 'free') { 564b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 565b33135efSNetali } else { 566b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 567b33135efSNetali } 568b33135efSNetali 569b33135efSNetali $data = array( 570b33135efSNetali 'name' => 'DokuWiki-Autotranslate-' . $src . '_' . $target, 571b33135efSNetali 'source_lang' => $src, 572b33135efSNetali 'target_lang' => $target, 573b33135efSNetali 'entries' => $entries, 574b33135efSNetali 'entries_format' => 'tsv' 575b33135efSNetali ); 576b33135efSNetali 577b33135efSNetali $http = new DokuHTTPClient(); 578b33135efSNetali 579b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 580b33135efSNetali 581b33135efSNetali $raw_response = $http->post($url, $data); 582b33135efSNetali 583b33135efSNetali if ($http->status >= 400) { 584b33135efSNetali // add error messages 585b33135efSNetali switch ($http->status) { 586b33135efSNetali case 403: 587b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 588b33135efSNetali break; 589b33135efSNetali case 400: 590b33135efSNetali msg($this->getLang('msg_glossary_content_invalid'), -1); 591b33135efSNetali break; 592b33135efSNetali default: 593b33135efSNetali msg($this->getLang('msg_glossary_create_fail'), -1); 594b33135efSNetali break; 595b33135efSNetali } 596b33135efSNetali 597b33135efSNetali // if any error occurred return an empty string 598b33135efSNetali return ''; 599b33135efSNetali } 600b33135efSNetali 601b33135efSNetali $json_response = json_decode($raw_response, true); 602b33135efSNetali 603b33135efSNetali return $json_response['glossary_id']; 604b33135efSNetali } 605b33135efSNetali 606b33135efSNetali private function delete_glossary($glossary_id): bool { 607b33135efSNetali if (!trim($this->getConf('api_key'))) { 608b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 609b33135efSNetali return false; 610b33135efSNetali } 611b33135efSNetali 612b33135efSNetali if ($this->getConf('api') == 'free') { 613b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 614b33135efSNetali } else { 615b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 616b33135efSNetali } 617b33135efSNetali 618b33135efSNetali $url .= '/' . $glossary_id; 619b33135efSNetali 620b33135efSNetali $http = new DokuHTTPClient(); 621b33135efSNetali 622b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 623b33135efSNetali 624b33135efSNetali $http->sendRequest($url, '', 'DELETE'); 625b33135efSNetali 626b33135efSNetali if ($http->status >= 400) { 627b33135efSNetali // add error messages 628b33135efSNetali switch ($http->status) { 629b33135efSNetali case 403: 630b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 631b33135efSNetali break; 632b33135efSNetali default: 633b33135efSNetali msg($this->getLang('msg_glossary_delete_fail'), -1); 634b33135efSNetali break; 635b33135efSNetali } 636b33135efSNetali 637b33135efSNetali // if any error occurred return false 638b33135efSNetali return false; 639b33135efSNetali } 640b33135efSNetali 641b33135efSNetali return true; 642b33135efSNetali } 643b33135efSNetali 6440180404cSNetali private function deepl_translate($text, $target_lang, $org_ns): string { 645b33135efSNetali if (!trim($this->getConf('api_key'))) { 646b33135efSNetali msg($this->getLang('msg_translation_fail_bad_key'), -1); 647b33135efSNetali return ''; 648b33135efSNetali } 6493832d0abSNetali 6500180404cSNetali $text = $this->patch_links($text, $target_lang, $org_ns); 6510180404cSNetali 6523832d0abSNetali $text = $this->insert_ignore_tags($text); 6533832d0abSNetali 654b33135efSNetali $data = array( 655b33135efSNetali 'source_lang' => strtoupper(substr($this->get_default_lang(), 0, 2)), // cut of things like "-informal" 6560180404cSNetali 'target_lang' => $this->langs[$target_lang], 6573832d0abSNetali 'tag_handling' => 'xml', 6580180404cSNetali 'ignore_tags' => 'ignore', 6593832d0abSNetali 'text' => $text 660b33135efSNetali ); 661b33135efSNetali 662b33135efSNetali // check if glossaries are enabled 663b33135efSNetali if ($this->get_glossary_ns()) { 664b33135efSNetali $src = substr($this->get_default_lang(), 0, 2); 665b33135efSNetali $target = substr($target_lang, 0, 2); 666b33135efSNetali $glossary_id = $this->get_glossary_id($src, $target); 667b33135efSNetali if ($glossary_id) { 668b33135efSNetali // use glossary if it is defined 669b33135efSNetali $data['glossary_id'] = $glossary_id; 670b33135efSNetali } 671b33135efSNetali } 6723832d0abSNetali 6733832d0abSNetali if ($this->getConf('api') == 'free') { 67481931e50SNetali $url = 'https://api-free.deepl.com/v2/translate'; 6753832d0abSNetali } else { 67681931e50SNetali $url = 'https://api.deepl.com/v2/translate'; 6773832d0abSNetali } 6783832d0abSNetali 67981931e50SNetali $http = new DokuHTTPClient(); 680b33135efSNetali 681b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 682b33135efSNetali 68381931e50SNetali $raw_response = $http->post($url, $data); 6843832d0abSNetali 6855f8ab21dSNetali if ($http->status >= 400) { 6865f8ab21dSNetali // add error messages 6875f8ab21dSNetali switch ($http->status) { 6885f8ab21dSNetali case 403: 6895f8ab21dSNetali msg($this->getLang('msg_translation_fail_bad_key'), -1); 6905f8ab21dSNetali break; 691b33135efSNetali case 404: 692b33135efSNetali msg($this->getLang('msg_translation_fail_invalid_glossary'), -1); 693b33135efSNetali break; 6945f8ab21dSNetali case 456: 6955f8ab21dSNetali msg($this->getLang('msg_translation_fail_quota_exceeded'), -1); 6965f8ab21dSNetali break; 6975f8ab21dSNetali default: 6985f8ab21dSNetali msg($this->getLang('msg_translation_fail'), -1); 6995f8ab21dSNetali break; 7005f8ab21dSNetali } 7015f8ab21dSNetali 7023832d0abSNetali // if any error occurred return an empty string 7035f8ab21dSNetali return ''; 7045f8ab21dSNetali } 7053832d0abSNetali 7063832d0abSNetali $json_response = json_decode($raw_response, true); 7073832d0abSNetali $translated_text = $json_response['translations'][0]['text']; 7083832d0abSNetali 7093832d0abSNetali $translated_text = $this->remove_ignore_tags($translated_text); 7103832d0abSNetali 7113832d0abSNetali return $translated_text; 7123832d0abSNetali } 7133832d0abSNetali 714153e4498SNetali private function get_push_langs(): array { 715153e4498SNetali $push_langs = trim($this->getConf('push_langs')); 716153e4498SNetali 717153e4498SNetali if ($push_langs === '') return array(); 718153e4498SNetali 719153e4498SNetali return explode(' ', $push_langs); 720153e4498SNetali } 721153e4498SNetali 7220180404cSNetali private function patch_links($text, $target_lang, $ns): string { 7230180404cSNetali /* 7240180404cSNetali * 1. Find links in [[ aa:bb ]] or [[ aa:bb | cc ]] 7250180404cSNetali * 2. Extract aa:bb 7260180404cSNetali * 3. Check if lang:aa:bb exists 7270180404cSNetali * 3.1. --> Yes --> replace 7280180404cSNetali * 3.2. --> No --> leave it as it is 7290180404cSNetali */ 7303832d0abSNetali 7310180404cSNetali 7320180404cSNetali /* 7330180404cSNetali * LINKS 7340180404cSNetali */ 7350180404cSNetali 7364b84d3cfSNetali preg_match_all('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', $text, $matches, PREG_SET_ORDER); 7370180404cSNetali 7380180404cSNetali foreach ($matches as $match) { 7390180404cSNetali 7400180404cSNetali // external link --> skip 741a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 7420180404cSNetali 74384cda41fSNetali // skip interwiki links 74484cda41fSNetali if (strpos($match[1], '>') !== false) continue; 74584cda41fSNetali 7462a12605eSnetali // skip mail addresses 7472a12605eSnetali if (strpos($match[1], '@') !== false) continue; 7482a12605eSnetali 74984cda41fSNetali // skip windows share links 75084cda41fSNetali if (strpos($match[1], '\\\\') !== false) continue; 75184cda41fSNetali 75284cda41fSNetali $resolved_id = trim($match[1]); 7530180404cSNetali 7540180404cSNetali resolve_pageid($ns, $resolved_id, $exists); 7550180404cSNetali 75653f3766cSNetali $resolved_id_full = $resolved_id; 7570180404cSNetali 7586663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 7596663bcb5SNetali $split_id = explode(':', $resolved_id); 7606663bcb5SNetali $lang_ns = array_shift($split_id); 7616663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 7626663bcb5SNetali $resolved_id = implode(':', $split_id); 7636663bcb5SNetali } 7646663bcb5SNetali 7650180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 7660180404cSNetali 7670180404cSNetali if (!page_exists($lang_id)) { 76853f3766cSNetali // Page in target lang does not exist --> replace with absolute ID in case it was a relative ID 76953f3766cSNetali $new_link = '[[' . $resolved_id_full . $match[2] . $match[3] . ']]'; 77053f3766cSNetali } else { 77153f3766cSNetali // Page in target lang exists --> replace link 7724b84d3cfSNetali $new_link = '[[' . $lang_id . $match[2] . $match[3] . ']]'; 77353f3766cSNetali } 7740180404cSNetali 7750180404cSNetali $text = str_replace($match[0], $new_link, $text); 7760180404cSNetali 7770180404cSNetali } 7780180404cSNetali 7790180404cSNetali /* 7800180404cSNetali * MEDIA 7810180404cSNetali */ 7820180404cSNetali 78384cda41fSNetali preg_match_all('/\{\{(([\s\S]*?)(\?[\s\S]*?)?)(\|([\s\S]*?))?}}/', $text, $matches, PREG_SET_ORDER); 7840180404cSNetali 7850180404cSNetali foreach ($matches as $match) { 7860180404cSNetali 7870180404cSNetali // external image --> skip 788a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 789a3a51507SNetali 790a3a51507SNetali // skip things like {{tag>...}} 791a3a51507SNetali if (strpos($match[1], '>') !== false) continue; 7920180404cSNetali 79384cda41fSNetali // keep alignment 79484cda41fSNetali $align_left = ""; 79584cda41fSNetali $align_right = ""; 79684cda41fSNetali 79784cda41fSNetali // align left --> space in front of ID 79884cda41fSNetali if (substr($match[1], 0, 1) == " ") $align_left = " "; 79984cda41fSNetali // align right --> space behind id 80084cda41fSNetali if (substr($match[1], -1) == " ") $align_right = " "; 80184cda41fSNetali 80284cda41fSNetali $resolved_id = trim($match[2]); 80384cda41fSNetali $params = trim($match[3]); 8040180404cSNetali 8050180404cSNetali resolve_mediaid($ns, $resolved_id, $exists); 8060180404cSNetali 80753f3766cSNetali $resolved_id_full = $resolved_id; 8080180404cSNetali 8096663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 8106663bcb5SNetali $split_id = explode(':', $resolved_id); 8116663bcb5SNetali $lang_ns = array_shift($split_id); 8126663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 8136663bcb5SNetali $resolved_id = implode(':', $split_id); 8146663bcb5SNetali } 8156663bcb5SNetali 8160180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 8170180404cSNetali 8180180404cSNetali $lang_id_fn = mediaFN($lang_id); 8190180404cSNetali 8200180404cSNetali if (!file_exists($lang_id_fn)) { 82153f3766cSNetali // media in target lang does not exist --> replace with absolute ID in case it was a relative ID 82284cda41fSNetali $new_link = '{{' . $align_left . $resolved_id_full . $params . $align_right . $match[4] . '}}'; 82353f3766cSNetali } else { 82453f3766cSNetali // media in target lang exists --> replace it 82584cda41fSNetali $new_link = '{{' . $align_left . $lang_id . $params . $align_right . $match[4] . '}}'; 82653f3766cSNetali } 8270180404cSNetali 8280180404cSNetali $text = str_replace($match[0], $new_link, $text); 8290180404cSNetali 8300180404cSNetali } 8310180404cSNetali 8320180404cSNetali return $text; 8330180404cSNetali } 8340180404cSNetali 8350180404cSNetali private function insert_ignore_tags($text): string { 8360180404cSNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 8370180404cSNetali $text = preg_replace('/<[\s\S]+?>/', '<ignore>${0}</ignore>', $text); 8380180404cSNetali 8391cd781c4SNetali // prevent deepl from breaking headings 8401cd781c4SNetali $text = preg_replace('/={1,6}/', '<ignore>${0}</ignore>', $text); 8411cd781c4SNetali 84243d62a6bSnetali // prevent deepl from messing with nocache-instructions 84343d62a6bSnetali $text = str_replace("~~NOCACHE~~", "<ignore>~~NOCACHE~~</ignore>", $text); 84443d62a6bSnetali 845a3a51507SNetali // fix for plugins like tag or template 846a3a51507SNetali $text = preg_replace('/\{\{[\s\w]+?>[\s\S]*?}}/', '<ignore>${0}</ignore>', $text); 8470180404cSNetali 8483b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 8493b1ff295SNetali $text = preg_replace('/\S+:\/\/\S+/', '<ignore>${0}</ignore>', $text); 8503b1ff295SNetali 8510180404cSNetali // ignore link/media ids but translate the text (if existing) 8524b84d3cfSNetali $text = preg_replace('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', '<ignore>[[${1}${2}${4}</ignore>${5}<ignore>]]</ignore>', $text); 8530180404cSNetali $text = preg_replace('/\{\{([\s\S]*?)(\?[\s\S]*?)?((\|)([\s\S]*?))?}}/', '<ignore>{{${1}${2}${4}</ignore>${5}<ignore>}}</ignore>', $text); 8540180404cSNetali 8553b1ff295SNetali // prevent deepl from messing with tables 8563b1ff295SNetali $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8577c99a9b0Sextrasec $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8587c99a9b0Sextrasec $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8597c99a9b0Sextrasec $text = str_replace("^ ", "<ignore>^ </ignore>", $text); 8607c99a9b0Sextrasec $text = str_replace(" ^", "<ignore> ^</ignore>", $text); 8617c99a9b0Sextrasec $text = str_replace("^", "<ignore>^</ignore>", $text); 8627c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 8637c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 8647c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 8657c99a9b0Sextrasec $text = str_replace("| ", "<ignore>| </ignore>", $text); 8667c99a9b0Sextrasec $text = str_replace(" |", "<ignore> |</ignore>", $text); 8673b1ff295SNetali $text = str_replace("|", "<ignore>|</ignore>", $text); 8683b1ff295SNetali 8690180404cSNetali // prevent deepl from doing strange things with dokuwiki syntax 8706301ba6eSNetali // if a full line is formatted, we have to double-ignore for some reason 8716301ba6eSNetali $text = str_replace("''", "<ignore><ignore>''</ignore></ignore>", $text); 8726301ba6eSNetali $text = str_replace("//", "<ignore><ignore>//</ignore></ignore>", $text); 8736301ba6eSNetali $text = str_replace("**", "<ignore><ignore>**</ignore></ignore>", $text); 8746301ba6eSNetali $text = str_replace("__", "<ignore><ignore>__</ignore></ignore>", $text); 8756301ba6eSNetali $text = str_replace("\\\\", "<ignore><ignore>\\\\</ignore></ignore>", $text); 8760180404cSNetali 87713221d46SNetali // prevent deepl from messing with smileys 87813221d46SNetali $smileys = array_keys(getSmileys()); 87913221d46SNetali foreach ($smileys as $smiley) { 88013221d46SNetali $text = str_replace($smiley, "<ignore>" . $smiley . "</ignore>", $text); 88113221d46SNetali } 88213221d46SNetali 8830180404cSNetali // ignore code tags 8840180404cSNetali $text = preg_replace('/(<php[\s\S]*?>[\s\S]*?<\/php>)/', '<ignore>${1}</ignore>', $text); 885057940f7SNetali $text = preg_replace('/(<file[\s\S]*?>[\s\S]*?<\/file>)/', '<ignore>${1}</ignore>', $text); 886057940f7SNetali $text = preg_replace('/(<code[\s\S]*?>[\s\S]*?<\/code>)/', '<ignore>${1}</ignore>', $text); 887057940f7SNetali 8880180404cSNetali // ignore the expressions from the ignore list 8893832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 8903832d0abSNetali 8913832d0abSNetali foreach ($ignored_expressions as $expression) { 8923832d0abSNetali $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 8933832d0abSNetali } 8943832d0abSNetali 8953832d0abSNetali return $text; 8963832d0abSNetali } 8973832d0abSNetali 8983832d0abSNetali private function remove_ignore_tags($text): string { 8993832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 9003832d0abSNetali 9013832d0abSNetali foreach ($ignored_expressions as $expression) { 9023832d0abSNetali $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 9033832d0abSNetali } 9043832d0abSNetali 90543d62a6bSnetali // prevent deepl from messing with nocache-instructions 90643d62a6bSnetali $text = str_replace("<ignore>~~NOCACHE~~</ignore>", "~~NOCACHE~~", $text); 90743d62a6bSnetali 9083b1ff295SNetali // prevent deepl from messing with tables 9093b1ff295SNetali $text = str_replace("<ignore>^</ignore>", "^", $text); 9107c99a9b0Sextrasec $text = str_replace("<ignore>^ </ignore>", "^ ", $text); 9117c99a9b0Sextrasec $text = str_replace("<ignore> ^</ignore>", " ^", $text); 9127c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9137c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9147c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9157c99a9b0Sextrasec $text = str_replace("<ignore>|</ignore>", "|", $text); 9167c99a9b0Sextrasec $text = str_replace("<ignore>| </ignore>", "| ", $text); 9177c99a9b0Sextrasec $text = str_replace("<ignore> |</ignore>", " |", $text); 9187c99a9b0Sextrasec $text = str_replace("<ignore> | </ignore>", " | ", $text); 9197c99a9b0Sextrasec $text = str_replace("<ignore> | </ignore>", " | ", $text); 9203b1ff295SNetali $text = str_replace("<ignore> | </ignore>", " | ", $text); 9213b1ff295SNetali 9226301ba6eSNetali $text = str_replace("<ignore><ignore>''</ignore></ignore>", "''", $text); 9236301ba6eSNetali $text = str_replace("<ignore><ignore>//</ignore></ignore>", "//", $text); 9246301ba6eSNetali $text = str_replace("<ignore><ignore>**</ignore></ignore>", "**", $text); 9256301ba6eSNetali $text = str_replace("<ignore><ignore>__</ignore></ignore>", "__", $text); 9266301ba6eSNetali $text = str_replace("<ignore><ignore>\\\\</ignore></ignore>", "\\\\", $text); 9270180404cSNetali 9283b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 9293b1ff295SNetali $text = preg_replace('/<ignore>(\S+:\/\/\S+)<\/ignore>/', '${1}', $text); 9303b1ff295SNetali 9314b84d3cfSNetali $text = preg_replace('/<ignore>\[\[([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>]]<\/ignore>/', '[[${1}${2}${4}]]', $text); 9324b84d3cfSNetali $text = preg_replace('/<ignore>\{\{([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>}}<\/ignore>/', '{{${1}${2}${4}}}', $text); 9334b84d3cfSNetali 93413221d46SNetali // prevent deepl from messing with smileys 93513221d46SNetali $smileys = array_keys(getSmileys()); 93613221d46SNetali foreach ($smileys as $smiley) { 93713221d46SNetali $text = str_replace("<ignore>" . $smiley . "</ignore>", $smiley, $text); 93813221d46SNetali } 93913221d46SNetali 9400180404cSNetali $text = preg_replace('/<ignore>(<php[\s\S]*?>[\s\S]*?<\/php>)<\/ignore>/', '${1}', $text); 9410180404cSNetali $text = preg_replace('/<ignore>(<file[\s\S]*?>[\s\S]*?<\/file>)<\/ignore>/', '${1}', $text); 9420180404cSNetali $text = preg_replace('/<ignore>(<code[\s\S]*?>[\s\S]*?<\/code>)<\/ignore>/', '${1}', $text); 9430180404cSNetali 944a3a51507SNetali // fix for plugins like tag or template 945a3a51507SNetali $text = preg_replace('/<ignore>(\{\{[\s\w]+?>[\s\S]*?}})<\/ignore>/', '${1}', $text); 9460180404cSNetali 9471cd781c4SNetali // prevent deepl from breaking headings 9481cd781c4SNetali $text = preg_replace('/<ignore>(={1,6})<\/ignore>/','${1}', $text); 9491cd781c4SNetali 9501cd781c4SNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 9511cd781c4SNetali $text = preg_replace('/<ignore>(<[\s\S]+?>)<\/ignore>/', '${1}', $text); 9521cd781c4SNetali 9530180404cSNetali // restore < and > for example from arrows (-->) in wikitext 9540180404cSNetali $text = str_replace('>', '>', $text); 9550180404cSNetali $text = str_replace('<', '<', $text); 9560180404cSNetali 9573b1ff295SNetali // restore & in wikitext 9583b1ff295SNetali $text = str_replace('&', '&', $text); 9593b1ff295SNetali 9603832d0abSNetali return $text; 9613832d0abSNetali } 9623832d0abSNetali} 9633832d0abSNetali 964