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