]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
First simple AntiSpam prevention:
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.83 2004-12-04 11:55:39 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             $this->tokens['PAGE_LOCKED_MESSAGE'] = $this->getSpamMessage();
434             return false;
435             /*
436             // Save failed. No changes made.
437             $this->_redirectToBrowsePage();
438             // user will probably not see the rest of this...
439             include_once('lib/display.php');
440             // force browse of current version:
441             $request->setArg('version', false);
442             displayPage($request, 'nochanges');
443             return true;
444             */
445         }
446
447         $page = &$this->page;
448
449         // Include any meta-data from original page version which
450         // has not been explicitly updated.
451         // (Except don't propagate pgsrc_version --- moot for now,
452         //  because at present it never gets into the db...)
453         $meta = $this->selected->getMetaData();
454         unset($meta['pgsrc_version']);
455         $meta = array_merge($meta, $this->meta);
456         
457         // Save new revision
458         $this->_content = $this->getContent();
459         $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
460         if (!isa($newrevision, 'wikidb_pagerevision')) {
461             // Save failed.  (Concurrent updates).
462             return false;
463         }
464         
465         // New contents successfully saved...
466         $this->updateLock();
467
468         // Clean out archived versions of this page.
469         include_once('lib/ArchiveCleaner.php');
470         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
471         $cleaner->cleanPageRevisions($page);
472
473         /* generate notification emails done in WikiDB::save to catch all direct calls 
474           (admin plugins) */
475
476         $dbi = $request->getDbh();
477         $warnings = $dbi->GenericWarnings();
478         $dbi->touch();
479         
480         global $WikiTheme;
481         if (empty($warnings) && ! $WikiTheme->getImageURL('signature')) {
482             // Do redirect to browse page if no signature has
483             // been defined.  In this case, the user will most
484             // likely not see the rest of the HTML we generate
485             // (below).
486             $this->_redirectToBrowsePage();
487         }
488
489         // Force browse of current page version.
490         $request->setArg('version', false);
491         //$request->setArg('action', false);
492
493         $template = Template('savepage', $this->tokens);
494         $template->replace('CONTENT', $newrevision->getTransformedContent());
495         if (!empty($warnings))
496             $template->replace('WARNINGS', $warnings);
497
498         $pagelink = WikiLink($page);
499
500         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
501         return true;
502     }
503
504     function isConcurrentUpdate () {
505         assert($this->current->getVersion() >= $this->_currentVersion);
506         return $this->current->getVersion() != $this->_currentVersion;
507     }
508
509     function canEdit () {
510         return !$this->page->get('locked') || $this->user->isAdmin();
511     }
512
513     function isInitialEdit () {
514         return $this->_initialEdit;
515     }
516
517     function isUnchanged () {
518         $current = &$this->current;
519
520         if ($this->meta['markup'] !=  $current->get('markup'))
521             return false;
522
523         return $this->_content == $current->getPackedContent();
524     }
525
526     /** 
527      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
528      * Need to check dynamically some blacklist content (plugin WikiAccessRestrictions)
529      * DONE: 
530      *   More then 20 new external links
531      * TODO: 
532      *   IP BlackList 
533      *   domain blacklist
534      *   url patterns
535      *   content patterns by babycart
536      */
537     function isSpam () {
538         $current = &$this->current;
539         $oldtext = $current->getPackedContent();
540         $newtext =& $this->_content;
541         if ($this->numLinks($newtext) - $this->numLinks($oldtext) >= 20)
542             return true;
543         return false;
544     }
545
546     function numLinks(&$text) {
547         return substr_count($text, "http://");
548     }
549
550     function getSpamMessage () {
551         return
552             HTML(HTML::h2(_("Spam Prevention")),
553                  HTML::p(_("This page edit seems to contain spam and was therefore not saved.")),
554                  HTML::p(_("")),
555                  HTML::p(_("Sorry for the inconvenience.")));
556     }
557
558     function getPreview () {
559         include_once('lib/PageType.php');
560         $this->_content = $this->getContent();
561         return new TransformedText($this->page, $this->_content, $this->meta);
562     }
563
564     // possibly convert HTMLAREA content back to Wiki markup
565     function getContent () {
566         if (USE_HTMLAREA) {
567             $xml_output = Edit_HtmlArea_ConvertAfter($this->_content);
568             $this->_content = join("",$xml_output->_content);
569             return $this->_content;
570         } else {
571             return $this->_content;
572         }
573     }
574
575     function getLockedMessage () {
576         return
577             HTML(HTML::h2(_("Page Locked")),
578                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
579                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
580                  HTML::p(_("Sorry for the inconvenience.")));
581     }
582
583     function getConflictMessage ($unresolved = false) {
584         /*
585          xgettext only knows about c/c++ line-continuation strings
586          it does not know about php's dot operator.
587          We want to translate this entire paragraph as one string, of course.
588          */
589
590         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
591
592         if ($unresolved)
593             $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.",
594                                 "<<<<<<< ". _("Your version"),
595                                 ">>>>>>> ". _("Other version")));
596         else
597             $message = HTML::p(_("Please check it through before saving."));
598
599
600
601         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
602           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
603                        $re_edit_link)),
604           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
605           HTML::li(_("Save your updated changes.")));
606         */
607         return
608             HTML(HTML::h2(_("Conflicting Edits!")),
609                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
610                  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.")),
611                  $message);
612     }
613
614
615     function getTextArea () {
616         $request = &$this->request;
617
618         // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
619         // long lines
620         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
621         if (USE_HTMLAREA) {
622             $html = $this->getPreview();
623             $this->_wikicontent = $this->_content;
624             $this->_content = $html->asXML();
625         }
626
627         /** <textarea wrap="virtual"> is not valid xhtml but Netscape 4 requires it
628          * to wrap long lines.
629          */
630         $textarea = HTML::textarea(array('class' => 'wikiedit',
631                                          'name' => 'edit[content]',
632                                          'id'   => 'edit[content]',
633                                          'rows' => $request->getPref('editHeight'),
634                                          'cols' => $request->getPref('editWidth'),
635                                          'readonly' => (bool) $readonly),
636                                    $this->_content);
637         if (isBrowserNS4())
638             $textarea->setAttr('wrap', 'virtual');
639         if (USE_HTMLAREA)
640             return Edit_HtmlArea_Textarea($textarea,$this->_wikicontent,'edit[content]');
641         else
642             return $textarea;
643     }
644
645     /**
646      * TODO: maybe support an uploadfile button.
647      */
648     function getFormElements () {
649         $request = &$this->request;
650         $page = &$this->page;
651
652         $h = array('action'   => 'edit',
653                    'pagename' => $page->getName(),
654                    'version'  => $this->version,
655                    'edit[pagetype]' => $this->meta['pagetype'],
656                    'edit[current_version]' => $this->_currentVersion);
657
658         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
659         $el['EDIT_TEXTAREA'] = $this->getTextArea();
660         $el['SUMMARY_INPUT']
661             = HTML::input(array('type'  => 'text',
662                                 'class' => 'wikitext',
663                                 'name'  => 'edit[summary]',
664                                 'size'  => 50,
665                                 'maxlength' => 256,
666                                 'value' => $this->meta['summary']));
667         $el['MINOR_EDIT_CB']
668             = HTML::input(array('type' => 'checkbox',
669                                 'name'  => 'edit[minor_edit]',
670                                 'checked' => (bool) $this->meta['is_minor_edit']));
671         $el['OLD_MARKUP_CB']
672             = HTML::input(array('type' => 'checkbox',
673                                 'name' => 'edit[markup]',
674                                 'value' => 'old',
675                                 'checked' => $this->meta['markup'] < 2.0,
676                                 'id' => 'useOldMarkup',
677                                 'onclick' => 'showOldMarkupRules(this.checked)'));
678
679         $el['LOCKED_CB']
680             = HTML::input(array('type' => 'checkbox',
681                                 'name' => 'edit[locked]',
682                                 'disabled' => (bool) !$this->user->isadmin(),
683                                 'checked'  => (bool) $this->locked));
684
685         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
686                                   'wikiaction');
687
688         //if (!$this->isConcurrentUpdate() && $this->canEdit())
689         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
690
691         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
692
693         return $el;
694     }
695
696     function _redirectToBrowsePage() {
697         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
698     }
699
700     function _restoreState () {
701         $request = &$this->request;
702
703         $posted = $request->getArg('edit');
704         $request->setArg('edit', false);
705
706         if (!$posted || !$request->isPost()
707             || $request->getArg('action') != 'edit')
708             return false;
709
710         if (!isset($posted['content']) || !is_string($posted['content']))
711             return false;
712         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
713                                         rtrim($posted['content']));
714         $this->_content = $this->getContent();
715
716         $this->_currentVersion = (int) $posted['current_version'];
717
718         if ($this->_currentVersion < 0)
719             return false;
720         if ($this->_currentVersion > $this->current->getVersion())
721             return false;       // FIXME: some kind of warning?
722
723         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
724         $meta['markup'] = $is_old_markup ? false : 2.0;
725         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
726         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
727         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
728         $this->meta = array_merge($this->meta, $meta);
729         $this->locked = !empty($posted['locked']);
730
731         if (!empty($posted['preview']))
732             $this->editaction = 'preview';
733         elseif (!empty($posted['save']))
734             $this->editaction = 'save';
735         else
736             $this->editaction = 'edit';
737
738         return true;
739     }
740
741     function _initializeState () {
742         $request = &$this->request;
743         $current = &$this->current;
744         $selected = &$this->selected;
745         $user = &$this->user;
746
747         if (!$selected)
748             NoSuchRevision($request, $this->page, $this->version); // noreturn
749
750         $this->_currentVersion = $current->getVersion();
751         $this->_content = $selected->getPackedContent();
752
753         $this->meta['summary'] = '';
754         $this->locked = $this->page->get('locked');
755
756         // If author same as previous author, default minor_edit to on.
757         $age = $this->meta['mtime'] - $current->get('mtime');
758         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
759                                          && $current->get('author') == $user->getId()
760                                          );
761
762         // Default for new pages is new-style markup.
763         if ($selected->hasDefaultContents())
764             $is_new_markup = true;
765         else
766             $is_new_markup = $selected->get('markup') >= 2.0;
767
768         $this->meta['markup'] = $is_new_markup ? 2.0: false;
769         $this->meta['pagetype'] = $selected->get('pagetype');
770         $this->editaction = 'edit';
771     }
772 }
773
774 class LoadFileConflictPageEditor
775 extends PageEditor
776 {
777     function editPage ($saveFailed = true) {
778         $tokens = &$this->tokens;
779
780         if (!$this->canEdit()) {
781             if ($this->isInitialEdit())
782                 return $this->viewSource();
783             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
784         }
785         elseif ($this->editaction == 'save') {
786             if ($this->savePage())
787                 return true;    // Page saved.
788             $saveFailed = true;
789         }
790
791         if ($saveFailed || $this->isConcurrentUpdate())
792         {
793             // Get the text of the original page, and the two conflicting edits
794             // The diff class takes arrays as input.  So retrieve content as
795             // an array, or convert it as necesary.
796             $orig = $this->page->getRevision($this->_currentVersion);
797             $this_content = explode("\n", $this->_content);
798             $other_content = $this->current->getContent();
799             include_once("lib/diff.php");
800             $diff2 = new Diff($other_content, $this_content);
801             $context_lines = max(4, count($other_content) + 1,
802                                  count($this_content) + 1);
803             $fmt = new BlockDiffFormatter($context_lines);
804
805             $this->_content = $fmt->format($diff2);
806             // FIXME: integrate this into class BlockDiffFormatter
807             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
808                                           $this->_content);
809             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
810                                           $this->_content);
811
812             $this->_currentVersion = $this->current->getVersion();
813             $this->version = $this->_currentVersion;
814             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
815         }
816
817         if ($this->editaction == 'preview')
818             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
819
820         // FIXME: NOT_CURRENT_MESSAGE?
821
822         $tokens = array_merge($tokens, $this->getFormElements());
823
824         return $this->output('editpage', _("Merge and Edit: %s"));
825         // FIXME: this doesn't display
826     }
827
828     function output ($template, $title_fs) {
829         $selected = &$this->selected;
830         $current = &$this->current;
831
832         if ($selected && $selected->getVersion() != $current->getVersion()) {
833             $rev = $selected;
834             $pagelink = WikiLink($selected);
835         }
836         else {
837             $rev = $current;
838             $pagelink = WikiLink($this->page);
839         }
840
841         $title = new FormattedText ($title_fs, $pagelink);
842         $template = Template($template, $this->tokens);
843
844         //GeneratePage($template, $title, $rev);
845         PrintXML($template);
846         return true;
847     }
848     function getConflictMessage () {
849         $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.",
850                                     "<<<<<<<",
851                                     "======="),
852                                 HTML::p(_("Please check it through before saving."))));
853         return $message;
854     }
855 }
856
857 /**
858  $Log: not supported by cvs2svn $
859  Revision 1.82  2004/11/30 22:21:56  rurban
860  changed gif to optimized (pngout) png
861
862  Revision 1.81  2004/11/29 17:57:27  rurban
863  translated pulldown buttons
864
865  Revision 1.80  2004/11/25 17:20:51  rurban
866  and again a couple of more native db args: backlinks
867
868  Revision 1.79  2004/11/21 11:59:20  rurban
869  remove final \n to be ob_cache independent
870
871  Revision 1.78  2004/11/16 17:57:45  rurban
872  fix search&replace button
873  use new addTagButton machinery
874  new showPulldown for categories, TODO: in a seperate request
875
876  Revision 1.77  2004/11/15 15:52:35  rurban
877  improve js stability
878
879  Revision 1.76  2004/11/15 15:37:34  rurban
880  fix JS_SEARCHREPLACE
881    don't use document.write for replace, otherwise self.opener is not defined.
882
883  Revision 1.75  2004/09/16 08:00:52  rurban
884  just some comments
885
886  Revision 1.74  2004/07/03 07:36:28  rurban
887  do not get unneccessary content
888
889  Revision 1.73  2004/06/16 21:23:44  rurban
890  fixed non-object fatal #215
891
892  Revision 1.72  2004/06/14 11:31:37  rurban
893  renamed global $Theme to $WikiTheme (gforge nameclash)
894  inherit PageList default options from PageList
895    default sortby=pagename
896  use options in PageList_Selectable (limit, sortby, ...)
897  added action revert, with button at action=diff
898  added option regex to WikiAdminSearchReplace
899
900  Revision 1.71  2004/06/03 18:06:29  rurban
901  fix file locking issues (only needed on write)
902  fixed immediate LANG and THEME in-session updates if not stored in prefs
903  advanced editpage toolbars (search & replace broken)
904
905  Revision 1.70  2004/06/02 20:47:47  rurban
906  dont use the wikiaction class
907
908  Revision 1.69  2004/06/02 10:17:56  rurban
909  integrated search/replace into toolbar
910  added save+preview buttons
911
912  Revision 1.68  2004/06/01 15:28:00  rurban
913  AdminUser only ADMIN_USER not member of Administrators
914  some RateIt improvements by dfrankow
915  edit_toolbar buttons
916
917  Revision _1.6  2004/05/26 15:48:00  syilek
918  fixed problem with creating page with slashes from one true page
919
920  Revision _1.5  2004/05/25 16:51:53  syilek
921  added ability to create a page from the category page and not have to edit it
922
923  Revision 1.67  2004/05/27 17:49:06  rurban
924  renamed DB_Session to DbSession (in CVS also)
925  added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
926  remove leading slash in error message
927  added force_unlock parameter to File_Passwd (no return on stale locks)
928  fixed adodb session AffectedRows
929  added FileFinder helpers to unify local filenames and DATA_PATH names
930  editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
931
932  Revision 1.66  2004/04/29 23:25:12  rurban
933  re-ordered locale init (as in 1.3.9)
934  fixed loadfile with subpages, and merge/restore anyway
935    (sf.net bug #844188)
936
937  Revision 1.65  2004/04/18 01:11:52  rurban
938  more numeric pagename fixes.
939  fixed action=upload with merge conflict warnings.
940  charset changed from constant to global (dynamic utf-8 switching)
941
942  Revision 1.64  2004/04/06 19:48:56  rurban
943  temp workaround for action=edit AddComment form
944
945  Revision 1.63  2004/03/24 19:39:02  rurban
946  php5 workaround code (plus some interim debugging code in XmlElement)
947    php5 doesn't work yet with the current XmlElement class constructors,
948    WikiUserNew does work better than php4.
949  rewrote WikiUserNew user upgrading to ease php5 update
950  fixed pref handling in WikiUserNew
951  added Email Notification
952  added simple Email verification
953  removed emailVerify userpref subclass: just a email property
954  changed pref binary storage layout: numarray => hash of non default values
955  print optimize message only if really done.
956  forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
957    prefs should be stored in db or homepage, besides the current session.
958
959  Revision 1.62  2004/03/17 18:41:05  rurban
960  initial_content and template support for CreatePage
961
962  Revision 1.61  2004/03/12 20:59:17  rurban
963  important cookie fix by Konstantin Zadorozhny
964  new editpage feature: JS_SEARCHREPLACE
965
966  Revision 1.60  2004/02/15 21:34:37  rurban
967  PageList enhanced and improved.
968  fixed new WikiAdmin... plugins
969  editpage, Theme with exp. htmlarea framework
970    (htmlarea yet committed, this is really questionable)
971  WikiUser... code with better session handling for prefs
972  enhanced UserPreferences (again)
973  RecentChanges for show_deleted: how should pages be deleted then?
974
975  Revision 1.59  2003/12/07 20:35:26  carstenklapp
976  Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
977  error: Call to undefined function: gettransformedcontent() in
978  /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
979  205.
980
981  Revision 1.58  2003/03/10 18:25:22  dairiki
982  Bug/typo fix.  If you use the edit page to un/lock a page, it
983  failed with: Fatal error: Call to a member function on a
984  non-object in editpage.php on line 136
985
986  Revision 1.57  2003/02/26 03:40:22  dairiki
987  New action=create.  Essentially the same as action=edit, except that if the
988  page already exists, it falls back to action=browse.
989
990  This is for use in the "question mark" links for unknown wiki words
991  to avoid problems and confusion when following links from stale pages.
992  (If the "unknown page" has been created in the interim, the user probably
993  wants to view the page before editing it.)
994
995  Revision 1.56  2003/02/21 18:07:14  dairiki
996  Minor, nitpicky, currently inconsequential changes.
997
998  Revision 1.55  2003/02/21 04:10:58  dairiki
999  Fixes for new cached markup.
1000  Some minor code cleanups.
1001
1002  Revision 1.54  2003/02/16 19:47:16  dairiki
1003  Update WikiDB timestamp when editing or deleting pages.
1004
1005  Revision 1.53  2003/02/15 23:20:27  dairiki
1006  Redirect back to browse current version of page upon save,
1007  even when no changes were made.
1008
1009  Revision 1.52  2003/01/03 22:22:00  carstenklapp
1010  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
1011
1012  Revision 1.51  2003/01/03 02:43:26  carstenklapp
1013  New class LoadFileConflictPageEditor, for merging / comparing a loaded
1014  pgsrc file with an existing page.
1015
1016  */
1017
1018 // Local Variables:
1019 // mode: php
1020 // tab-width: 8
1021 // c-basic-offset: 4
1022 // c-hanging-comment-ender-p: nil
1023 // indent-tabs-mode: nil
1024 // End:
1025 ?>