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