]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
new edit_convert button, to get rid of old markup eventually
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.86 2004-12-11 14:50:15 rurban Exp $');
3
4 require_once('lib/Template.php');
5
6 // USE_HTMLAREA - Support for some WYSIWYG HTML Editor
7 // Not yet enabled, since we cannot convert HTML to Wiki Markup yet.
8 // (See HtmlParser.php for the ongoing efforts)
9 // We might use a HTML PageType, which is contra wiki, but some people might prefer HTML markup.
10 // TODO: Change from constant to user preference variable (checkbox setting),
11 //       when HtmlParser is finished.
12 if (!defined('USE_HTMLAREA')) define('USE_HTMLAREA', false);
13 if (USE_HTMLAREA) require_once('lib/htmlarea.php');
14
15 class PageEditor
16 {
17     function PageEditor (&$request) {
18         $this->request = &$request;
19
20         $this->user = $request->getUser();
21         $this->page = $request->getPage();
22
23         $this->current = $this->page->getCurrentRevision(false);
24
25         // HACKish short circuit to browse on action=create
26         if ($request->getArg('action') == 'create') {
27             if (! $this->current->hasDefaultContents()) 
28                 $request->redirect(WikiURL($this->page->getName())); // noreturn
29         }
30         
31         
32         $this->meta = array('author' => $this->user->getId(),
33                             'author_id' => $this->user->getAuthenticatedId(),
34                             'mtime' => time());
35         
36         $this->tokens = array();
37         
38        $version = $request->getArg('version');
39         if ($version !== false) {
40             $this->selected = $this->page->getRevision($version);
41             $this->version = $version;
42         }
43         else {
44             $this->version = $this->current->getVersion();
45             $this->selected = $this->page->getRevision($this->version);
46         }
47
48         if ($this->_restoreState()) {
49             $this->_initialEdit = false;
50         }
51         else {
52             $this->_initializeState();
53             $this->_initialEdit = true;
54
55             // The edit request has specified some initial content from a template 
56             if (  ($template = $request->getArg('template')) and 
57                   $request->_dbi->isWikiPage($template)) {
58                 $page = $request->_dbi->getPage($template);
59                 $current = $page->getCurrentRevision();
60                 $this->_content = $current->getPackedContent();
61             } elseif ($initial_content = $request->getArg('initial_content')) {
62                 $this->_content = $initial_content;
63                 $this->_redirect_to = $request->getArg('save_and_redirect_to');
64             }
65         }
66         if (!headers_sent())
67             header("Content-Type: text/html; charset=" . $GLOBALS['charset']);
68     }
69
70     function editPage () {
71         global $WikiTheme;
72         $saveFailed = false;
73         $tokens = &$this->tokens;
74
75         if (! $this->canEdit()) {
76             if ($this->isInitialEdit())
77                 return $this->viewSource();
78             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
79         }
80         elseif ($this->request->getArg('save_and_redirect_to') != "") {
81             if ($this->savePage()) {
82                 // noreturn
83                 $this->request->redirect(WikiURL($this->request->getArg('save_and_redirect_to')));
84                 return true;    // Page saved.
85             }
86             $saveFailed = true;
87         }
88         elseif ($this->editaction == 'save') {
89             if ($this->savePage()) {
90                 return true;    // Page saved.
91             }
92             $saveFailed = true;
93         }
94
95         if ($saveFailed and $this->isConcurrentUpdate())
96         {
97             // Get the text of the original page, and the two conflicting edits
98             // The diff3 class takes arrays as input.  So retrieve content as
99             // an array, or convert it as necesary.
100             $orig = $this->page->getRevision($this->_currentVersion);
101             // FIXME: what if _currentVersion has be deleted?
102             $orig_content = $orig->getContent();
103             $this_content = explode("\n", $this->_content);
104             $other_content = $this->current->getContent();
105             include_once("lib/diff3.php");
106             $diff = new diff3($orig_content, $this_content, $other_content);
107             $output = $diff->merged_output(_("Your version"), _("Other version"));
108             // Set the content of the textarea to the merged diff
109             // output, and update the version
110             $this->_content = implode ("\n", $output);
111             $this->_currentVersion = $this->current->getVersion();
112             $this->version = $this->_currentVersion;
113             $unresolved = $diff->ConflictingBlocks;
114             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
115         } elseif ($saveFailed) {
116             $tokens['CONCURRENT_UPDATE_MESSAGE'] = 
117                 HTML(HTML::h2(_("Some internal editing error")),
118                      HTML::p(_("Your are probably trying to edit/create an invalid version of this page.")),
119                      HTML::p(HTML::em(_("&version=-1 might help."))));
120         }
121
122         if ($this->editaction == 'edit_convert')
123             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
124         if ($this->editaction == 'preview')
125             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
126
127         // FIXME: NOT_CURRENT_MESSAGE?
128         $tokens = array_merge($tokens, $this->getFormElements());
129
130         //FIXME: enable Undo button for all other buttons also, not only the search/replace button
131         if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
132             $tokens['JS_SEARCHREPLACE'] = 1;
133             $undo_btn = $WikiTheme->getImageURL("ed_undo.png"); 
134             $undo_d_btn = $WikiTheme->getImageURL("ed_undo_d.png"); 
135             // JS_SEARCHREPLACE from walterzorn.de
136             $WikiTheme->addMoreHeaders(Javascript("
137 var f, sr_undo, replacewin, undo_buffer=new Array(), undo_buffer_index=0;
138
139 function define_f() {
140    f=document.getElementById('editpage');
141    f.editarea=document.getElementById('edit[content]');
142    sr_undo=document.getElementById('sr_undo');
143    undo_enable(false);
144    f.editarea.focus();
145 }
146 function undo_enable(bool) {
147    if (bool) {
148      sr_undo.src='".$undo_btn."';
149      sr_undo.alt='"
150 ._("Undo")
151 ."';
152      sr_undo.disabled = false;
153    } else {
154      sr_undo.src='".$undo_d_btn."';
155      sr_undo.alt='"
156 ._("Undo disabled")
157 ."';
158      sr_undo.disabled = true;
159      if(sr_undo.blur) sr_undo.blur();
160   }
161 }
162 function replace() {
163    replacewin = window.open('','','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,height=90,width=450');
164    replacewin.window.document.write('<html><head><title>"
165 ._("Search & Replace")
166 ."</title><style type=\"text/css\"><'+'!'+'-- body, input {font-family:Tahoma,Arial,Helvetica,sans-serif;font-size:10pt;font-weight:bold;} td {font-size:9pt}  --'+'></style></head><body bgcolor=\"#dddddd\" onload=\"if(document.forms[0].ein.focus) document.forms[0].ein.focus(); return false;\"><form><center><table><tr><td align=\"right\">'+'"
167 ._("Search")
168 .":</td><td align=\"left\"><input type=\"text\" name=\"ein\" size=\"45\" maxlength=\"500\"></td></tr><tr><td align=\"right\">'+' "
169 ._("Replace with")
170 .":</td><td align=\"left\"><input type=\"text\" name=\"aus\" size=\"45\" maxlength=\"500\"></td></tr><tr><td colspan=\"2\" align=\"center\"><input type=\"button\" value=\" "
171 ._("OK")
172 ." \" onclick=\"if(self.opener)self.opener.do_replace(); return false;\">&nbsp;&nbsp;&nbsp;<input type=\"button\" value=\""
173 ._("Close")
174 ."\" onclick=\"self.close(); return false;\"></td></tr></table></center></form></body></html>');
175    replacewin.window.document.close();
176    return false;
177 }
178 function do_replace() {
179    var txt=undo_buffer[undo_buffer_index]=f.editarea.value, ein=new RegExp(replacewin.document.forms[0].ein.value,'g'), aus=replacewin.document.forms[0].aus.value;
180    if (ein==''||ein==null) {
181       if (replacewin) replacewin.window.document.forms[0].ein.focus();
182       return;
183    }
184    var z_repl=txt.match(ein)? txt.match(ein).length : 0;
185    txt=txt.replace(ein,aus);
186    ein=ein.toString().substring(1,ein.toString().length-2);
187    result(z_repl, 'Substring \"'+ein+'\" found '+z_repl+' times. Replace with \"'+aus+'\"?', txt, 'String \"'+ein+'\" not found.');
188    replacewin.window.focus();
189    replacewin.window.document.forms[0].ein.focus();
190    return false;
191 }
192 function result(zahl,frage,txt,alert_txt) {
193    if (zahl>0) {
194       if(window.confirm(frage)==true) {
195          f.editarea.value=txt;
196          undo_save();
197          undo_enable(true);
198       }
199    } else alert(alert_txt);
200 }
201 function do_undo() {
202    if(undo_buffer_index==0) return;
203    else if(undo_buffer_index>0) {
204       f.editarea.value=undo_buffer[undo_buffer_index-1];
205       undo_buffer[undo_buffer_index]=null;
206       undo_buffer_index--;
207       if(undo_buffer_index==0) {
208          alert('".
209 _("Operation undone")
210 ."');
211          undo_enable(false);
212       }
213    }
214 }
215 //save a snapshot in the undo buffer (unused)
216 function undo_save() {
217    undo_buffer[undo_buffer_index]=f.editarea.value;
218    undo_buffer_index++;
219    undo_enable(true);
220 }
221 "));
222             $WikiTheme->addMoreAttr('body'," onload='define_f()'");
223         } else {
224             $WikiTheme->addMoreAttr('body',"document.getElementById('edit[content]').editarea.focus()");
225         }
226         if (defined('ENABLE_EDIT_TOOLBAR') and ENABLE_EDIT_TOOLBAR) {
227             $WikiTheme->addMoreHeaders(JavaScript('',array('src' => $WikiTheme->_findData("toolbar.js"))));
228             $tokens['EDIT_TOOLBAR'] = $this->toolbar();
229         } else {
230             $tokens['EDIT_TOOLBAR'] = '';
231         }
232
233         return $this->output('editpage', _("Edit: %s"));
234     }
235
236     function toolbar () {
237         global $WikiTheme;
238         $toolarray = array(
239                            array(
240                                  "image"=>"ed_format_bold.png",
241                                  "open"=>"*",
242                                  "close"=>"*",
243                                  "sample"=>_("Bold text"),
244                                  "tip"=>_("Bold text")),
245                            array("image"=>"ed_format_italic.png",
246                                  "open"=>"_",
247                                  "close"=>"_",
248                                  "sample"=>_("Italic text"),
249                                  "tip"=>_("Italic text")),
250                            array("image"=>"ed_pagelink.png",
251                                  "open"=>"[",
252                                  "close"=>"]",
253                                  "sample"=>_("optional label | PageName"),
254                                  "tip"=>_("Link to page")),
255                            array("image"=>"ed_link.png",
256                                  "open"=>"[",
257                                  "close"=>"]",
258                                  "sample"=>_("optional label | http://www.example.com"),
259                                  "tip"=>_("External link (remember http:// prefix)")),
260                            array("image"=>"ed_headline.png",
261                                  "open"=>"\\n!!! ",
262                                  "close"=>"\\n",
263                                  "sample"=>_("Headline text"),
264                                  "tip"=>_("Level 1 headline")),
265                            array("image"=>"ed_image.png",
266                                  "open"=>"[ ",
267                                  "close"=>" ]",
268                                  "sample"=>_("Example.jpg"),
269                                  "tip"=>_("Embedded image")),
270                            array("image"=>"ed_nowiki.png",
271                                  "open"=>"\\n\\<verbatim\\>\\n",
272                                  "close"=>"\\n\\</verbatim\\>\\n",
273                                  "sample"=>_("Insert non-formatted text here"),
274                                  "tip"=>_("Ignore wiki formatting")),
275                            array("image"=>"ed_sig.png",
276                                  "open" => " --" . $GLOBALS['request']->_user->UserName(),
277                                  "close" => "",
278                                  "sample"=>"",
279                                  "tip"=>_("Your signature")),
280                            array("image"=>"ed_hr.png",
281                                  "open"=>"\\n----\\n",
282                                  "close"=>"",
283                                  "sample"=>"",
284                                  "tip"=>_("Horizontal line"))
285                            );
286         $toolbar = "document.writeln(\"<div class=\\\"edit-toolbar\\\" id=\\\"toolbar\\\">\");\n";
287
288         $btn = new SubmitImageButton(_("Save"), "edit[save]", 'toolbar', $WikiTheme->getImageURL("ed_save.png"));
289         $btn->addTooltip(_("Save"));
290         $toolbar.='document.writeln("'.addslashes($btn->asXml()).'");'."\n";
291         $btn = new SubmitImageButton(_("Preview"), "edit[preview]", 'toolbar', $WikiTheme->getImageURL("ed_preview.png"));
292         $btn->addTooltip(_("Preview"));
293         $toolbar.='document.writeln("'.addslashes($btn->asXml()).'");'."\n";
294
295         foreach ($toolarray as $tool) {
296             $image = $WikiTheme->getImageURL($tool["image"]);
297             $open  = $tool["open"];
298             $close = $tool["close"];
299             $sample = addslashes( $tool["sample"] );
300             // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
301             // Older browsers show a "speedtip" type message only for ALT.
302             // Ideally these should be different, realistically they
303             // probably don't need to be.
304             $tip = addslashes( $tool["tip"] );
305             $toolbar.="addTagButton('$image','$tip','$open','$close','$sample');\n";
306         }
307         $toolbar.="addInfobox('" . addslashes( _("Click a button to get an example text") ) . "');\n";
308
309         if (JS_SEARCHREPLACE) {
310             $undo_d_btn = $WikiTheme->getImageURL("ed_undo_d.png"); 
311             //$redo_btn = $WikiTheme->getImageURL("ed_redo.png");
312             $sr_btn   = $WikiTheme->getImageURL("ed_replace.png");
313             $sr_html = HTML(HTML::img(array('class'=>"toolbar",
314                                             'id'   =>"sr_undo",
315                                             'src'  =>$undo_d_btn,
316                                             'title'=>_("Undo Search & Replace"),
317                                             'alt'  =>_("Undo Search & Replace"),
318                                             'disabled'=>"disabled", 
319                                             'value'   =>"Undo",
320                                             'onfocus' =>"if(this.blur && undo_buffer_index==0) this.blur()",
321                                             'onclick' =>"do_undo()")),
322                             HTML::img(array('class'=>"toolbar",
323                                             'src'  => $sr_btn,
324                                             'alt'  =>_("Search & Replace"),
325                                             'title'=>_("Search & Replace"),
326                                             'onclick'=>"replace()")));
327         } else {
328             $sr_html = '';
329         }
330         // More:
331         // Button to generate pagenames, display in extra window as pulldown and insert
332         // Button to generate plugins, display in extra window as pulldown and insert
333         // Button to generate categories, display in extra window as pulldown and insert
334         if (DEBUG and JS_SEARCHREPLACE) {
335             //TODO: delegate this calculation to a seperate pulldown action request
336             require_once('lib/TextSearchQuery.php');
337             $dbi =& $GLOBALS['request']->_dbi;
338             $pages = $dbi->titleSearch(new TextSearchQuery(''.
339                                                            _("Category").' OR '.
340                                                            _("Topic").''));
341             if ($pages->count()) {
342                 $categories = array();
343                 while ($p = $pages->next()){
344                     $categories[] = $p->getName();
345                 }
346                 $more_buttons = HTML::img(array('class'=>"toolbar",
347                                                 'src'  => $WikiTheme->getImageURL("ed_category.png"),
348                                                 'title'=>_("AddCategory"),
349                                                 'onclick'=>"showPulldown('".
350                                                 _("Insert Categories (double-click)")
351                                                 ."',['".join("','",$categories)."'],'"
352                                                 ._("Insert")."','"
353                                                 ._("Close")
354                                                 ."')"));
355                 if ($sr_html) $sr_html = HTML($sr_html, $more_buttons);
356                 else $sr_html = $more_buttons;
357             }
358         }
359         $toolbar_end = "document.writeln(\"</div>\");";
360         // don't use document.write for replace, otherwise self.opener is not defined.
361         if ($sr_html)
362             return HTML(Javascript($toolbar),
363                         "\n",$sr_html,
364                         Javascript($toolbar_end));
365         else
366             return HTML(Javascript($toolbar . $toolbar_end));
367     }
368
369     function output ($template, $title_fs) {
370         global $WikiTheme;
371         $selected = &$this->selected;
372         $current = &$this->current;
373
374         if ($selected && $selected->getVersion() != $current->getVersion()) {
375             $rev = $selected;
376             $pagelink = WikiLink($selected);
377         }
378         else {
379             $rev = $current;
380             $pagelink = WikiLink($this->page);
381         }
382
383
384         $title = new FormattedText ($title_fs, $pagelink);
385         if (USE_HTMLAREA and $template == 'editpage') {
386             $WikiTheme->addMoreHeaders(Edit_HtmlArea_Head());
387             //$tokens['PAGE_SOURCE'] = Edit_HtmlArea_ConvertBefore($this->_content);
388         }
389         $template = Template($template, $this->tokens);
390         GeneratePage($template, $title, $rev);
391         return true;
392     }
393
394
395     function viewSource () {
396         assert($this->isInitialEdit());
397         assert($this->selected);
398
399         $this->tokens['PAGE_SOURCE'] = $this->_content;
400         return $this->output('viewsource', _("View Source: %s"));
401     }
402
403     function updateLock() {
404         if ((bool)$this->page->get('locked') == (bool)$this->locked)
405             return false;       // Not changed.
406
407         if (!$this->user->isAdmin()) {
408             // FIXME: some sort of message
409             return false;         // not allowed.
410         }
411
412         $this->page->set('locked', (bool)$this->locked);
413         $this->tokens['LOCK_CHANGED_MSG']
414             = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
415
416         return true;            // lock changed.
417     }
418
419     function savePage () {
420         $request = &$this->request;
421
422         if ($this->isUnchanged()) {
423             // Allow admin lock/unlock even if
424             // no text changes were made.
425             if ($this->updateLock()) {
426                 $dbi = $request->getDbh();
427                 $dbi->touch();
428             }
429             // Save failed. No changes made.
430             $this->_redirectToBrowsePage();
431             // user will probably not see the rest of this...
432             include_once('lib/display.php');
433             // force browse of current version:
434             $request->setArg('version', false);
435             displayPage($request, 'nochanges');
436             return true;
437         }
438
439         if ($this->isSpam()) {
440             return false;
441             /*
442             // Save failed. No changes made.
443             $this->_redirectToBrowsePage();
444             // user will probably not see the rest of this...
445             include_once('lib/display.php');
446             // force browse of current version:
447             $request->setArg('version', false);
448             displayPage($request, 'nochanges');
449             return true;
450             */
451         }
452
453         $page = &$this->page;
454
455         // Include any meta-data from original page version which
456         // has not been explicitly updated.
457         // (Except don't propagate pgsrc_version --- moot for now,
458         //  because at present it never gets into the db...)
459         $meta = $this->selected->getMetaData();
460         unset($meta['pgsrc_version']);
461         $meta = array_merge($meta, $this->meta);
462         
463         // Save new revision
464         $this->_content = $this->getContent();
465         $newrevision = $page->save($this->_content, 
466                                    $this->version == -1 ? -1 : $this->_currentVersion + 1, // force new?
467                                    $meta);
468         if (!isa($newrevision, 'WikiDB_PageRevision')) {
469             // Save failed.  (Concurrent updates).
470             return false;
471         }
472         
473         // New contents successfully saved...
474         $this->updateLock();
475
476         // Clean out archived versions of this page.
477         include_once('lib/ArchiveCleaner.php');
478         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
479         $cleaner->cleanPageRevisions($page);
480
481         /* generate notification emails done in WikiDB::save to catch all direct calls 
482           (admin plugins) */
483
484         $dbi = $request->getDbh();
485         $warnings = $dbi->GenericWarnings();
486         $dbi->touch();
487         
488         global $WikiTheme;
489         if (empty($warnings) && ! $WikiTheme->getImageURL('signature')) {
490             // Do redirect to browse page if no signature has
491             // been defined.  In this case, the user will most
492             // likely not see the rest of the HTML we generate
493             // (below).
494             $this->_redirectToBrowsePage();
495         }
496
497         // Force browse of current page version.
498         $request->setArg('version', false);
499         //$request->setArg('action', false);
500
501         $template = Template('savepage', $this->tokens);
502         $template->replace('CONTENT', $newrevision->getTransformedContent());
503         if (!empty($warnings))
504             $template->replace('WARNINGS', $warnings);
505
506         $pagelink = WikiLink($page);
507
508         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
509         return true;
510     }
511
512     function isConcurrentUpdate () {
513         assert($this->current->getVersion() >= $this->_currentVersion);
514         return $this->current->getVersion() != $this->_currentVersion;
515     }
516
517     function canEdit () {
518         return !$this->page->get('locked') || $this->user->isAdmin();
519     }
520
521     function isInitialEdit () {
522         return $this->_initialEdit;
523     }
524
525     function isUnchanged () {
526         $current = &$this->current;
527
528         if ($this->meta['markup'] !=  $current->get('markup'))
529             return false;
530
531         return $this->_content == $current->getPackedContent();
532     }
533
534     /** 
535      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
536      * Need to check dynamically some blacklist wikipage settings (plugin WikiAccessRestrictions)
537      * and some static blacklist.
538      * DONE: 
539      *   More then 20 new external links
540      *   content patterns by babycart (only php >= 4.3 for now)
541      * TODO:
542      *   IP BlackList 
543      *   domain blacklist
544      *   url patterns
545      */
546     function isSpam () {
547         $current = &$this->current;
548         $request = &$this->request;
549
550         $oldtext = $current->getPackedContent();
551         $newtext =& $this->_content;
552         // 1. Not more then 20 new external links
553         if ($this->numLinks($newtext) - $this->numLinks($oldtext) >= 20) {
554             // mail the admin?
555             $this->tokens['PAGE_LOCKED_MESSAGE'] = 
556                 HTML($this->getSpamMessage(),
557                      HTML::p(HTML::em(_("Too many external links."))));
558             return true;
559         }
560         // 2. external babycart (SpamAssassin) check
561         // This will probably prevent from discussing sex or viagra related topics. So beware.
562         if (ENABLE_SPAMASSASSIN) {
563             $user = $request->getUser();
564             include_once("lib/spam_babycart.php");
565             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"), $user->getId())) {
566                 // mail the admin?
567                 if (is_array($babycart))
568                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
569                         HTML($this->getSpamMessage(),
570                              HTML::p(HTML::em(_("SpamAssassin reports: ", join("\n", $babycart)))));
571                 return true;
572             }
573         }
574         return false;
575     }
576
577     /** Number of external links in the wikitext
578      */
579     function numLinks(&$text) {
580         return substr_count($text, "http://");
581     }
582
583     /** Header of the Anti Spam message 
584      */
585     function getSpamMessage () {
586         return
587             HTML(HTML::h2(_("Spam Prevention")),
588                  HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
589                          HTML::br(),
590                          _("Sorry for the inconvenience.")),
591                  HTML::p(""));
592     }
593
594     function getPreview () {
595         include_once('lib/PageType.php');
596         $this->_content = $this->getContent();
597         return new TransformedText($this->page, $this->_content, $this->meta);
598     }
599
600     function getConvertedPreview () {
601         include_once('lib/PageType.php');
602         $this->_content = $this->getContent();
603         $this->meta['markup'] = 2.0;
604         $this->_content = ConvertOldMarkup($this->_content);
605         return new TransformedText($this->page, $this->_content, $this->meta);
606     }
607
608     // possibly convert HTMLAREA content back to Wiki markup
609     function getContent () {
610         if (USE_HTMLAREA) {
611             $xml_output = Edit_HtmlArea_ConvertAfter($this->_content);
612             $this->_content = join("", $xml_output->_content);
613             return $this->_content;
614         } else {
615             return $this->_content;
616         }
617     }
618
619     function getLockedMessage () {
620         return
621             HTML(HTML::h2(_("Page Locked")),
622                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
623                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
624                  HTML::p(_("Sorry for the inconvenience.")));
625     }
626
627     function getConflictMessage ($unresolved = false) {
628         /*
629          xgettext only knows about c/c++ line-continuation strings
630          it does not know about php's dot operator.
631          We want to translate this entire paragraph as one string, of course.
632          */
633
634         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
635
636         if ($unresolved)
637             $message =  HTML::p(fmt("Some of the changes could not automatically be combined.  Please look for sections beginning with '%s', and ending with '%s'.  You will need to edit those sections by hand before you click Save.",
638                                 "<<<<<<< ". _("Your version"),
639                                 ">>>>>>> ". _("Other version")));
640         else
641             $message = HTML::p(_("Please check it through before saving."));
642
643
644
645         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
646           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
647                        $re_edit_link)),
648           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
649           HTML::li(_("Save your updated changes.")));
650         */
651         return
652             HTML(HTML::h2(_("Conflicting Edits!")),
653                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
654                  HTML::p(_("Your changes can not be saved as they are, since doing so would overwrite the other author's changes. So, your changes and those of the other author have been combined. The result is shown below.")),
655                  $message);
656     }
657
658
659     function getTextArea () {
660         $request = &$this->request;
661
662         // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
663         // long lines
664         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
665         if (USE_HTMLAREA) {
666             $html = $this->getPreview();
667             $this->_wikicontent = $this->_content;
668             $this->_content = $html->asXML();
669         }
670
671         /** <textarea wrap="virtual"> is not valid xhtml but Netscape 4 requires it
672          * to wrap long lines.
673          */
674         $textarea = HTML::textarea(array('class' => 'wikiedit',
675                                          'name' => 'edit[content]',
676                                          'id'   => 'edit[content]',
677                                          'rows' => $request->getPref('editHeight'),
678                                          'cols' => $request->getPref('editWidth'),
679                                          'readonly' => (bool) $readonly),
680                                    $this->_content);
681         if (isBrowserNS4())
682             $textarea->setAttr('wrap', 'virtual');
683         if (USE_HTMLAREA)
684             return Edit_HtmlArea_Textarea($textarea,$this->_wikicontent,'edit[content]');
685         else
686             return $textarea;
687     }
688
689     /**
690      * TODO: maybe support an uploadfile button.
691      */
692     function getFormElements () {
693         $request = &$this->request;
694         $page = &$this->page;
695
696         $h = array('action'   => 'edit',
697                    'pagename' => $page->getName(),
698                    'version'  => $this->version,
699                    'edit[pagetype]' => $this->meta['pagetype'],
700                    'edit[current_version]' => $this->_currentVersion);
701
702         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
703         $el['EDIT_TEXTAREA'] = $this->getTextArea();
704         $el['SUMMARY_INPUT']
705             = HTML::input(array('type'  => 'text',
706                                 'class' => 'wikitext',
707                                 'name'  => 'edit[summary]',
708                                 'size'  => 50,
709                                 'maxlength' => 256,
710                                 'value' => $this->meta['summary']));
711         $el['MINOR_EDIT_CB']
712             = HTML::input(array('type' => 'checkbox',
713                                 'name'  => 'edit[minor_edit]',
714                                 'checked' => (bool) $this->meta['is_minor_edit']));
715         $el['OLD_MARKUP_CB']
716             = HTML::input(array('type' => 'checkbox',
717                                 'name' => 'edit[markup]',
718                                 'value' => 'old',
719                                 'checked' => $this->meta['markup'] < 2.0,
720                                 'id' => 'useOldMarkup',
721                                 'onclick' => 'showOldMarkupRules(this.checked)'));
722         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0) ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
723         $el['LOCKED_CB']
724             = HTML::input(array('type' => 'checkbox',
725                                 'name' => 'edit[locked]',
726                                 'disabled' => (bool) !$this->user->isadmin(),
727                                 'checked'  => (bool) $this->locked));
728
729         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
730                                   'wikiaction');
731
732         //if (!$this->isConcurrentUpdate() && $this->canEdit())
733         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
734
735         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
736
737         return $el;
738     }
739
740     function _redirectToBrowsePage() {
741         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
742     }
743
744     function _restoreState () {
745         $request = &$this->request;
746
747         $posted = $request->getArg('edit');
748         $request->setArg('edit', false);
749
750         if (!$posted || !$request->isPost()
751             || $request->getArg('action') != 'edit')
752             return false;
753
754         if (!isset($posted['content']) || !is_string($posted['content']))
755             return false;
756         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
757                                         rtrim($posted['content']));
758         $this->_content = $this->getContent();
759
760         $this->_currentVersion = (int) $posted['current_version'];
761
762         if ($this->_currentVersion < 0)
763             return false;
764         if ($this->_currentVersion > $this->current->getVersion())
765             return false;       // FIXME: some kind of warning?
766
767         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
768         $meta['markup'] = $is_old_markup ? false : 2.0;
769         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
770         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
771         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
772         $this->meta = array_merge($this->meta, $meta);
773         $this->locked = !empty($posted['locked']);
774
775         if (!empty($posted['preview']))
776             $this->editaction = 'preview';
777         elseif (!empty($posted['save']))
778             $this->editaction = 'save';
779         elseif (!empty($posted['edit_convert']))
780             $this->editaction = 'edit_convert';
781         else
782             $this->editaction = 'edit';
783
784         return true;
785     }
786
787     function _initializeState () {
788         $request = &$this->request;
789         $current = &$this->current;
790         $selected = &$this->selected;
791         $user = &$this->user;
792
793         if (!$selected)
794             NoSuchRevision($request, $this->page, $this->version); // noreturn
795
796         $this->_currentVersion = $current->getVersion();
797         $this->_content = $selected->getPackedContent();
798
799         $this->meta['summary'] = '';
800         $this->locked = $this->page->get('locked');
801
802         // If author same as previous author, default minor_edit to on.
803         $age = $this->meta['mtime'] - $current->get('mtime');
804         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
805                                          && $current->get('author') == $user->getId()
806                                          );
807
808         // Default for new pages is new-style markup.
809         if ($selected->hasDefaultContents())
810             $is_new_markup = true;
811         else
812             $is_new_markup = $selected->get('markup') >= 2.0;
813
814         $this->meta['markup'] = $is_new_markup ? 2.0: false;
815         $this->meta['pagetype'] = $selected->get('pagetype');
816         $this->editaction = 'edit';
817     }
818 }
819
820 class LoadFileConflictPageEditor
821 extends PageEditor
822 {
823     function editPage ($saveFailed = true) {
824         $tokens = &$this->tokens;
825
826         if (!$this->canEdit()) {
827             if ($this->isInitialEdit())
828                 return $this->viewSource();
829             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
830         }
831         elseif ($this->editaction == 'save') {
832             if ($this->savePage())
833                 return true;    // Page saved.
834             $saveFailed = true;
835         }
836
837         if ($saveFailed || $this->isConcurrentUpdate())
838         {
839             // Get the text of the original page, and the two conflicting edits
840             // The diff class takes arrays as input.  So retrieve content as
841             // an array, or convert it as necesary.
842             $orig = $this->page->getRevision($this->_currentVersion);
843             $this_content = explode("\n", $this->_content);
844             $other_content = $this->current->getContent();
845             include_once("lib/diff.php");
846             $diff2 = new Diff($other_content, $this_content);
847             $context_lines = max(4, count($other_content) + 1,
848                                  count($this_content) + 1);
849             $fmt = new BlockDiffFormatter($context_lines);
850
851             $this->_content = $fmt->format($diff2);
852             // FIXME: integrate this into class BlockDiffFormatter
853             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
854                                           $this->_content);
855             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
856                                           $this->_content);
857
858             $this->_currentVersion = $this->current->getVersion();
859             $this->version = $this->_currentVersion;
860             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
861         }
862
863         if ($this->editaction == 'edit_convert')
864             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
865         if ($this->editaction == 'preview')
866             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
867
868         // FIXME: NOT_CURRENT_MESSAGE?
869
870         $tokens = array_merge($tokens, $this->getFormElements());
871
872         return $this->output('editpage', _("Merge and Edit: %s"));
873         // FIXME: this doesn't display
874     }
875
876     function output ($template, $title_fs) {
877         $selected = &$this->selected;
878         $current = &$this->current;
879
880         if ($selected && $selected->getVersion() != $current->getVersion()) {
881             $rev = $selected;
882             $pagelink = WikiLink($selected);
883         }
884         else {
885             $rev = $current;
886             $pagelink = WikiLink($this->page);
887         }
888
889         $title = new FormattedText ($title_fs, $pagelink);
890         $template = Template($template, $this->tokens);
891
892         //GeneratePage($template, $title, $rev);
893         PrintXML($template);
894         return true;
895     }
896     function getConflictMessage () {
897         $message = HTML(HTML::p(fmt("Some of the changes could not automatically be combined.  Please look for sections beginning with '%s', and ending with '%s'.  You will need to edit those sections by hand before you click Save.",
898                                     "<<<<<<<",
899                                     "======="),
900                                 HTML::p(_("Please check it through before saving."))));
901         return $message;
902     }
903 }
904
905 /**
906  $Log: not supported by cvs2svn $
907  Revision 1.85  2004/12/06 19:49:56  rurban
908  enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
909  renamed delete_page to purge_page.
910  enable action=edit&version=-1 to force creation of a new version.
911  added BABYCART_PATH config
912  fixed magiqc in adodb.inc.php
913  and some more docs
914
915  Revision 1.84  2004/12/04 12:58:26  rurban
916  enable babycart Blog::SpamAssassin module on ENABLE_SPAMASSASSIN=true
917  (currently only for php >= 4.3.0)
918
919  Revision 1.83  2004/12/04 11:55:39  rurban
920  First simple AntiSpam prevention:
921    No more than 20 new http:// links allowed
922
923  Revision 1.82  2004/11/30 22:21:56  rurban
924  changed gif to optimized (pngout) png
925
926  Revision 1.81  2004/11/29 17:57:27  rurban
927  translated pulldown buttons
928
929  Revision 1.80  2004/11/25 17:20:51  rurban
930  and again a couple of more native db args: backlinks
931
932  Revision 1.79  2004/11/21 11:59:20  rurban
933  remove final \n to be ob_cache independent
934
935  Revision 1.78  2004/11/16 17:57:45  rurban
936  fix search&replace button
937  use new addTagButton machinery
938  new showPulldown for categories, TODO: in a seperate request
939
940  Revision 1.77  2004/11/15 15:52:35  rurban
941  improve js stability
942
943  Revision 1.76  2004/11/15 15:37:34  rurban
944  fix JS_SEARCHREPLACE
945    don't use document.write for replace, otherwise self.opener is not defined.
946
947  Revision 1.75  2004/09/16 08:00:52  rurban
948  just some comments
949
950  Revision 1.74  2004/07/03 07:36:28  rurban
951  do not get unneccessary content
952
953  Revision 1.73  2004/06/16 21:23:44  rurban
954  fixed non-object fatal #215
955
956  Revision 1.72  2004/06/14 11:31:37  rurban
957  renamed global $Theme to $WikiTheme (gforge nameclash)
958  inherit PageList default options from PageList
959    default sortby=pagename
960  use options in PageList_Selectable (limit, sortby, ...)
961  added action revert, with button at action=diff
962  added option regex to WikiAdminSearchReplace
963
964  Revision 1.71  2004/06/03 18:06:29  rurban
965  fix file locking issues (only needed on write)
966  fixed immediate LANG and THEME in-session updates if not stored in prefs
967  advanced editpage toolbars (search & replace broken)
968
969  Revision 1.70  2004/06/02 20:47:47  rurban
970  dont use the wikiaction class
971
972  Revision 1.69  2004/06/02 10:17:56  rurban
973  integrated search/replace into toolbar
974  added save+preview buttons
975
976  Revision 1.68  2004/06/01 15:28:00  rurban
977  AdminUser only ADMIN_USER not member of Administrators
978  some RateIt improvements by dfrankow
979  edit_toolbar buttons
980
981  Revision _1.6  2004/05/26 15:48:00  syilek
982  fixed problem with creating page with slashes from one true page
983
984  Revision _1.5  2004/05/25 16:51:53  syilek
985  added ability to create a page from the category page and not have to edit it
986
987  Revision 1.67  2004/05/27 17:49:06  rurban
988  renamed DB_Session to DbSession (in CVS also)
989  added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
990  remove leading slash in error message
991  added force_unlock parameter to File_Passwd (no return on stale locks)
992  fixed adodb session AffectedRows
993  added FileFinder helpers to unify local filenames and DATA_PATH names
994  editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
995
996  Revision 1.66  2004/04/29 23:25:12  rurban
997  re-ordered locale init (as in 1.3.9)
998  fixed loadfile with subpages, and merge/restore anyway
999    (sf.net bug #844188)
1000
1001  Revision 1.65  2004/04/18 01:11:52  rurban
1002  more numeric pagename fixes.
1003  fixed action=upload with merge conflict warnings.
1004  charset changed from constant to global (dynamic utf-8 switching)
1005
1006  Revision 1.64  2004/04/06 19:48:56  rurban
1007  temp workaround for action=edit AddComment form
1008
1009  Revision 1.63  2004/03/24 19:39:02  rurban
1010  php5 workaround code (plus some interim debugging code in XmlElement)
1011    php5 doesn't work yet with the current XmlElement class constructors,
1012    WikiUserNew does work better than php4.
1013  rewrote WikiUserNew user upgrading to ease php5 update
1014  fixed pref handling in WikiUserNew
1015  added Email Notification
1016  added simple Email verification
1017  removed emailVerify userpref subclass: just a email property
1018  changed pref binary storage layout: numarray => hash of non default values
1019  print optimize message only if really done.
1020  forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
1021    prefs should be stored in db or homepage, besides the current session.
1022
1023  Revision 1.62  2004/03/17 18:41:05  rurban
1024  initial_content and template support for CreatePage
1025
1026  Revision 1.61  2004/03/12 20:59:17  rurban
1027  important cookie fix by Konstantin Zadorozhny
1028  new editpage feature: JS_SEARCHREPLACE
1029
1030  Revision 1.60  2004/02/15 21:34:37  rurban
1031  PageList enhanced and improved.
1032  fixed new WikiAdmin... plugins
1033  editpage, Theme with exp. htmlarea framework
1034    (htmlarea yet committed, this is really questionable)
1035  WikiUser... code with better session handling for prefs
1036  enhanced UserPreferences (again)
1037  RecentChanges for show_deleted: how should pages be deleted then?
1038
1039  Revision 1.59  2003/12/07 20:35:26  carstenklapp
1040  Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
1041  error: Call to undefined function: gettransformedcontent() in
1042  /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
1043  205.
1044
1045  Revision 1.58  2003/03/10 18:25:22  dairiki
1046  Bug/typo fix.  If you use the edit page to un/lock a page, it
1047  failed with: Fatal error: Call to a member function on a
1048  non-object in editpage.php on line 136
1049
1050  Revision 1.57  2003/02/26 03:40:22  dairiki
1051  New action=create.  Essentially the same as action=edit, except that if the
1052  page already exists, it falls back to action=browse.
1053
1054  This is for use in the "question mark" links for unknown wiki words
1055  to avoid problems and confusion when following links from stale pages.
1056  (If the "unknown page" has been created in the interim, the user probably
1057  wants to view the page before editing it.)
1058
1059  Revision 1.56  2003/02/21 18:07:14  dairiki
1060  Minor, nitpicky, currently inconsequential changes.
1061
1062  Revision 1.55  2003/02/21 04:10:58  dairiki
1063  Fixes for new cached markup.
1064  Some minor code cleanups.
1065
1066  Revision 1.54  2003/02/16 19:47:16  dairiki
1067  Update WikiDB timestamp when editing or deleting pages.
1068
1069  Revision 1.53  2003/02/15 23:20:27  dairiki
1070  Redirect back to browse current version of page upon save,
1071  even when no changes were made.
1072
1073  Revision 1.52  2003/01/03 22:22:00  carstenklapp
1074  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
1075
1076  Revision 1.51  2003/01/03 02:43:26  carstenklapp
1077  New class LoadFileConflictPageEditor, for merging / comparing a loaded
1078  pgsrc file with an existing page.
1079
1080  */
1081
1082 // Local Variables:
1083 // mode: php
1084 // tab-width: 8
1085 // c-basic-offset: 4
1086 // c-hanging-comment-ender-p: nil
1087 // indent-tabs-mode: nil
1088 // End:
1089 ?>