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