1<?php 2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/'); 3if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 4if(!defined('DOKU_MEDIA')) define('DOKU_MEDIA',DOKU_INC.'data/media/'); 5define ('BROKEN_IMAGE', DOKU_BASE . 'lib/plugins/ckgedit/fckeditor/userfiles/blink.jpg?nolink&33x34'); 6require_once(DOKU_PLUGIN.'action.php'); 7if(!defined('FCK_ACTION_SUBDIR')) define('FCK_ACTION_SUBDIR', realpath(dirname(__FILE__)) . '/'); 8/** 9 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 10 */ 11 12class action_plugin_ckgedit_save extends DokuWiki_Action_Plugin { 13 var $helper = false; 14 function register(Doku_Event_Handler $controller) { 15 16 $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'ckgedit_save_preprocess'); 17 } 18 19 function ckgedit_save_preprocess(Doku_Event $event){ 20 global $ACT,$INPUT; 21 $this->helper = $this->loadhelper('ckgedit'); 22 if (!isset($_REQUEST['ckgedit']) || ! is_array($ACT) || !(isset($ACT['save']) || isset($ACT['preview']))) return; 23 if (isset($_REQUEST["fontdel"]) ) { 24 msg($this->getLang("fontdel"),1); 25 } 26 if (isset($_REQUEST["formatdel"]) ) { 27 msg($this->getLang("formatdel"),1); 28 } 29 if (isset($_REQUEST["linkintbl"]) ) { 30 msg($this->getLang("list_in_table"),1); 31 } 32 $img_size = $INPUT->int('broken_image'); 33 if($img_size) msg($this->getLang('broken_image') . $img_size/1000000 . 'M' ); 34 35 36 global $TEXT, $conf; 37 38 if (!$TEXT) return; 39 $preserve_enc = $this->getConf('preserve_enc'); 40 $deaccent = $conf['deaccent'] == 0 ? false : true; 41 $TEXT = $_REQUEST['fck_wikitext']; 42 43 if(!preg_match('/^\s+(\-|\*)/',$TEXT)) { 44 $TEXT = trim($TEXT); 45 } 46 47 48 $TEXT = preg_replace_callback( 49 '|\{\{data:(.*?);base64|ms', 50 function($matches) { 51 if(!preg_match("/image/",$matches[1])) { 52 return "{{data:image/jpeg;base64"; 53 } 54 return $matches[0]; 55 },$TEXT); 56 57 if(strpos($TEXT,'data:image') !== false) { 58 $TEXT = preg_replace_callback( 59 '|\{\{(\s*)data:image\/(\w+;base64,\s*)(.*?)\?nolink&(\s*)\}\}|ms', 60 function($matches) { 61 list($ext,$base) = explode(";",$matches[2]); 62 if($ext == "jpeg" || $ext == "tiff") $ext = "jpg"; 63 if(function_exists("imagecreatefromstring") && !imagecreatefromstring (base64_decode($matches[3]))) { 64 msg("Clipboard paste: invalid $ext image format"); 65 return "{{" . BROKEN_IMAGE . "}}"; 66 } 67 global $INFO,$conf,$ID; 68 $fn = $this->get_imgpaste_fname($ext); 69 if(!$fn) { 70 $ns = getNS($INFO["id"]); 71 $ns = trim($ns); 72 if(!empty($ns)) { 73 $ns = ":$ns:"; 74 $dir = str_replace(":","/",$ns); 75 } 76 else { // root namespace 77 $dir = "/"; 78 $ns = ":"; 79 } 80 $fn = md5($matches[3]) . ".$ext"; 81 $path = $conf["mediadir"] . $dir . $fn; 82 } 83 else { 84 $path = $conf["mediadir"] . "/$fn"; 85 $path = str_replace(':','/',$path); 86 } 87 88 @io_makeFileDir($path); 89 if(!file_exists($path)) { 90 @file_put_contents($path, base64_decode($matches[3])); 91 global $lang; 92 $id = $dir . $fn; 93 $id = str_replace("/",":",$id); 94 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_CREATE, $lang["created"],"", null, strlen(base64_decode($matches[3]))); 95 } 96 else { 97 msg("file for this image previousely saved",2); 98 } 99 $left = "{{"; 100 $right = "}}"; 101 if($matches[1]) $left .= $matches[1]; 102 if($matches[4]) $right = $matches[4] . $right; 103 104 $retv = "$left" . $ns. $fn . "$right"; 105 return $retv; 106 }, 107 $TEXT 108 ); 109 } 110 $TEXT = str_replace('%%', "FCKGPERCENTESC", $TEXT); 111 112 if($deaccent || $preserve_enc) { 113 $TEXT = preg_replace_callback('/^(.*?)(\[\[.*?\]\])*(.*?)$/ms', 114 function($matches) { 115 $matches[1] = preg_replace("/%([A-F0-9]{1,3})/i", "URLENC_PERCENT$1", $matches[1]); 116 $matches[2] = preg_replace("/%([A-F0-9]{1,3})/i", "URLENC_PERCENT$1", $matches[2]); 117 $matches[3] = preg_replace("/%([A-F0-9]{1,3})/i", "URLENC_PERCENT$1", $matches[3]); 118 return $matches[1].$matches[2].$matches[3]; 119 }, 120 $TEXT 121 ); 122 } 123 124 $TEXT = rawurldecode($TEXT); 125 $TEXT = preg_replace('/NOWIKI_%_NOWIKI_%_/', '%%',$TEXT); 126 $TEXT = preg_replace('/URLENC_PERCENT/', '%',$TEXT); 127 $TEXT = preg_replace('/NOWIKI_(.)_/', '$1',$TEXT); 128 129 /* preserve newlines in code blocks */ 130 $TEXT = preg_replace_callback( 131 '/(<code>|<file>)(.*?)(<\/code>|<\/file>)/ms', 132 function($matches) { 133 return str_replace("\n", "__code_NL__",$matches[0]); 134 }, 135 $TEXT 136 ); 137 138 $TEXT = preg_replace('/^\s*[\r\n]$/ms',"__n__", $TEXT); 139 $TEXT = preg_replace('/oIWIKIo|cIWIKIc/ms',"", $TEXT); 140 $TEXT = preg_replace('/\r/ms',"", $TEXT); 141 $TEXT = preg_replace('/^\s+(?=\^|\|)/ms',"", $TEXT); 142 $TEXT = preg_replace('/__n__/',"\n", $TEXT); 143 $TEXT = str_replace("__code_NL__","\n", $TEXT); 144 $TEXT = str_replace("FCKGPERCENTESC", '%%', $TEXT); 145 if($this->getConf('complex_tables')) { 146 $TEXT = str_replace('~~COMPLEX_TABLES~~','',$TEXT); 147 } 148 $TEXT .= "\n"; 149 // Removes relics of markup characters left over after acronym markup has been removed 150 //$TEXT = preg_replace('/([\*\/_]{2})\s+\\1\s*([A-Z]+)\s*\\1+/ms',"$2",$TEXT); 151 152 $pos = strpos($TEXT, 'MULTI_PLUGIN_OPEN'); 153 if($pos !== false) { 154 $TEXT = preg_replace_callback( 155 '|MULTI_PLUGIN_OPEN.*?MULTI_PLUGIN_CLOSE|ms', 156 function($matches) { 157 return preg_replace("/\\\\\\\\/ms","\n",$matches[0]); 158 }, 159 $TEXT 160 ); 161 162 $TEXT = preg_replace_callback( 163 '|MULTI_PLUGIN_OPEN.*?MULTI_PLUGIN_CLOSE|ms', 164 function($matches) { 165 return preg_replace("/^\s+/ms","",$matches[0]); 166 }, 167 $TEXT 168 ); 169 $TEXT = str_replace("~~MULTI_PLUGIN_OPEN~~","~~MULTI_PLUGIN_OPEN~~\n",$TEXT); 170 } 171 172 if(strpos($TEXT,'L_PARgr') !== false) { 173 $TEXT = preg_replace_callback( 174 '|\(\((.*?)\)\)|ms', 175 function($matches) { 176 return "((" . trim($matches[1]) . "))"; 177 }, 178 $TEXT 179 ); 180 $TEXT = str_replace('L_PARgr', '(',$TEXT); 181 $TEXT = str_replace('R_PARgr', ')',$TEXT); 182 } 183/* 184Restructure numbered syntax highlighting 13/09/2019 185*/ 186$TEXT = preg_replace_callback("#<code\s+(\w+)>.*?(\[enable_line_numbers.*?\])\s*\*\/#ms", 187 function($matches) { 188 return '<code ' . $matches[1] .' ' . $matches[2] .'>'; 189 }, $TEXT 190) ; 191 192 $this->replace_entities(); 193 /*Remove urls from linkonly images inserted after second and additional saves, resulting in multiple urls corrupting HTML output */ 194 $TEXT = preg_replace("/\{\{http:\/\/.*?fetch.php\?media=(.*?linkonly.*?)\}\}/",'{{' . "$1" .'}}',$TEXT); 195 $TEXT = str_replace('< nowiki >', '%%<nowiki>%%',$TEXT); 196 197 198 $TEXT = preg_replace_callback( 199 '#\[\[(.*?)\]\]#ms', 200 function($matches){ 201 if($this->helper->has_plugin('button') && strpos($matches[0], '[[{') === 0) { 202 return $matches[0]; 203 } 204 if(preg_match('/[\w\.]+\s*>/',$matches[0])) { 205 return $matches[0]; 206 } 207 if(preg_match('/([\w\.\-]+@[\w\.\-]+\.\w{2,3})\?.*?\|\1/i',$matches[0])) { 208 return $matches[0]; 209 } 210 global $ID, $conf; 211 $qs = ""; 212 if(preg_match("/\[\[http/",$matches[0])) return $matches[0]; //not an internal link 213 if(preg_match("#\[\[.*?\|\{\{.*?\}\}\]\]#", $matches[0],$matches_1)) { // media file 214 if(!$this->getConf('rel_links')) { 215 return $matches[0]; 216 } 217 $link = explode('?',$matches[1]); 218 list($link_id,$linktext) = explode('|', $link[0]); 219 $current_id = $this->abs2rel($link_id,$ID); 220 return preg_replace("#$link_id#",$current_id, $matches[0]); 221 } 222 223 $link = explode('?',$matches[1]); 224 if($link[1]) { 225 $link_id = $link[0]; 226 list($qs,$linktext) = explode('|', $link[1]); 227 } 228 else list($link_id,$linktext) = explode('|', $link[0]); 229 if($this->getConf('rel_links')) 230 $current_id = $this->abs2rel($link_id,$ID); 231 else $current_id = $link_id; 232 if($qs) $current_id .= "?$qs"; 233 234 //as in _getLinkTitle in xhtml.php 235 if(useHeading('content')) { 236 $tmp_linktext = p_get_first_heading($link_id); 237 if(trim($linktext) == trim($tmp_linktext)) { 238 $linktext = ""; 239 } 240 } 241 $tmp_ar = explode(':',$link_id); 242 $tmp_id = array_pop($tmp_ar); 243 if(!useHeading('content') && (trim($linktext,'.: ' ) == trim($tmp_id,'.: '))) 244 $linktext = ""; 245 246 $current_id = $current_id.'|'.$linktext; 247 return '[[' . $current_id .']]'; 248 }, 249 $TEXT 250 ); 251 252 if($this->getConf('rel_links')) { 253 $TEXT = preg_replace_callback( 254 '#\{\{(\s*)(.*?)(\s*)\}\}#ms', 255 function($matches) { 256 global $ID; 257 $link = explode('?',$matches[2]); 258 list($link_id,$linktext) = explode('|', $link[0]); 259 $rel = $this->abs2rel($link_id,$ID); 260 if(!empty($link[1])) $rel .= '?' . $link[1]; 261 if(!empty($linktext)) $rel = $rel.'|'.$linktext; 262 return '{{' .$matches[1] . $rel . $matches[3] .'}}'; 263 }, 264 $TEXT 265 ); 266 } 267 268/* 11 Dec 2013 see comment below 269Remove discarded font syntax 270*/ 271 $TEXT = preg_replace_callback( 272 '|_REMOVE_FONTS_START_(.*?)_REMOVE_FONTS_END_|ms', 273 function($matches) { 274 $matches[1] = preg_replace("/<font.*?>/ms","",$matches[1]); 275 return preg_replace("/<\/font>/ms","",$matches[1]); 276 }, 277 $TEXT 278 ); 279 280 /* 2816 April 2013 282Removed newlines and spaces from beginnings and ends of text enclosed by font tags. Too subtle for javascript. 283 */ 284 $TEXT = preg_replace_callback( 285 '|(<font.*?>)(.*?)(?=</font>)|ms', 286 function($matches) { 287 $matches[2]=preg_replace("/^\s+/ms","",$matches[2]); 288 $matches[2]=preg_replace("/\s+$/ms","",$matches[2]); 289 return $matches[1]. $matches[2]; 290 }, 291 $TEXT 292 ); 293 /* insure space before and after ckgedit font oprning and closing tags*/ 294 $TEXT = preg_replace("/<font.*?>\s+<\/font>/","", $TEXT); 295 $TEXT = preg_replace("/<font/ms"," <font", $TEXT); // add space 296 $TEXT = preg_replace("/<\/font>/ms","</font> ", $TEXT); 297 $TEXT = preg_replace('/\s{2,}<font/ms',"\n <font",$TEXT); // remove duplicate spaces 298 $TEXT = preg_replace('/font>{2,}/ms',' font> ',$TEXT); 299 300 $TEXT = preg_replace('/__QUOTE__/ms',">",$TEXT); 301 $TEXT = preg_replace('/[\t\x20]+$/ms',"",$TEXT); 302 $TEXT = preg_replace('/\n{4,}/ms',"\n\n",$TEXT); 303 $TEXT = preg_replace('/\n{3,}/ms',"\n\n",$TEXT); 304 305 /*first pass for blockquotes*/ 306 $TEXT = preg_replace_callback( 307 "#^>+(.*?)\\\\\\\\#ms", 308 function($matches) { 309 return str_replace('\\',"",$matches[0]); 310 }, 311 $TEXT 312 ); 313 314 /* remove extra line-feeds following in-table code blocks 315 make sure cell-ending pipe not mistaken for a following link divider 316 */ 317 $TEXT = preg_replace_callback( 318 '#(/code|/file)\>.*?\n\|#ms', 319 function($matches) { 320 $matches[0] = preg_replace("/([\S\s\w\:])\\\\\\\\(\w)/ms","$1@#@$2",$matches[0]); //retain backslashes inside code blocks 321 $matches[0] = preg_replace("/(\w+)\\\\\\\\(\w)/ms","$1@#@",$matches[0]); 322 $matches[0] = preg_replace("/\\\\(\w+)/ms","@!@$1",$matches[0]); 323 $matches[0] = preg_replace("/(\w+)\\\\/ms","@!@$1",$matches[0]); 324 $matches[0] = str_replace("\\", "",$matches[0]); 325 $matches[0] = str_replace("@!@",'\\',$matches[0]); 326 return str_replace("@#@", "\\\\",$matches[0]); 327 }, 328 $TEXT 329 ); 330 /* reformat table cell after removing extra line-feeds, above */ 331 $TEXT = preg_replace_callback( 332 '#\|[\s\n]+(\<file.*?\>)(.*?)(\<\/file>\s*.*?)\n?\|#ms', 333 function($matches) { 334 //$ret = '</' . $matches[1] . '>' . str_replace('\\',"",$matches[2]) . '|'; 335 $matches[3] = preg_replace('/\n+/',"",$matches[3] ); 336 $matches[3] = preg_replace('/\s+$/',"",$matches[3] ) . '|'; 337 return '|' . $matches[1] . $matches[2] . str_replace("\\ ","",$matches[3]); 338 }, 339 $TEXT 340 ); 341 342 343 /* Feb 23 2019 344 remove spaces and line feeds between beginning of table cell and start of code block 345 */ 346 $TEXT = preg_replace_callback( 347 '#\|(.*?)[\s\n]+\<(code|file)\>#ms', 348 function($matches) { 349 return '|' . $matches[1] ."\n<". $matches[2] .'>' . "\n"; 350 },$TEXT 351 ); 352 /*remove line feeds following block */ 353 $TEXT = preg_replace_callback( 354 '#\<\/(code|file)\>([^\w\|]+)#ms', 355 function($matches) { 356 $matches[2] = str_replace(':\\', '~~WIN__DIR~~',$matches[2]); 357 $matches[2] = preg_replace('#([\w;.:=\:])\\\\#ms', "$1_bSL_",$matches[2]); //protect backslashes in Windows paths 358 $ret = '</' . $matches[1] . '>' . str_replace("\\","",$matches[2]); 359 $ret = str_replace( '_bSL_', '\\',$ret); 360 $ret = str_replace( '~~WIN__DIR~~', ':\\',$ret); 361 return "\n" .$ret; 362 },$TEXT 363 ); 364 $TEXT = str_replace('CBL__Bksl','\\',$TEXT); 365 $TEXT = preg_replace("/<code\s+file/ms",'<code ',$TEXT); 366 367 $TEXT = preg_replace('#((\\\\){2}\s*)$#', "",$TEXT); 368 369 return; 370 371 } 372 373 function get_imgpaste_fname($ext) { 374 global $ID; 375 if(!$this->helper->has_plugin('imgpaste')) return; 376 if(!$this->getConf('imgpaste')) return; 377 $imgpaste = plugin_load('action','imgpaste'); 378 $filename = $imgpaste->getConf('filename'); 379 if(!$filename) return false; 380 $filename = str_replace( 381 array( 382 '@NS@', 383 '@ID@', 384 '@USER@', 385 '@PAGE@' 386 ), 387 array( 388 getNS($ID),// getNS($INPUT->post->str('id')), 389 $ID,// $INPUT->post->str('id'), 390 $_SERVER['REMOTE_USER'], 391 noNS($ID) //noNS($INPUT->post->str('id')) 392 ), 393 $filename 394 ); 395 $filename = strftime($filename); 396 $filename = cleanID($filename); 397 return $filename . '.' . $ext; 398} 399 400function replace_entities() { 401global $TEXT; 402global $ents; 403 $serialized = FCK_ACTION_SUBDIR . 'ent.ser'; 404 $ents = unserialize(file_get_contents($serialized)); 405 406 $TEXT = preg_replace_callback( 407 '|(&(\w+);)|', 408 function($matches) { 409 global $ents; return $ents[$matches[2]]; 410 }, 411 $TEXT 412 ); 413 414} 415 416 417function write_debug($data) { 418 return; 419 if (!$handle = fopen(DOKU_INC . 'save.txt', 'a')) { 420 return; 421 } 422 423 // Write $somecontent to our opened file. 424 fwrite($handle, "save.php: $data\n"); 425 fclose($handle); 426 427} 428/* @auth Sergey Kotov */ 429//linkPath is the link in the page 430//pagePath is absolute path of the page (ns1:ns2:....:page or :ns1:ns2:....:page) 431function abs2rel($linkPath,$pagePath){ 432 if ($linkPath[0]==='.'){ 433 // It's already relative 434 return $linkPath; 435 } 436 $aLink=explode(':',$linkPath); 437 $nLink=count($aLink); 438 if ($nLink<2){ 439 return $linkPath; 440 } 441 $aPage=explode(':',$pagePath); 442 if(empty($aLink[0])) { 443 // If linkPath is started by ':' 444 // Make canonical absolute path ns1:ns2:.....:pageLink (strip leading :) 445 array_shift($aLink); 446 if (--$nLink<2) { 447 return $linkPath; 448 } 449 } 450 451 if(empty($aPage[0])) { 452 // If pagePath is started by ':' 453 // Make canonical absolute path ns1:ns2:.....:page (strip leading :) 454 array_shift($aPage); 455 } 456 $nPage=count($aPage); 457 $nslEqual=0; // count of equal namespaces from left to right 458 // Minimal length of these two arrays, page name is not included 459 $nMin=($nLink<$nPage ? $nLink : $nPage)-1 ; 460 for ($i=0;$i<$nMin;++$i){ 461 if ($aLink[$i]===$aPage[$i]){ 462 ++$nslEqual; 463 } 464 else { 465 break; 466 } 467 } 468 if ($nslEqual==0){ 469 // Link and page from different root namespaces 470 return $linkPath; 471 } 472 // Truncate equal lef namespaces 473 $aPageDiff=array_slice($aPage,$nslEqual); 474 $nPageDiff=count($aPageDiff); 475 $aLinkDiff=array_slice($aLink,$nslEqual); 476 477 // Now we have to go up to nPageDiff-1 levels 478 $aResult=array(); 479 if ($nPageDiff>1){ 480 $aResult=array_fill(0,$nPageDiff-1,'..'); 481 } 482 else if($nPageDiff == 1) { 483 $aResult[] = '.'; 484 } 485 $aResult=array_merge($aResult,$aLinkDiff); 486 return implode(':', $aResult); 487} 488 489 490} //end of action class 491?> 492