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