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