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