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