You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

492 lines
19 KiB

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