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', 35d33a2f20SJennifer Graul 'no' => 'NB', 363832d0abSNetali 'pl' => 'PL', 373832d0abSNetali 'pt' => 'PT-PT', 383832d0abSNetali 'ro' => 'RO', 393832d0abSNetali 'ru' => 'RU', 403832d0abSNetali 'sk' => 'SK', 413832d0abSNetali 'sl' => 'SL', 423832d0abSNetali 'sv' => 'SV', 438311ddaaSnetali 'uk' => 'UK', 443832d0abSNetali 'zh' => 'ZH' 45b33135efSNetali ); 463832d0abSNetali 473832d0abSNetali /** 483832d0abSNetali * Register its handlers with the DokuWiki's event controller 493832d0abSNetali */ 503832d0abSNetali public function register(Doku_Event_Handler $controller) { 51153e4498SNetali $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'preprocess'); 52b33135efSNetali $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'pagetpl_load'); 53b33135efSNetali $controller->register_hook('COMMON_WIKIPAGE_SAVE','AFTER', $this, 'update_glossary'); 543c636ad3SNetali $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button'); 553c636ad3SNetali } 563c636ad3SNetali 570180404cSNetali public function add_menu_button(Doku_Event $event): void { 58153e4498SNetali global $ID; 59bbb1fba9SNetali global $ACT; 60bbb1fba9SNetali 61bbb1fba9SNetali if ($ACT != 'show') return; 62153e4498SNetali 633c636ad3SNetali if ($event->data['view'] != 'page') return; 643c636ad3SNetali 653c636ad3SNetali if (!$this->getConf('show_button')) return; 66153e4498SNetali 67b33135efSNetali // no translations for the glossary namespace 68b33135efSNetali if ($this->check_in_glossary_ns()) return; 69b33135efSNetali 70153e4498SNetali $split_id = explode(':', $ID); 71153e4498SNetali $lang_ns = array_shift($split_id); 72153e4498SNetali // check if we are in a language namespace 73153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 74ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 7599da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> check for push translation 7699da9a08SNetali if (!$this->check_do_push_translate()) return; 7799da9a08SNetali } else { 78153e4498SNetali // in language namespace --> check if we should translate 793c636ad3SNetali if (!$this->check_do_translation(true)) return; 8099da9a08SNetali } 81153e4498SNetali } else { 8299da9a08SNetali // do not show the button if we are not in a language namespace and the default language is in a namespace 8399da9a08SNetali if($this->getConf('default_lang_in_ns')) return; 84b33135efSNetali // not in language namespace and default language is not in a namespace --> check if we should show the push translate button 85153e4498SNetali if (!$this->check_do_push_translate()) return; 86153e4498SNetali } 873c636ad3SNetali 883c636ad3SNetali array_splice($event->data['items'], -1, 0, [new MenuItem()]); 893832d0abSNetali } 903832d0abSNetali 91153e4498SNetali public function preprocess(Doku_Event $event, $param): void { 923832d0abSNetali global $ID; 933c636ad3SNetali 943c636ad3SNetali // check if action is show or translate 953c636ad3SNetali if ($event->data != 'show' and $event->data != 'translate') return; 963c636ad3SNetali 97b33135efSNetali // redirect to glossary ns start if glossary ns is called 98b33135efSNetali if ($this->check_in_glossary_ns() and $event->data == 'show' and $ID == $this->get_glossary_ns()) { 99b33135efSNetali send_redirect(wl($this->get_glossary_ns() . ':start')); 100b33135efSNetali } 101b33135efSNetali 102153e4498SNetali $split_id = explode(':', $ID); 103153e4498SNetali $lang_ns = array_shift($split_id); 104153e4498SNetali // check if we are in a language namespace 105153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 106ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 10799da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> push translate 1083e2a3564SAndreas Gohr $this->push_translate_event($event); 10999da9a08SNetali } else { 11099da9a08SNetali // in language namespace --> autotrans direct 111153e4498SNetali $this->autotrans_direct($event); 11299da9a08SNetali } 113153e4498SNetali } else { 114153e4498SNetali // not in language namespace --> push translate 1153e2a3564SAndreas Gohr $this->push_translate_event($event); 116153e4498SNetali } 117153e4498SNetali } 118153e4498SNetali 119b33135efSNetali public function pagetpl_load(Doku_Event $event, $param): void { 120b33135efSNetali // handle glossary namespace init when we are in it 121b33135efSNetali if ($this->check_in_glossary_ns()) { 122b33135efSNetali $this->handle_glossary_init($event); 123b33135efSNetali return; 124b33135efSNetali } 125b33135efSNetali 126b33135efSNetali $this->autotrans_editor($event); 127b33135efSNetali } 128b33135efSNetali 129b33135efSNetali public function update_glossary(Doku_Event $event, $param): void { 130b33135efSNetali global $ID; 131b33135efSNetali // this also checks if the glossary feature is enabled 132b33135efSNetali if (!$this->check_in_glossary_ns()) return; 133b33135efSNetali 134b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 135b33135efSNetali 136b33135efSNetali // check if we are in a glossary definition 137b33135efSNetali if(preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $id_match)) { 138b33135efSNetali $old_glossary_id = $this->get_glossary_id($id_match[1], $id_match[2]); 139b33135efSNetali if ($event->data['changeType'] == DOKU_CHANGE_TYPE_DELETE) { 140b33135efSNetali // page deleted --> delete glossary 141b33135efSNetali if ($old_glossary_id) { 142b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 143b33135efSNetali if ($result) { 144b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 145b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 146b33135efSNetali } 147b33135efSNetali } 148b33135efSNetali return; 149b33135efSNetali } 150b33135efSNetali 151b33135efSNetali $entries = ''; 152b33135efSNetali 153b33135efSNetali // grep entries from definition table 154b33135efSNetali preg_match_all('/[ \t]*\|(.*?)\|(.*?)\|/', $event->data['newContent'], $matches, PREG_SET_ORDER); 155b33135efSNetali foreach ($matches as $match) { 156b33135efSNetali $src = trim($match[1]); 157b33135efSNetali $target = trim($match[2]); 158b33135efSNetali if ($src == '' or $target == '') { 159b33135efSNetali msg($this->getLang('msg_glossary_empty_key'), -1); 160b33135efSNetali return; 161b33135efSNetali } 162b33135efSNetali $entries .= $src . "\t" . $target . "\n"; 163b33135efSNetali } 164b33135efSNetali 165b33135efSNetali if (empty($matches)) { 166b33135efSNetali // no matches --> delete glossary 167b33135efSNetali if ($old_glossary_id) { 168b33135efSNetali $result = $this->delete_glossary($old_glossary_id); 169b33135efSNetali if ($result) { 170b33135efSNetali msg($this->getLang('msg_glossary_delete_success'), 1); 171b33135efSNetali $this->unset_glossary_id($id_match[1], $id_match[2]); 172b33135efSNetali } 173b33135efSNetali } 174b33135efSNetali return; 175b33135efSNetali } 176b33135efSNetali 177b33135efSNetali $new_glossary_id = $this->create_glossary($id_match[1], $id_match[2], $entries); 178b33135efSNetali 179b33135efSNetali if ($new_glossary_id) { 180b33135efSNetali msg($this->getLang('msg_glossary_create_success'), 1); 181b33135efSNetali $this->set_glossary_id($id_match[1], $id_match[2], $new_glossary_id); 182b33135efSNetali } else { 183b33135efSNetali return; 184b33135efSNetali } 185b33135efSNetali 186b33135efSNetali if ($old_glossary_id) $this->delete_glossary($old_glossary_id); 187b33135efSNetali } 188b33135efSNetali } 189b33135efSNetali 190153e4498SNetali private function autotrans_direct(Doku_Event $event): void { 191153e4498SNetali global $ID; 192153e4498SNetali 1933c636ad3SNetali // abort if action is translate and the translate button is disabled 1943c636ad3SNetali if ($event->data == 'translate' and !$this->getConf('show_button')) return; 1953c636ad3SNetali 1963c636ad3SNetali // do nothing on show action when mode is not direct 1973c636ad3SNetali if ($event->data == 'show' and $this->get_mode() != 'direct') return; 1983c636ad3SNetali 1993c636ad3SNetali // allow translation of existing pages is we are in the translate action 2003c636ad3SNetali $allow_existing = ($event->data == 'translate'); 2013c636ad3SNetali 2023c636ad3SNetali // reset action to show 2033c636ad3SNetali $event->data = 'show'; 2043c636ad3SNetali 205153e4498SNetali if (!$this->check_do_translation($allow_existing)) { 206153e4498SNetali return; 207153e4498SNetali } 2083832d0abSNetali 2090180404cSNetali $org_page_info = $this->get_org_page_info(); 2103e2a3564SAndreas Gohr try { 2110180404cSNetali $translated_text = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2123e2a3564SAndreas Gohr } catch (\Exception $e) { 2133e2a3564SAndreas Gohr msg($e->getMessage(), -1); 214153e4498SNetali return; 215153e4498SNetali } 2163832d0abSNetali 2173832d0abSNetali saveWikiText($ID, $translated_text, 'Automatic translation'); 2183832d0abSNetali 219153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 220153e4498SNetali 2213c636ad3SNetali // reload the page after translation 2223c636ad3SNetali send_redirect(wl($ID)); 2233832d0abSNetali } 2243832d0abSNetali 225b33135efSNetali private function autotrans_editor(Doku_Event $event): void { 2263832d0abSNetali if ($this->get_mode() != 'editor') return; 2273832d0abSNetali 2283832d0abSNetali if (!$this->check_do_translation()) return; 2293832d0abSNetali 2300180404cSNetali $org_page_info = $this->get_org_page_info(); 2313832d0abSNetali 2323e2a3564SAndreas Gohr try { 2330180404cSNetali $event->data['tpl'] = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 2343e2a3564SAndreas Gohr } catch (\Exception $e) { 2353e2a3564SAndreas Gohr msg($e->getMessage(), -1); 2363e2a3564SAndreas Gohr return; 2373e2a3564SAndreas Gohr } 2383832d0abSNetali } 2393832d0abSNetali 2403e2a3564SAndreas Gohr private function push_translate_event(Doku_Event $event): void { 241153e4498SNetali global $ID; 242153e4498SNetali 243153e4498SNetali // check if action is translate 244153e4498SNetali if ($event->data != 'translate') return; 245153e4498SNetali 246153e4498SNetali // check if button is enabled 247153e4498SNetali if (!$this->getConf('show_button')) { 248153e4498SNetali send_redirect(wl($ID)); 249153e4498SNetali return; 250153e4498SNetali } 251153e4498SNetali 252153e4498SNetali // push translate 253153e4498SNetali $push_langs = $this->get_push_langs(); 254153e4498SNetali $org_page_text = rawWiki($ID); 255153e4498SNetali foreach ($push_langs as $lang) { 2563e2a3564SAndreas Gohr try { 2573e2a3564SAndreas Gohr $this->push_translate($ID, $org_page_text, $lang); 2583e2a3564SAndreas Gohr } catch (\Exception $e) { 2593e2a3564SAndreas Gohr msg($e->getMessage(), -1); 260153e4498SNetali } 261153e4498SNetali } 262153e4498SNetali 263153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 264153e4498SNetali 265153e4498SNetali // reload the page after translation to clear the action 266153e4498SNetali send_redirect(wl($ID)); 267153e4498SNetali } 268153e4498SNetali 2693e2a3564SAndreas Gohr public function push_translate($id, $org_page_text, $lang): string { 2703e2a3564SAndreas Gohr if (!$this->check_do_push_translate()) { 2713e2a3564SAndreas Gohr throw new \Exception('Failed push translate checks', 400); 2723e2a3564SAndreas Gohr } 2733e2a3564SAndreas Gohr 2743e2a3564SAndreas Gohr // skip invalid languages 2753e2a3564SAndreas Gohr if (!array_key_exists($lang, $this->langs)) { 2763e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_invalid_lang') . $lang, 404); 2773e2a3564SAndreas Gohr } 2783e2a3564SAndreas Gohr 2793e2a3564SAndreas Gohr if ($this->getConf('default_lang_in_ns')) { 2803e2a3564SAndreas Gohr // if default lang is in ns: replace language namespace in ID 2813e2a3564SAndreas Gohr $split_id = explode(':', $id); 2823e2a3564SAndreas Gohr array_shift($split_id); 2833e2a3564SAndreas Gohr $lang_id = implode(':', $split_id); 2843e2a3564SAndreas Gohr $lang_id = $lang . ':' . $lang_id; 2853e2a3564SAndreas Gohr } else { 2863e2a3564SAndreas Gohr // if default lang is not in ns: add language namespace to ID 2873e2a3564SAndreas Gohr $lang_id = $lang . ':' . $id; 2883e2a3564SAndreas Gohr } 2893e2a3564SAndreas Gohr 2903e2a3564SAndreas Gohr // check permissions 2913e2a3564SAndreas Gohr $perm = auth_quickaclcheck($lang_id); 2923e2a3564SAndreas Gohr $exists = page_exists($lang_id); 2933e2a3564SAndreas Gohr if (($exists and $perm < AUTH_EDIT) or (!$exists and $perm < AUTH_CREATE)) { 2943e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_no_permissions') . $lang_id, 403); 2953e2a3564SAndreas Gohr } 2963e2a3564SAndreas Gohr 2973e2a3564SAndreas Gohr $translated_text = $this->deepl_translate($org_page_text, $lang, getNS($id)); 2983e2a3564SAndreas Gohr saveWikiText($lang_id, $translated_text, 'Automatic push translation'); 2993e2a3564SAndreas Gohr 3003e2a3564SAndreas Gohr return $lang_id; 3013e2a3564SAndreas Gohr } 3023e2a3564SAndreas Gohr 303b33135efSNetali private function handle_glossary_init(Doku_Event $event): void { 304b33135efSNetali global $ID; 305b33135efSNetali 306b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 307b33135efSNetali 308b33135efSNetali // create glossary landing page 309b33135efSNetali if ($ID == $glossary_ns . ':start') { 310b33135efSNetali $landing_page_text = '====== ' . $this->getLang('glossary_landing_heading') . ' ======' . "\n"; 311b33135efSNetali $landing_page_text .= $this->getLang('glossary_landing_info_msg') . "\n"; 312b33135efSNetali 313b33135efSNetali $src_lang = substr($this->get_default_lang(), 0, 2); 314b33135efSNetali 315b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 316b33135efSNetali foreach ($available_glossaries as $glossary) { 317b33135efSNetali if ($glossary['source_lang'] != $src_lang) continue; 318b33135efSNetali // generate links to the available glossary pages 319b33135efSNetali $landing_page_text .= ' * [[.:' . $glossary['source_lang'] . '_' . $glossary['target_lang'] . '|' . strtoupper($glossary['source_lang']) . ' -> ' . strtoupper($glossary['target_lang']) . ']]' . "\n"; 320b33135efSNetali } 321b33135efSNetali $event->data['tpl'] = $landing_page_text; 322b33135efSNetali return; 323b33135efSNetali } 324b33135efSNetali 325b33135efSNetali if (preg_match('/^' . $glossary_ns . ':(\w{2})_(\w{2})$/', $ID, $match)) { 326b33135efSNetali // check if glossaries are supported for this language pair 327b33135efSNetali if (!$this->check_glossary_supported($match[1], $match[2])) { 328b33135efSNetali msg($this->getLang('msg_glossary_unsupported'), -1); 329b33135efSNetali return; 330b33135efSNetali } 331b33135efSNetali 332b33135efSNetali $page_text = '====== ' . $this->getLang('glossary_definition_heading') . ': ' . strtoupper($match[1]) . ' -> ' . strtoupper($match[2]) . ' ======' . "\n"; 333b33135efSNetali $page_text .= $this->getLang('glossary_definition_help') . "\n\n"; 334b33135efSNetali $page_text .= '^ ' . strtoupper($match[1]) . ' ^ ' . strtoupper($match[2]) . ' ^' . "\n"; 335b33135efSNetali 336b33135efSNetali $event->data['tpl'] = $page_text; 337b33135efSNetali return; 338b33135efSNetali } 339b33135efSNetali } 340b33135efSNetali 341b33135efSNetali private function get_glossary_ns(): string { 342b33135efSNetali return trim(strtolower($this->getConf('glossary_ns'))); 343b33135efSNetali } 344b33135efSNetali 3453832d0abSNetali private function get_mode(): string { 3463832d0abSNetali global $ID; 3473832d0abSNetali if ($this->getConf('editor_regex')) { 3483832d0abSNetali if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 3493832d0abSNetali } 3503832d0abSNetali if ($this->getConf('direct_regex')) { 3513832d0abSNetali if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 3523832d0abSNetali } 3533832d0abSNetali return $this->getConf('mode'); 3543832d0abSNetali } 3553832d0abSNetali 3563832d0abSNetali private function get_target_lang(): string { 3573832d0abSNetali global $ID; 3583832d0abSNetali $split_id = explode(':', $ID); 3593832d0abSNetali return array_shift($split_id); 3603832d0abSNetali } 3613832d0abSNetali 362ff327fe6SNetali private function get_default_lang(): string { 363ff327fe6SNetali global $conf; 364ff327fe6SNetali 365ff327fe6SNetali if (empty($conf['lang_before_translation'])) { 366ff327fe6SNetali $default_lang = $conf['lang']; 367ff327fe6SNetali } else { 368ff327fe6SNetali $default_lang = $conf['lang_before_translation']; 369ff327fe6SNetali } 370ff327fe6SNetali 371ff327fe6SNetali return $default_lang; 372ff327fe6SNetali } 373ff327fe6SNetali 3740180404cSNetali private function get_org_page_info(): array { 3753832d0abSNetali global $ID; 3763832d0abSNetali 3773832d0abSNetali $split_id = explode(':', $ID); 3783832d0abSNetali array_shift($split_id); 3793832d0abSNetali $org_id = implode(':', $split_id); 3803832d0abSNetali 38199da9a08SNetali // if default lang is in ns: add default ns in front of org id 38299da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 383ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 38499da9a08SNetali } 38599da9a08SNetali 3860180404cSNetali return array("ns" => getNS($org_id), "text" => rawWiki($org_id)); 3873832d0abSNetali } 3883832d0abSNetali 389b33135efSNetali private function get_available_glossaries(): array { 390b33135efSNetali if (!trim($this->getConf('api_key'))) { 391b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 392b33135efSNetali return array(); 393b33135efSNetali } 394b33135efSNetali 395b33135efSNetali if ($this->getConf('api') == 'free') { 396b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossary-language-pairs'; 397b33135efSNetali } else { 398b33135efSNetali $url = 'https://api.deepl.com/v2/glossary-language-pairs'; 399b33135efSNetali } 400b33135efSNetali 401b33135efSNetali $http = new DokuHTTPClient(); 402b33135efSNetali 403b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 404b33135efSNetali 405b33135efSNetali $raw_response = $http->get($url); 406b33135efSNetali 407b33135efSNetali if ($http->status >= 400) { 408b33135efSNetali // add error messages 409b33135efSNetali switch ($http->status) { 410b33135efSNetali case 403: 411b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 412b33135efSNetali break; 413b33135efSNetali default: 414b33135efSNetali msg($this->getLang('msg_glossary_fetch_fail'), -1); 415b33135efSNetali break; 416b33135efSNetali } 417b33135efSNetali 418b33135efSNetali // if any error occurred return an empty array 419b33135efSNetali return array(); 420b33135efSNetali } 421b33135efSNetali 422b33135efSNetali $json_response = json_decode($raw_response, true); 423b33135efSNetali 424b33135efSNetali return $json_response['supported_languages']; 425b33135efSNetali } 426b33135efSNetali 427b33135efSNetali private function get_glossary_id($src, $target): string { 428b33135efSNetali if (!file_exists(DOKU_CONF . 'deepl-glossaries.json')) return ''; 429b33135efSNetali 430b33135efSNetali $key = $src . "_" . $target; 431b33135efSNetali 432b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 433b33135efSNetali $content = json_decode($raw_json, true); 434b33135efSNetali 435b33135efSNetali if (array_key_exists($key, $content)) { 436b33135efSNetali return $content[$key]; 437b33135efSNetali } else { 438b33135efSNetali return ''; 439b33135efSNetali } 440b33135efSNetali } 441b33135efSNetali 442b33135efSNetali private function set_glossary_id($src, $target, $glossary_id): void { 443b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 444b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 445b33135efSNetali $content = json_decode($raw_json, true); 446b33135efSNetali } else { 447b33135efSNetali $content = array(); 448b33135efSNetali } 449b33135efSNetali 450b33135efSNetali $key = $src . "_" . $target; 451b33135efSNetali 452b33135efSNetali $content[$key] = $glossary_id; 453b33135efSNetali 454b33135efSNetali $raw_json = json_encode($content); 455b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 456b33135efSNetali } 457b33135efSNetali 458b33135efSNetali private function unset_glossary_id($src, $target): void { 459b33135efSNetali if (file_exists(DOKU_CONF . 'deepl-glossaries.json')) { 460b33135efSNetali $raw_json = file_get_contents(DOKU_CONF . 'deepl-glossaries.json'); 461b33135efSNetali $content = json_decode($raw_json, true); 462b33135efSNetali } else { 463b33135efSNetali return; 464b33135efSNetali } 465b33135efSNetali 466b33135efSNetali $key = $src . "_" . $target; 467b33135efSNetali 468b33135efSNetali unset($content[$key]); 469b33135efSNetali 470b33135efSNetali $raw_json = json_encode($content); 471b33135efSNetali file_put_contents(DOKU_CONF . 'deepl-glossaries.json', $raw_json); 472b33135efSNetali } 473b33135efSNetali 474b33135efSNetali private function check_in_glossary_ns(): bool { 475b33135efSNetali global $ID; 476b33135efSNetali 477b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 478b33135efSNetali 479b33135efSNetali // check if the glossary namespace is defined 480b33135efSNetali if (!$glossary_ns) return false; 481b33135efSNetali 482b33135efSNetali // check if we are in the glossary namespace 483b33135efSNetali if (substr($ID, 0, strlen($glossary_ns)) == $glossary_ns) { 484b33135efSNetali return true; 485b33135efSNetali } else { 486b33135efSNetali return false; 487b33135efSNetali } 488b33135efSNetali } 489b33135efSNetali 490b33135efSNetali private function check_glossary_supported($src, $target): bool { 491b33135efSNetali if(strlen($src) != 2 or strlen($target) != 2) return false; 492b33135efSNetali $available_glossaries = $this->get_available_glossaries(); 493b33135efSNetali foreach ($available_glossaries as $glossary) { 494b33135efSNetali if ($src == $glossary['source_lang'] and $target == $glossary['target_lang']) return true; 495b33135efSNetali } 496b33135efSNetali return false; 497b33135efSNetali } 498b33135efSNetali 4993c636ad3SNetali private function check_do_translation($allow_existing = false): bool { 5003832d0abSNetali global $INFO; 5013832d0abSNetali global $ID; 5023832d0abSNetali 5033c636ad3SNetali // only translate if the current page does not exist 5043c636ad3SNetali if ($INFO['exists'] and !$allow_existing) return false; 5053c636ad3SNetali 5063c636ad3SNetali // permission check 5073c636ad3SNetali $perm = auth_quickaclcheck($ID); 5083c636ad3SNetali if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false; 5093c636ad3SNetali 5103832d0abSNetali // skip blacklisted namespaces and pages 5113832d0abSNetali if ($this->getConf('blacklist_regex')) { 5123832d0abSNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 5133832d0abSNetali } 5143832d0abSNetali 5153832d0abSNetali $split_id = explode(':', $ID); 5163832d0abSNetali $lang_ns = array_shift($split_id); 5173832d0abSNetali // only translate if the current page is in a language namespace 5183832d0abSNetali if (!array_key_exists($lang_ns, $this->langs)) return false; 5193832d0abSNetali 5203832d0abSNetali $org_id = implode(':', $split_id); 52199da9a08SNetali 52299da9a08SNetali // if default lang is in ns: add default ns in front of org id 52399da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 524ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 52599da9a08SNetali } 52699da9a08SNetali 527b33135efSNetali // no translations for the glossary namespace 528b33135efSNetali $glossary_ns = $this->get_glossary_ns(); 529b33135efSNetali if ($glossary_ns and substr($org_id, 0, strlen($glossary_ns)) == $glossary_ns) return false; 530b33135efSNetali 5313832d0abSNetali // check if the original page exists 5323832d0abSNetali if (!page_exists($org_id)) return false; 5333832d0abSNetali 5343832d0abSNetali return true; 5353832d0abSNetali } 5363832d0abSNetali 537153e4498SNetali private function check_do_push_translate(): bool { 538153e4498SNetali global $ID; 53999da9a08SNetali global $INFO; 54099da9a08SNetali 54199da9a08SNetali if (!$INFO['exists']) return false; 54299da9a08SNetali 543a3a51507SNetali // only allow push translation if the user can edit this page 544a3a51507SNetali $perm = auth_quickaclcheck($ID); 545a3a51507SNetali if ($perm < AUTH_EDIT) return false; 546a3a51507SNetali 54799da9a08SNetali // if default language is in namespace: only allow push translation from that namespace 54899da9a08SNetali if($this->getConf('default_lang_in_ns')) { 54999da9a08SNetali $split_id = explode(':', $ID); 55099da9a08SNetali $lang_ns = array_shift($split_id); 55199da9a08SNetali 552ff327fe6SNetali if ($lang_ns !== $this->get_default_lang()) return false; 55399da9a08SNetali } 554153e4498SNetali 555b33135efSNetali // no translations for the glossary namespace 556b33135efSNetali if ($this->check_in_glossary_ns()) return false; 557b33135efSNetali 558153e4498SNetali $push_langs = $this->get_push_langs(); 559153e4498SNetali // push_langs empty --> push_translate disabled --> abort 560153e4498SNetali if (empty($push_langs)) return false; 561153e4498SNetali 562153e4498SNetali // skip blacklisted namespaces and pages 563153e4498SNetali if ($this->getConf('blacklist_regex')) { 564153e4498SNetali // blacklist regex match --> abort 565153e4498SNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 566153e4498SNetali } 567153e4498SNetali 568153e4498SNetali return true; 569153e4498SNetali } 570153e4498SNetali 571b33135efSNetali private function create_glossary($src, $target, $entries): string { 572b33135efSNetali if (!trim($this->getConf('api_key'))) { 573b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 574b33135efSNetali return ''; 575b33135efSNetali } 576b33135efSNetali 577b33135efSNetali if ($this->getConf('api') == 'free') { 578b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 579b33135efSNetali } else { 580b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 581b33135efSNetali } 582b33135efSNetali 583b33135efSNetali $data = array( 584b33135efSNetali 'name' => 'DokuWiki-Autotranslate-' . $src . '_' . $target, 585b33135efSNetali 'source_lang' => $src, 586b33135efSNetali 'target_lang' => $target, 587b33135efSNetali 'entries' => $entries, 588b33135efSNetali 'entries_format' => 'tsv' 589b33135efSNetali ); 590b33135efSNetali 591b33135efSNetali $http = new DokuHTTPClient(); 592b33135efSNetali 593b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 594b33135efSNetali 595b33135efSNetali $raw_response = $http->post($url, $data); 596b33135efSNetali 597b33135efSNetali if ($http->status >= 400) { 598b33135efSNetali // add error messages 599b33135efSNetali switch ($http->status) { 600b33135efSNetali case 403: 601b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 602b33135efSNetali break; 603b33135efSNetali case 400: 604b33135efSNetali msg($this->getLang('msg_glossary_content_invalid'), -1); 605b33135efSNetali break; 606b33135efSNetali default: 607b33135efSNetali msg($this->getLang('msg_glossary_create_fail'), -1); 608b33135efSNetali break; 609b33135efSNetali } 610b33135efSNetali 611b33135efSNetali // if any error occurred return an empty string 612b33135efSNetali return ''; 613b33135efSNetali } 614b33135efSNetali 615b33135efSNetali $json_response = json_decode($raw_response, true); 616b33135efSNetali 617b33135efSNetali return $json_response['glossary_id']; 618b33135efSNetali } 619b33135efSNetali 620b33135efSNetali private function delete_glossary($glossary_id): bool { 621b33135efSNetali if (!trim($this->getConf('api_key'))) { 622b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 623b33135efSNetali return false; 624b33135efSNetali } 625b33135efSNetali 626b33135efSNetali if ($this->getConf('api') == 'free') { 627b33135efSNetali $url = 'https://api-free.deepl.com/v2/glossaries'; 628b33135efSNetali } else { 629b33135efSNetali $url = 'https://api.deepl.com/v2/glossaries'; 630b33135efSNetali } 631b33135efSNetali 632b33135efSNetali $url .= '/' . $glossary_id; 633b33135efSNetali 634b33135efSNetali $http = new DokuHTTPClient(); 635b33135efSNetali 636b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 637b33135efSNetali 638b33135efSNetali $http->sendRequest($url, '', 'DELETE'); 639b33135efSNetali 640b33135efSNetali if ($http->status >= 400) { 641b33135efSNetali // add error messages 642b33135efSNetali switch ($http->status) { 643b33135efSNetali case 403: 644b33135efSNetali msg($this->getLang('msg_bad_key'), -1); 645b33135efSNetali break; 646b33135efSNetali default: 647b33135efSNetali msg($this->getLang('msg_glossary_delete_fail'), -1); 648b33135efSNetali break; 649b33135efSNetali } 650b33135efSNetali 651b33135efSNetali // if any error occurred return false 652b33135efSNetali return false; 653b33135efSNetali } 654b33135efSNetali 655b33135efSNetali return true; 656b33135efSNetali } 657b33135efSNetali 6580180404cSNetali private function deepl_translate($text, $target_lang, $org_ns): string { 659b33135efSNetali if (!trim($this->getConf('api_key'))) { 6603e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_bad_key'), 400); 661b33135efSNetali } 6623832d0abSNetali 6630180404cSNetali $text = $this->patch_links($text, $target_lang, $org_ns); 6640180404cSNetali 6653832d0abSNetali $text = $this->insert_ignore_tags($text); 6663832d0abSNetali 667b33135efSNetali $data = array( 668b33135efSNetali 'source_lang' => strtoupper(substr($this->get_default_lang(), 0, 2)), // cut of things like "-informal" 6690180404cSNetali 'target_lang' => $this->langs[$target_lang], 6703832d0abSNetali 'tag_handling' => 'xml', 6710180404cSNetali 'ignore_tags' => 'ignore', 672*d6d581afSJennifer Graul 'tag_handling_version' => 'v1', 6733832d0abSNetali 'text' => $text 674b33135efSNetali ); 675b33135efSNetali 676b33135efSNetali // check if glossaries are enabled 677b33135efSNetali if ($this->get_glossary_ns()) { 678b33135efSNetali $src = substr($this->get_default_lang(), 0, 2); 679b33135efSNetali $target = substr($target_lang, 0, 2); 680b33135efSNetali $glossary_id = $this->get_glossary_id($src, $target); 681b33135efSNetali if ($glossary_id) { 682b33135efSNetali // use glossary if it is defined 683b33135efSNetali $data['glossary_id'] = $glossary_id; 684b33135efSNetali } 685b33135efSNetali } 6863832d0abSNetali 6873832d0abSNetali if ($this->getConf('api') == 'free') { 68881931e50SNetali $url = 'https://api-free.deepl.com/v2/translate'; 6893832d0abSNetali } else { 69081931e50SNetali $url = 'https://api.deepl.com/v2/translate'; 6913832d0abSNetali } 6923832d0abSNetali 69381931e50SNetali $http = new DokuHTTPClient(); 694858956d2SAndreas Gohr $http->keep_alive = false; 695b33135efSNetali 696b33135efSNetali $http->headers = array('Authorization' => 'DeepL-Auth-Key ' . $this->getConf('api_key')); 697b33135efSNetali 69881931e50SNetali $raw_response = $http->post($url, $data); 6993832d0abSNetali 700858956d2SAndreas Gohr if ($http->status >= 400 || $http->status < 200) { 7015f8ab21dSNetali // add error messages 7025f8ab21dSNetali switch ($http->status) { 7035f8ab21dSNetali case 403: 7043e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_bad_key'), 403); 705b33135efSNetali case 404: 7063e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_invalid_glossary'), 404); 7075f8ab21dSNetali case 456: 7083e2a3564SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail_quota_exceeded'), 456); 7095f8ab21dSNetali default: 7106a17ed8dSAnna Dabrowska if ($this->getConf('api_log_errors')) { 7116a17ed8dSAnna Dabrowska $logger = \dokuwiki\Logger::getInstance('deeplautotranslate'); 7126a17ed8dSAnna Dabrowska $logger->log("$http->status " . $http->resp_body, $data['text']); 7136a17ed8dSAnna Dabrowska } 714858956d2SAndreas Gohr throw new \Exception($this->getLang('msg_translation_fail'), $http->status ?: 500); 7153e2a3564SAndreas Gohr } 7165f8ab21dSNetali } 7175f8ab21dSNetali 7183e2a3564SAndreas Gohr $json_response = json_decode($raw_response, true, JSON_THROW_ON_ERROR); 7193832d0abSNetali $translated_text = $json_response['translations'][0]['text']; 7203832d0abSNetali 7213832d0abSNetali $translated_text = $this->remove_ignore_tags($translated_text); 7223832d0abSNetali 7233832d0abSNetali return $translated_text; 7243832d0abSNetali } 7253832d0abSNetali 726153e4498SNetali private function get_push_langs(): array { 727153e4498SNetali $push_langs = trim($this->getConf('push_langs')); 728153e4498SNetali 729153e4498SNetali if ($push_langs === '') return array(); 730153e4498SNetali 731153e4498SNetali return explode(' ', $push_langs); 732153e4498SNetali } 733153e4498SNetali 73424d1dd2fSAndreas Gohr /** 73524d1dd2fSAndreas Gohr * Is the given ID a relative path? 73624d1dd2fSAndreas Gohr * 73724d1dd2fSAndreas Gohr * Always returns false if keep_relative is disabled. 73824d1dd2fSAndreas Gohr * 73924d1dd2fSAndreas Gohr * @param string $id 74024d1dd2fSAndreas Gohr * @return bool 74124d1dd2fSAndreas Gohr */ 742522d0814SJennifer Graul private function is_relative_link($id): bool { 74324d1dd2fSAndreas Gohr if (!$this->getConf('keep_relative')) return false; 74424d1dd2fSAndreas Gohr if ($id === '') return false; 74524d1dd2fSAndreas Gohr if (strpos($id, ':') === false) return true; 74624d1dd2fSAndreas Gohr if ($id[0] === '.') return true; 74724d1dd2fSAndreas Gohr if ($id[0] === '~') return true; 74824d1dd2fSAndreas Gohr return false; 74924d1dd2fSAndreas Gohr } 75024d1dd2fSAndreas Gohr 7510180404cSNetali private function patch_links($text, $target_lang, $ns): string { 7520180404cSNetali /* 7530180404cSNetali * 1. Find links in [[ aa:bb ]] or [[ aa:bb | cc ]] 7540180404cSNetali * 2. Extract aa:bb 7550180404cSNetali * 3. Check if lang:aa:bb exists 7560180404cSNetali * 3.1. --> Yes --> replace 7570180404cSNetali * 3.2. --> No --> leave it as it is 7580180404cSNetali */ 7593832d0abSNetali 7600180404cSNetali 7610180404cSNetali /* 7620180404cSNetali * LINKS 7630180404cSNetali */ 7640180404cSNetali 7654b84d3cfSNetali preg_match_all('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', $text, $matches, PREG_SET_ORDER); 7660180404cSNetali 7670180404cSNetali foreach ($matches as $match) { 7680180404cSNetali 7690180404cSNetali // external link --> skip 770a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 7710180404cSNetali 77284cda41fSNetali // skip interwiki links 77384cda41fSNetali if (strpos($match[1], '>') !== false) continue; 77484cda41fSNetali 7752a12605eSnetali // skip mail addresses 7762a12605eSnetali if (strpos($match[1], '@') !== false) continue; 7772a12605eSnetali 77884cda41fSNetali // skip windows share links 77984cda41fSNetali if (strpos($match[1], '\\\\') !== false) continue; 78084cda41fSNetali 78184cda41fSNetali $resolved_id = trim($match[1]); 782522d0814SJennifer Graul if($this->is_relative_link($resolved_id)) continue; 7830180404cSNetali 7840180404cSNetali resolve_pageid($ns, $resolved_id, $exists); 7850180404cSNetali 78653f3766cSNetali $resolved_id_full = $resolved_id; 7870180404cSNetali 7886663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 7896663bcb5SNetali $split_id = explode(':', $resolved_id); 7906663bcb5SNetali $lang_ns = array_shift($split_id); 7916663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 7926663bcb5SNetali $resolved_id = implode(':', $split_id); 7936663bcb5SNetali } 7946663bcb5SNetali 7950180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 7960180404cSNetali 7970180404cSNetali if (!page_exists($lang_id)) { 79853f3766cSNetali // Page in target lang does not exist --> replace with absolute ID in case it was a relative ID 79953f3766cSNetali $new_link = '[[' . $resolved_id_full . $match[2] . $match[3] . ']]'; 80053f3766cSNetali } else { 80153f3766cSNetali // Page in target lang exists --> replace link 8024b84d3cfSNetali $new_link = '[[' . $lang_id . $match[2] . $match[3] . ']]'; 80353f3766cSNetali } 8040180404cSNetali 8050180404cSNetali $text = str_replace($match[0], $new_link, $text); 8060180404cSNetali 8070180404cSNetali } 8080180404cSNetali 8090180404cSNetali /* 8100180404cSNetali * MEDIA 8110180404cSNetali */ 8120180404cSNetali 81384cda41fSNetali preg_match_all('/\{\{(([\s\S]*?)(\?[\s\S]*?)?)(\|([\s\S]*?))?}}/', $text, $matches, PREG_SET_ORDER); 8140180404cSNetali 8150180404cSNetali foreach ($matches as $match) { 8160180404cSNetali 8170180404cSNetali // external image --> skip 818a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 819a3a51507SNetali 820a3a51507SNetali // skip things like {{tag>...}} 821a3a51507SNetali if (strpos($match[1], '>') !== false) continue; 8220180404cSNetali 82384cda41fSNetali // keep alignment 82484cda41fSNetali $align_left = ""; 82584cda41fSNetali $align_right = ""; 82684cda41fSNetali 82784cda41fSNetali // align left --> space in front of ID 82884cda41fSNetali if (substr($match[1], 0, 1) == " ") $align_left = " "; 82984cda41fSNetali // align right --> space behind id 83084cda41fSNetali if (substr($match[1], -1) == " ") $align_right = " "; 83184cda41fSNetali 83284cda41fSNetali $resolved_id = trim($match[2]); 83384cda41fSNetali $params = trim($match[3]); 8340180404cSNetali 835522d0814SJennifer Graul if($this->is_relative_link($resolved_id)) continue; 83624d1dd2fSAndreas Gohr 8370180404cSNetali resolve_mediaid($ns, $resolved_id, $exists); 8380180404cSNetali 83953f3766cSNetali $resolved_id_full = $resolved_id; 8400180404cSNetali 8416663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 8426663bcb5SNetali $split_id = explode(':', $resolved_id); 8436663bcb5SNetali $lang_ns = array_shift($split_id); 8446663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 8456663bcb5SNetali $resolved_id = implode(':', $split_id); 8466663bcb5SNetali } 8476663bcb5SNetali 8480180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 8490180404cSNetali 8500180404cSNetali $lang_id_fn = mediaFN($lang_id); 8510180404cSNetali 8520180404cSNetali if (!file_exists($lang_id_fn)) { 85353f3766cSNetali // media in target lang does not exist --> replace with absolute ID in case it was a relative ID 85484cda41fSNetali $new_link = '{{' . $align_left . $resolved_id_full . $params . $align_right . $match[4] . '}}'; 85553f3766cSNetali } else { 85653f3766cSNetali // media in target lang exists --> replace it 85784cda41fSNetali $new_link = '{{' . $align_left . $lang_id . $params . $align_right . $match[4] . '}}'; 85853f3766cSNetali } 8590180404cSNetali 8600180404cSNetali $text = str_replace($match[0], $new_link, $text); 8610180404cSNetali 8620180404cSNetali } 8630180404cSNetali 8640180404cSNetali return $text; 8650180404cSNetali } 8660180404cSNetali 8670180404cSNetali private function insert_ignore_tags($text): string { 8680180404cSNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 8690180404cSNetali $text = preg_replace('/<[\s\S]+?>/', '<ignore>${0}</ignore>', $text); 8700180404cSNetali 8711cd781c4SNetali // prevent deepl from breaking headings 8721cd781c4SNetali $text = preg_replace('/={1,6}/', '<ignore>${0}</ignore>', $text); 8731cd781c4SNetali 874087c645bSJennifer Graul // prevent deepl from with some page lists 875087c645bSJennifer Graul $text = str_replace("{{top}}", "<ignore>{{top}}</ignore>", $text); 876087c645bSJennifer Graul $text = str_replace("{{rating}}", "<ignore>{{rating}}</ignore>", $text); 877087c645bSJennifer Graul 87843d62a6bSnetali // prevent deepl from messing with nocache-instructions 87943d62a6bSnetali $text = str_replace("~~NOCACHE~~", "<ignore>~~NOCACHE~~</ignore>", $text); 88043d62a6bSnetali 881a3a51507SNetali // fix for plugins like tag or template 882a3a51507SNetali $text = preg_replace('/\{\{[\s\w]+?>[\s\S]*?}}/', '<ignore>${0}</ignore>', $text); 8830180404cSNetali 8843b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 8853b1ff295SNetali $text = preg_replace('/\S+:\/\/\S+/', '<ignore>${0}</ignore>', $text); 8863b1ff295SNetali 8870180404cSNetali // ignore link/media ids but translate the text (if existing) 8884b84d3cfSNetali $text = preg_replace('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', '<ignore>[[${1}${2}${4}</ignore>${5}<ignore>]]</ignore>', $text); 8890180404cSNetali $text = preg_replace('/\{\{([\s\S]*?)(\?[\s\S]*?)?((\|)([\s\S]*?))?}}/', '<ignore>{{${1}${2}${4}</ignore>${5}<ignore>}}</ignore>', $text); 8900180404cSNetali 8913b1ff295SNetali // prevent deepl from messing with tables 8923b1ff295SNetali $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8937c99a9b0Sextrasec $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8947c99a9b0Sextrasec $text = str_replace(" ^ ", "<ignore> ^ </ignore>", $text); 8957c99a9b0Sextrasec $text = str_replace("^ ", "<ignore>^ </ignore>", $text); 8967c99a9b0Sextrasec $text = str_replace(" ^", "<ignore> ^</ignore>", $text); 8977c99a9b0Sextrasec $text = str_replace("^", "<ignore>^</ignore>", $text); 8987c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 8997c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 9007c99a9b0Sextrasec $text = str_replace(" | ", "<ignore> | </ignore>", $text); 9017c99a9b0Sextrasec $text = str_replace("| ", "<ignore>| </ignore>", $text); 9027c99a9b0Sextrasec $text = str_replace(" |", "<ignore> |</ignore>", $text); 9033b1ff295SNetali $text = str_replace("|", "<ignore>|</ignore>", $text); 9043b1ff295SNetali 9050180404cSNetali // prevent deepl from doing strange things with dokuwiki syntax 9066301ba6eSNetali // if a full line is formatted, we have to double-ignore for some reason 9076301ba6eSNetali $text = str_replace("''", "<ignore><ignore>''</ignore></ignore>", $text); 9086301ba6eSNetali $text = str_replace("//", "<ignore><ignore>//</ignore></ignore>", $text); 9096301ba6eSNetali $text = str_replace("**", "<ignore><ignore>**</ignore></ignore>", $text); 9106301ba6eSNetali $text = str_replace("__", "<ignore><ignore>__</ignore></ignore>", $text); 9116301ba6eSNetali $text = str_replace("\\\\", "<ignore><ignore>\\\\</ignore></ignore>", $text); 9120180404cSNetali 91313221d46SNetali // prevent deepl from messing with smileys 91413221d46SNetali $smileys = array_keys(getSmileys()); 91513221d46SNetali foreach ($smileys as $smiley) { 91613221d46SNetali $text = str_replace($smiley, "<ignore>" . $smiley . "</ignore>", $text); 91713221d46SNetali } 91813221d46SNetali 9190180404cSNetali // ignore code tags 9200180404cSNetali $text = preg_replace('/(<php[\s\S]*?>[\s\S]*?<\/php>)/', '<ignore>${1}</ignore>', $text); 921057940f7SNetali $text = preg_replace('/(<file[\s\S]*?>[\s\S]*?<\/file>)/', '<ignore>${1}</ignore>', $text); 922057940f7SNetali $text = preg_replace('/(<code[\s\S]*?>[\s\S]*?<\/code>)/', '<ignore>${1}</ignore>', $text); 923057940f7SNetali 9240180404cSNetali // ignore the expressions from the ignore list 9253832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 9263832d0abSNetali 9273832d0abSNetali foreach ($ignored_expressions as $expression) { 9283832d0abSNetali $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 9293832d0abSNetali } 9303832d0abSNetali 9313832d0abSNetali return $text; 9323832d0abSNetali } 9333832d0abSNetali 9343832d0abSNetali private function remove_ignore_tags($text): string { 9353832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 9363832d0abSNetali 9373832d0abSNetali foreach ($ignored_expressions as $expression) { 9383832d0abSNetali $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 9393832d0abSNetali } 9403832d0abSNetali 94143d62a6bSnetali // prevent deepl from messing with nocache-instructions 94243d62a6bSnetali $text = str_replace("<ignore>~~NOCACHE~~</ignore>", "~~NOCACHE~~", $text); 94343d62a6bSnetali 944d72397a2SJennifer Graul // prevent deepl from breaking headings 945d72397a2SJennifer Graul $text = preg_replace('/<ignore>(={1,6})<\/ignore>/','${1}', $text); 946d72397a2SJennifer Graul 9473b1ff295SNetali // prevent deepl from messing with tables 9483b1ff295SNetali $text = str_replace("<ignore>^</ignore>", "^", $text); 9497c99a9b0Sextrasec $text = str_replace("<ignore>^ </ignore>", "^ ", $text); 9507c99a9b0Sextrasec $text = str_replace("<ignore> ^</ignore>", " ^", $text); 9517c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9527c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9537c99a9b0Sextrasec $text = str_replace("<ignore> ^ </ignore>", " ^ ", $text); 9547c99a9b0Sextrasec $text = str_replace("<ignore>|</ignore>", "|", $text); 9557c99a9b0Sextrasec $text = str_replace("<ignore>| </ignore>", "| ", $text); 9567c99a9b0Sextrasec $text = str_replace("<ignore> |</ignore>", " |", $text); 9577c99a9b0Sextrasec $text = str_replace("<ignore> | </ignore>", " | ", $text); 9587c99a9b0Sextrasec $text = str_replace("<ignore> | </ignore>", " | ", $text); 9593b1ff295SNetali $text = str_replace("<ignore> | </ignore>", " | ", $text); 9603b1ff295SNetali 9616301ba6eSNetali $text = str_replace("<ignore><ignore>''</ignore></ignore>", "''", $text); 9626301ba6eSNetali $text = str_replace("<ignore><ignore>//</ignore></ignore>", "//", $text); 9636301ba6eSNetali $text = str_replace("<ignore><ignore>**</ignore></ignore>", "**", $text); 9646301ba6eSNetali $text = str_replace("<ignore><ignore>__</ignore></ignore>", "__", $text); 9656301ba6eSNetali $text = str_replace("<ignore><ignore>\\\\</ignore></ignore>", "\\\\", $text); 9660180404cSNetali 9673b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 9683b1ff295SNetali $text = preg_replace('/<ignore>(\S+:\/\/\S+)<\/ignore>/', '${1}', $text); 9693b1ff295SNetali 9704b84d3cfSNetali $text = preg_replace('/<ignore>\[\[([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>]]<\/ignore>/', '[[${1}${2}${4}]]', $text); 9714b84d3cfSNetali $text = preg_replace('/<ignore>\{\{([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>}}<\/ignore>/', '{{${1}${2}${4}}}', $text); 9724b84d3cfSNetali 973087c645bSJennifer Graul // prevent deepl from with some page lists 974087c645bSJennifer Graul $text = str_replace("<ignore>{{top}}</ignore>", "{{top}}", $text); 975087c645bSJennifer Graul $text = str_replace("<ignore>{{rating}}</ignore>", "{{rating}}", $text); 976087c645bSJennifer Graul 97713221d46SNetali // prevent deepl from messing with smileys 97813221d46SNetali $smileys = array_keys(getSmileys()); 97913221d46SNetali foreach ($smileys as $smiley) { 98013221d46SNetali $text = str_replace("<ignore>" . $smiley . "</ignore>", $smiley, $text); 98113221d46SNetali } 98213221d46SNetali 9830180404cSNetali $text = preg_replace('/<ignore>(<php[\s\S]*?>[\s\S]*?<\/php>)<\/ignore>/', '${1}', $text); 9840180404cSNetali $text = preg_replace('/<ignore>(<file[\s\S]*?>[\s\S]*?<\/file>)<\/ignore>/', '${1}', $text); 9850180404cSNetali $text = preg_replace('/<ignore>(<code[\s\S]*?>[\s\S]*?<\/code>)<\/ignore>/', '${1}', $text); 9860180404cSNetali 987a3a51507SNetali // fix for plugins like tag or template 988a3a51507SNetali $text = preg_replace('/<ignore>(\{\{[\s\w]+?>[\s\S]*?}})<\/ignore>/', '${1}', $text); 9890180404cSNetali 9901cd781c4SNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 9911cd781c4SNetali $text = preg_replace('/<ignore>(<[\s\S]+?>)<\/ignore>/', '${1}', $text); 9921cd781c4SNetali 9930180404cSNetali // restore < and > for example from arrows (-->) in wikitext 9940180404cSNetali $text = str_replace('>', '>', $text); 9950180404cSNetali $text = str_replace('<', '<', $text); 9960180404cSNetali 9973b1ff295SNetali // restore & in wikitext 9983b1ff295SNetali $text = str_replace('&', '&', $text); 9993b1ff295SNetali 10003832d0abSNetali return $text; 10013832d0abSNetali } 10023832d0abSNetali} 10033832d0abSNetali 1004