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