1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Viktor Söderqvist <viktor@zuiderkwast.se> 5 * 6 * Translate - A translation plugin using Dublin Core metadata. No restriction to page IDs of the translations. 7 * 8 * Metadata keys used by this plugin are 9 * 10 * relation/istranslationof 11 * a list of source pages, normally only one (array: ID => language) 12 * relation/translations 13 * a list of translation pages (array: ID => language) 14 * language 15 * the language code of the page, 2-letter ISO code 16 */ 17 18// must be run within Dokuwiki 19if(!defined('DOKU_INC')) die(); 20if(!defined('DOKU_LF')) define ('DOKU_LF',"\n"); 21 22if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 23require_once(DOKU_PLUGIN.'action.php'); 24 25class action_plugin_translate extends DokuWiki_Action_Plugin { 26 27 /** 28 * register the eventhandlers 29 */ 30 public function register(Doku_Event_Handler $contr) { 31 $contr->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'handleDokuwikiStarted'); 32 $contr->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handleTplActUnknown'); 33 $contr->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handleActPreprocess'); 34 // Events for DW > 2020-07-29 "hogfather" 35 $contr->register_hook('FORM_EDIT_OUTPUT', 'BEFORE', $this, 'handleHtmlEditformOutput', []); 36 // Events for DW ≤ 2020-07-29 "hogfather" 37 $contr->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handleHtmlEditformOutput'); 38 // TODO: When a translation is deleted, delete it from the original's list of translations. 39 //$contr->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, 'handlePageWrite'); 40 $contr->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handleActRender'); 41 } 42 43 /** Ensure translators' permission to edit the page */ 44 public function handleDokuwikiStarted($event, $param) { 45 global $INFO; 46 $info = & $INFO; 47 48 // Is the user blocked from editing the page? 49 if (empty($_SERVER['REMOTE_USER']) || 50 empty($INFO['meta']['relation']['istranslationof']) || 51 $info['perm'] < AUTH_READ || 52 $info['perm'] >= AUTH_EDIT) return; 53 54 // Any translator group set? 55 $grp = $this->getConf('translator_group'); 56 if (empty($grp)) return; 57 58 // Is the current user member of that group? 59 if (!in_array($grp, $info['userinfo']['grps'])) return; 60 61 // Set permission to edit 62 $info['perm'] = AUTH_EDIT; 63 64 // Recreate writable and editable values (code from pageinfo in common.php) 65 if($info['exists']){ 66 $info['writable'] = (is_writable($info['filepath']) && 67 ($info['perm'] >= AUTH_EDIT)); 68 }else{ 69 $info['writable'] = ($info['perm'] >= AUTH_CREATE); 70 } 71 $info['editable'] = ($info['writable'] && empty($info['lock'])); 72 } 73 74 /** Insert translation links on top of page */ 75 public function handleActRender($event, $param) { 76 if ($event->data != 'show') return; 77 // show links to translations at top of page if that option is on. 78 if ($this->getConf('insert_translation_links')) { 79 $my = $this->loadHelper('translate',true); 80 echo $my->translationLinks(); 81 } 82 } 83 84 /** 85 * Hook for event ACTION_ACT_PREPROCESS, action 'translate'. 86 * 87 * Redirect to the translated page if there is one already. 88 */ 89 public function handleActPreprocess($event, $param) { 90 $act = $event->data; 91 if (is_array($act)) { 92 list($act) = array_keys($act); 93 } 94 switch ($act) { 95 case 'createpage': 96 $this->handleActPreprocessCreatepage($event, $param); 97 break; 98 case 'translate': 99 $this->handleActPreprocessTranslate($event, $param); 100 break; 101 default: 102 return; // not handled here 103 } 104 $event->preventDefault(); 105 $event->stopPropagation(); 106 } 107 108 /** 109 * Hook for event TPL_ACT_UNKNOWN, action 'translate' 110 * Show page with translation form (before the translated page is created) 111 */ 112 public function handleTplActUnknown($event, $param) { 113 global $ID, $INFO; // $PRE, $TEXT, $SUF, 114 if ($event->data == 'translate') { 115 $my = $this->loadHelper('translate',true); 116 $my->printActionTranslatePage(); 117 } 118 elseif ($event->data == 'createpage') { 119 $my = $this->loadHelper('translate',true); 120 $my->printActionCreatepagePage(); 121 } 122 else { 123 return; // not handled here 124 } 125 $event->preventDefault(); 126 $event->stopPropagation(); 127 } 128 129 130 /** 131 * Hook for event HTML_EDITFORM_OUTPUT (DW ≤ 2020-07-29 "hogfather") and 132 * event FORM_EDIT_OUTPUT (DW > 2020-07-29 "hogfather" ) 133 * Check and gather info on the untranslated page and the current form. 134 * If needed, call the method that adds our elements to the form. 135 */ 136 public function handleHtmlEditformOutput($event, $param) { 137 global $INFO, $ID; 138 139 // Original from meta 140 if (empty($INFO['meta']['relation']['istranslationof'])) return; 141 list ($id) = array_keys($INFO['meta']['relation']['istranslationof']); 142 143 // Get original content 144 $file = wikiFN($id); 145 if (!@file_exists($file)) { 146 msg('The original file for this translation does not exist. Perhaps it has been deleted.'); 147 return; 148 } 149 $origtext = io_readWikiPage($file,$id); 150 151 // Insert original page on the side 152 $form = $event->data; 153 if (is_a($form, \dokuwiki\Form\Form::class)) { 154 // DW > 2020-07-29 "hogfather" 155 $pos = $form->findPositionByType('textarea'); 156 if ($pos===false) return; 157 $this->handleHtmlEditformOutputNG($form, $pos, $origtext); 158 } else { 159 // DW ≤ 2020-07-29 "hogfather" 160 $pos = $form->findElementByType('wikitext'); 161 if ($pos===false) return; 162 $this->handleHtmlEditformOutputLegacy($form, $pos, $origtext); 163 } 164 } 165 166 /** 167 * Add our elements to the form. DW > 2020-07-29 "hogfather" 168 */ 169 protected function handleHtmlEditformOutputNG($form, $pos, $origtext) { 170 // Before the wikitext... 171 $form->addTagOpen('div', $pos++)->id('wrapper__wikitext')->addClass('hor'); 172 // After the wikitext... 173 $pos++; 174 $form->addTagClose('div', $pos++); 175 $form->addTagOpen('div', $pos++)->id('wrapper__sourcetext')->addClass('hor'); 176 177 $attrs=Array(); 178 $attrs['readonly']='readonly'; 179 $attrs['cols']=80; 180 $attrs['rows']=10; 181 $attrs['style']='width:100%;'; 182 $attrs['readonly']='readonly'; 183 $form->addTextarea('origWikitext', '', $pos++)->attrs($attrs)->val($origtext) 184 ->id('translate__sourcetext')->addClass('edit'); 185 186 $form->addTagClose('div', $pos++); 187 $form->addTagOpen('div', $pos++)->addClass('clearer'); 188 $form->addTagClose('div', $pos++); 189 } 190 191 /** 192 * Add our elements to the form. DW ≤ 2020-07-29 "hogfather" 193 */ 194 protected function handleHtmlEditformOutputLegacy($form, $pos, $origtext) { 195 // Before the wikitext... 196 $form->insertElement($pos++, form_makeOpenTag('div', array('id'=>'wrapper__wikitext','class'=>'hor'))); 197 // After the wikitext... 198 $pos++; 199 $form->insertElement($pos++, form_makeCloseTag('div')); 200 $form->insertElement($pos++, form_makeOpenTag('div', array('id'=>'wrapper__sourcetext','class'=>'hor'))); 201 $origelem = '<textarea id="translate__sourcetext" '. 202 //buildAttributes($attrs,true). 203 'class="edit" readonly="readonly" cols="80" rows="10"'. 204 'style="width:100%;"'.// height:600px; overflow:auto 205 '>'.NL. 206 hsc($origtext). 207 '</textarea>'; 208 $form->insertElement($pos++, $origelem); 209 $form->insertElement($pos++, form_makeCloseTag('div')); 210 $form->insertElement($pos++, form_makeOpenTag('div', array('class'=>'clearer'))); 211 $form->insertElement($pos++, form_makeCloseTag('div')); 212 } 213 214 public function handleActPreprocessCreatepage($event, $param) { 215 global $INFO; 216 $my = $this->loadHelper('translate',true); 217 $title = $_REQUEST['title']; 218 $lang = $_REQUEST['lang']; 219 220 // Check input 221 if (empty($title) || empty($lang)) { 222 // Not filled. Show form. 223 return; 224 } 225 226 // Illegal language 227 if (!$my->languageExists($lang)) { 228 msg(sprintf("Illegal language %s",$lang), -1); 229 return; 230 } 231 232 $id = $my->suggestPageId($title, $lang); 233 if (page_exists($id)) { 234 // Error message 235 //$this->_formErrors['title'] = 1; 236 msg(sprintf($this->getLang['e_pageexists'], $title),-1); 237 return; 238 } 239 240 // Check permission to create the page. 241 $auth = auth_quickaclcheck($id); 242 $auth_ok = ($auth >= AUTH_CREATE); 243 if (!$auth_ok && $auth >= AUTH_READ) { 244 // Check special translation permission 245 // Is the current user member of the translator group? 246 $grp = $this->getConf('author_group'); 247 $auth_ok = !empty($grp) && 248 in_array($grp, $INFO['userinfo']['grps']); 249 } 250 if (!$auth_ok) { 251 msg($this->getLang('e_denied'), -1); 252 return; 253 } 254 255 // Create and save page 256 $wikitext = "====== ".$title." ======".DOKU_LF.DOKU_LF; 257 saveWikiText($id, $wikitext, $GLOBALS['lang']['created']); //$this->getLang('translation_created')); 258 259 // Add metadata to the new page 260 $file = wikiFN($id); 261 $created = @filectime($file); 262 $meta = array(); 263 $meta['date']['created'] = $created; 264 $user = $_SERVER['REMOTE_USER']; 265 if ($user) $meta['creator'] = $INFO['userinfo']['name']; 266 $meta['language'] = $lang; 267 p_set_metadata($target_id, $meta); 268 269 // Redirect to edit the new page 270 // Should we trigger some event before redirecting to edit? 271 $url = wl($id, 'do=edit'); 272 send_redirect($url); 273 } 274 275 /** Handle translate action. Validates input and creates the translation page */ 276 public function handleActPreprocessTranslate($event, $param) { 277 global $ID, $INFO; 278 $my = $this->loadHelper('translate',true); 279 $target_title = $_REQUEST['title']; 280 $target_lang = $_REQUEST['to']; 281 $source_lang = $my->getPageLanguage(); 282 283 // Check if this is the original 284 if ($my->isTranslation()) { 285 $orig_id = $my->getOriginal(); 286 $param = array('do'=>'translate','to'=>$target_lang); 287 if ($target_title) $param['title'] = $target_title; 288 $url = wl($orig_id, $param, false, '&'); 289 send_redirect($url); 290 return; 291 } 292 293 // Check original language 294 if (!isset($source_lang)) { 295 // show error message and no form 296 msg($this->getLang('e_languageunknown'),-1); 297 return; 298 } 299 300 // Require target language 301 if (empty($target_lang)) { 302 // Not filled. Show form. 303 return; 304 } 305 306 // Translate to same language? Just show page. 307 if ($target_lang == $source_lang) { 308 $event->data = 'show'; 309 return; 310 } 311 312 // Illegal language 313 if (!$my->languageExists($target_lang)) { 314 msg(sprintf("Illegal language %s",$target_lang), -1); 315 return; 316 } 317 318 // Check existence of source page 319 if (!page_exists($ID)) { 320 // Just ignore and show "page does not exist". 321 $event->data = 'show'; 322 return; 323 } 324 325 // Check if already translated 326 $translated_id = $my->translationLookup($target_lang, $ID); 327 if (!empty($translated_id)) { 328 //$langname = $my->getLanguageName($target_lang); 329 //msg(sprintf($this->getLang('e_translationexists'), $langname)); 330 331 // Redirect to already translated page 332 $opts = array('id' => $translated_id, 'preact' => 'translate'); 333 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute'); 334 // This will redirect and exit. 335 } 336 337 // Require title 338 if (empty($target_title)) { 339 // Not filled. Show form. 340 return; 341 } 342 343 // Check if target page exists 344 $target_id = $my->suggestTranslationId($target_title, $target_lang, $source_lang); 345 if (page_exists($target_id)) { 346 // Error message 347 //$this->_formErrors['title'] = 1; 348 msg(sprintf($this->getLang('e_pageexists'), $target_id),-1); 349 return; 350 } 351 352 // Check permission to create the page. 353 $auth = auth_quickaclcheck($target_id); 354 $auth_ok = ($auth >= AUTH_CREATE); 355 if (!$auth_ok && $auth >= AUTH_READ) { 356 // Check special translation permission 357 // Is the current user member of the translator group? 358 $grp = $this->getConf('translator_group'); 359 $auth_ok = !empty($grp) && 360 in_array($grp, $INFO['userinfo']['grps']); 361 } 362 if (!$auth_ok) { 363 msg($this->getLang('e_denied'), -1); 364 return; 365 } 366 367 // Create and save page 368 $wikitext = "====== ".$target_title." ======".DOKU_LF.DOKU_LF; 369 saveWikiText($target_id, $wikitext, $this->getLang('translation_created')); 370 371 // Add metadata to the new page 372 $file = wikiFN($target_id); 373 $created = @filectime($file); 374 $meta = array(); 375 $meta['date']['created'] = $created; 376 $user = $_SERVER['REMOTE_USER']; 377 if ($user) $meta['creator'] = $INFO['userinfo']['name']; 378 $meta['relation']['istranslationof'][$ID] = $source_lang; 379 $meta['language'] = $target_lang; 380 p_set_metadata($target_id, $meta); 381 382 // Add metadata to the original 383 $meta = array('relation' => array('translations' => array($target_id => $target_lang))); 384 p_set_metadata($ID, $meta); 385 386 // Redirect to edit the new page 387 // Should we trigger some event before redirecting to edit? 388 $url = wl($target_id, 'do=edit'); 389 send_redirect($url); 390 } 391} 392// vim:ts=4:sw=4:et: 393