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