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