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