]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
fix and optimize ENABLE_SPAMBLOCKLIST and ($newlinks > 5))
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.112 2007-06-09 20:05:35 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             include_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             include_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             include_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             include_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             include_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         include_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         $numlinks = $this->numLinks($newtext);
382         $newlinks = $numlinks - $this->numLinks($oldtext);
383         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
384         //        better use a certain text : link ratio.
385
386         // 1. Not more then 20 new external links
387         if ($newlinks >= NUM_SPAM_LINKS)
388         {
389             // Allow strictly authenticated users?
390             // TODO: mail the admin?
391             $this->tokens['PAGE_LOCKED_MESSAGE'] = 
392                 HTML($this->getSpamMessage(),
393                      HTML::p(HTML::strong(_("Too many external links."))));
394             return true;
395         }
396         // 2. external babycart (SpamAssassin) check
397         // This will probably prevent from discussing sex or viagra related topics. So beware.
398         if (ENABLE_SPAMASSASSIN) {
399             include_once("lib/spam_babycart.php");
400             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"), 
401                                            $this->user->getId())) {
402                 // TODO: mail the admin
403                 if (is_array($babycart))
404                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
405                         HTML($this->getSpamMessage(),
406                              HTML::p(HTML::em(_("SpamAssassin reports: "), 
407                                                 join("\n", $babycart))));
408                 return true;
409             }
410         }
411         // 3. extract (new) links and check surbl for blocked domains
412         if (ENABLE_SPAMBLOCKLIST and ($newlinks > 5)) {
413             include_once("lib/SpamBlocklist.php");
414             include_once("lib/InlineParser.php");
415             $oldparsed = TransformLinks($oldtext);
416             $oldlinks = array();
417             foreach ($oldparsed->_content as $link) {
418                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
419                     $uri = $link->_getURL($this->page->getName());
420                     $oldlinks[$uri]++;
421                 }
422             }
423             unset($oldparsed);
424             unset($oldtext);
425             $parsed = TransformLinks($newtext);
426             foreach ($parsed->_content as $link) {
427                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
428                     $uri = $link->_getURL($this->page->getName());
429                     // only check new links, so admins may add blocked links.
430                     if (!array_key_exists($uri, $oldlinks) and ($res = IsBlackListed($uri))) {
431                         // TODO: mail the admin
432                         $this->tokens['PAGE_LOCKED_MESSAGE'] = 
433                             HTML($this->getSpamMessage(),
434                                  HTML::p(HTML::strong(_("External links contain blocked domains:")),
435                                          HTML::ul(HTML::li(sprintf(_("%s is listed at %s with %s"), 
436                                                                    $uri." [".$res[2]."]", $res[0], $res[1])))));
437                         return true;
438                     }
439                 }
440             }
441             unset($oldlinks);
442             unset($parsed);
443         }
444
445         return false;
446     }
447
448     /** Number of external links in the wikitext
449      */
450     function numLinks(&$text) {
451         return substr_count($text, "http://") + substr_count($text, "https://");
452     }
453
454     /** Header of the Anti Spam message 
455      */
456     function getSpamMessage () {
457         return
458             HTML(HTML::h2(_("Spam Prevention")),
459                  HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
460                          HTML::br(),
461                          _("Sorry for the inconvenience.")),
462                  HTML::p(""));
463     }
464
465     function getPreview () {
466         include_once('lib/PageType.php');
467         $this->_content = $this->getContent();
468         return new TransformedText($this->page, $this->_content, $this->meta);
469     }
470
471     function getConvertedPreview () {
472         include_once('lib/PageType.php');
473         $this->_content = $this->getContent();
474         $this->meta['markup'] = 2.0;
475         $this->_content = ConvertOldMarkup($this->_content);
476         return new TransformedText($this->page, $this->_content, $this->meta);
477     }
478
479     // possibly convert HTMLAREA content back to Wiki markup
480     function getContent () {
481         if (ENABLE_WYSIWYG) {
482             // don't store everything as html
483             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
484                 // Wikiwyg shortcut to avoid the InlineTransformer:
485                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
486                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
487                 $this->_content = join("", $xml_output->_content);
488             } else {
489                 $this->meta['pagetype'] = 'html';
490             }
491             return $this->_content;
492         } else {
493             return $this->_content;
494         }
495     }
496
497     function getLockedMessage () {
498         return
499             HTML(HTML::h2(_("Page Locked")),
500                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
501                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
502                  HTML::p(_("Sorry for the inconvenience.")));
503     }
504
505     function isModerated() {
506         return $this->page->get('moderation');
507     }
508     function getModeratedMessage() {
509         return
510             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
511                  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")))),
512                  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.", 
513                          WikiLink(_("UserPreferences")))));
514     }
515     function getConflictMessage ($unresolved = false) {
516         /*
517          xgettext only knows about c/c++ line-continuation strings
518          it does not know about php's dot operator.
519          We want to translate this entire paragraph as one string, of course.
520          */
521
522         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
523
524         if ($unresolved)
525             $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.",
526                                 "<<<<<<< ". _("Your version"),
527                                 ">>>>>>> ". _("Other version")));
528         else
529             $message = HTML::p(_("Please check it through before saving."));
530
531
532
533         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
534           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
535                        $re_edit_link)),
536           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
537           HTML::li(_("Save your updated changes.")));
538         */
539         return
540             HTML(HTML::h2(_("Conflicting Edits!")),
541                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
542                  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.")),
543                  $message);
544     }
545
546
547     function getTextArea () {
548         $request = &$this->request;
549
550         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
551
552         // WYSIWYG will need two pagetypes: raw wikitest and converted html
553         if (ENABLE_WYSIWYG) {
554             $this->_wikicontent = $this->_content;
555             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
556             //                $this->getPreview();
557             //$this->_htmlcontent = $this->_content->asXML();
558         }
559
560         $textarea = HTML::textarea(array('class'=> 'wikiedit',
561                                          'name' => 'edit[content]',
562                                          'id'   => 'edit-content',
563                                          'rows' => $request->getPref('editHeight'),
564                                          'cols' => $request->getPref('editWidth'),
565                                          'readonly' => (bool) $readonly),
566                                    $this->_content);
567         /** <textarea wrap="virtual"> is not valid XHTML but Netscape 4 requires it
568          * to wrap long lines.
569          */
570         if (isBrowserNS4())
571             $textarea->setAttr('wrap', 'virtual');
572         if (ENABLE_WYSIWYG) {
573             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent, 
574                                                 $textarea->getAttr('name'));
575         } else
576             return $textarea;
577     }
578
579     function getFormElements () {
580         global $WikiTheme;
581         $request = &$this->request;
582         $page = &$this->page;
583
584         $h = array('action'   => 'edit',
585                    'pagename' => $page->getName(),
586                    'version'  => $this->version,
587                    'edit[pagetype]' => $this->meta['pagetype'],
588                    'edit[current_version]' => $this->_currentVersion);
589
590         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
591         $el['EDIT_TEXTAREA'] = $this->getTextArea();
592         if ( ENABLE_CAPTCHA ) {
593             $el = array_merge($el, $this->Captcha->getFormElements());
594         }
595         $el['SUMMARY_INPUT']
596             = HTML::input(array('type'  => 'text',
597                                 'class' => 'wikitext',
598                                 'id' => 'edit-summary',
599                                 'name'  => 'edit[summary]',
600                                 'size'  => 50,
601                                 'maxlength' => 256,
602                                 'value' => $this->meta['summary']));
603         $el['MINOR_EDIT_CB']
604             = HTML::input(array('type' => 'checkbox',
605                                 'name'  => 'edit[minor_edit]',
606                                 'id' => 'edit-minor_edit',
607                                 'checked' => (bool) $this->meta['is_minor_edit']));
608         $el['OLD_MARKUP_CB']
609             = HTML::input(array('type' => 'checkbox',
610                                 'name' => 'edit[markup]',
611                                 'value' => 'old',
612                                 'checked' => $this->meta['markup'] < 2.0,
613                                 'id' => 'useOldMarkup',
614                                 'onclick' => 'showOldMarkupRules(this.checked)'));
615         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0) 
616             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
617         $el['LOCKED_CB']
618             = HTML::input(array('type' => 'checkbox',
619                                 'name' => 'edit[locked]',
620                                 'id'   => 'edit-locked',
621                                 'disabled' => (bool) !$this->user->isadmin(),
622                                 'checked'  => (bool) $this->locked));
623
624         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
625                                   'wikiaction',
626                                   array('accesskey'=> 'p'));
627
628         //if (!$this->isConcurrentUpdate() && $this->canEdit())
629         $el['SAVE_B'] = Button('submit:edit[save]',
630                                _("Save"), 'wikiaction',
631                                array('accesskey'=> 's'));
632         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
633
634         $el['WIDTH_PREF'] 
635             = HTML::input(array('type'     => 'text',
636                                 'size'     => 3,
637                                 'maxlength'=> 4,
638                                 'class'    => "numeric",
639                                 'name'     => 'pref[editWidth]',
640                                 'id'       => 'pref-editWidth',
641                                 'value'    => $request->getPref('editWidth'),
642                                 'onchange' => 'this.form.submit();'));
643         $el['HEIGHT_PREF'] 
644             = HTML::input(array('type'     => 'text',
645                                 'size'     => 3,
646                                 'maxlength'=> 4,
647                                 'class'    => "numeric",
648                                 'name'     => 'pref[editHeight]',
649                                 'id'       => 'pref-editHeight',
650                                 'value'    => $request->getPref('editHeight'),
651                                 'onchange' => 'this.form.submit();'));
652         $el['SEP'] = $WikiTheme->getButtonSeparator();
653         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.", 
654                                     HTML::em($this->user->getId()));
655         
656         return $el;
657     }
658
659     function _redirectToBrowsePage() {
660         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
661     }
662
663     function _restoreState () {
664         $request = &$this->request;
665
666         $posted = $request->getArg('edit');
667         $request->setArg('edit', false);
668
669         if (!$posted 
670             || !$request->isPost()
671             || !in_array($request->getArg('action'),array('edit','loadfile')))
672             return false;
673
674         if (!isset($posted['content']) || !is_string($posted['content']))
675             return false;
676         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
677                                         rtrim($posted['content']));
678         $this->_content = $this->getContent();
679
680         $this->_currentVersion = (int) $posted['current_version'];
681
682         if ($this->_currentVersion < 0)
683             return false;
684         if ($this->_currentVersion > $this->current->getVersion())
685             return false;       // FIXME: some kind of warning?
686
687         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
688         $meta['markup'] = $is_old_markup ? false : 2.0;
689         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
690         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
691         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
692         if ( ENABLE_CAPTCHA )
693             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
694                 $posted['captcha_input'] : '';
695
696         $this->meta = array_merge($this->meta, $meta);
697         $this->locked = !empty($posted['locked']);
698
699         foreach (array('preview','save','edit_convert',
700                        'keep_old','overwrite') as $o) 
701         {
702             if (!empty($posted[$o]))
703                 $this->editaction = $o;
704         }
705         if (empty($this->editaction))
706             $this->editaction = 'edit';
707
708         return true;
709     }
710
711     function _initializeState () {
712         $request = &$this->request;
713         $current = &$this->current;
714         $selected = &$this->selected;
715         $user = &$this->user;
716
717         if (!$selected)
718             NoSuchRevision($request, $this->page, $this->version); // noreturn
719
720         $this->_currentVersion = $current->getVersion();
721         $this->_content = $selected->getPackedContent();
722
723         $this->locked = $this->page->get('locked');
724
725         // If author same as previous author, default minor_edit to on.
726         $age = $this->meta['mtime'] - $current->get('mtime');
727         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
728                                          && $current->get('author') == $user->getId()
729                                          );
730
731         // Default for new pages is new-style markup.
732         if ($selected->hasDefaultContents())
733             $is_new_markup = true;
734         else
735             $is_new_markup = $selected->get('markup') >= 2.0;
736
737         $this->meta['markup'] = $is_new_markup ? 2.0: false;
738         $this->meta['pagetype'] = $selected->get('pagetype');
739         if ($this->meta['pagetype'] == 'wikiblog')
740             $this->meta['summary'] = $selected->get('summary'); // keep blog title
741         else
742             $this->meta['summary'] = '';
743         $this->editaction = 'edit';
744     }
745 }
746
747 class LoadFileConflictPageEditor
748 extends PageEditor
749 {
750     function editPage ($saveFailed = true) {
751         $tokens = &$this->tokens;
752
753         if (!$this->canEdit()) {
754             if ($this->isInitialEdit()) {
755                 return $this->viewSource();
756             }
757             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
758         }
759         elseif ($this->editaction == 'save') {
760             if ($this->savePage()) {
761                 return true;    // Page saved.
762             }
763             $saveFailed = true;
764         }
765
766         if ($saveFailed || $this->isConcurrentUpdate())
767         {
768             // Get the text of the original page, and the two conflicting edits
769             // The diff class takes arrays as input.  So retrieve content as
770             // an array, or convert it as necesary.
771             $orig = $this->page->getRevision($this->_currentVersion);
772             $this_content = explode("\n", $this->_content);
773             $other_content = $this->current->getContent();
774             include_once("lib/diff.php");
775             $diff2 = new Diff($other_content, $this_content);
776             $context_lines = max(4, count($other_content) + 1,
777                                  count($this_content) + 1);
778             $fmt = new BlockDiffFormatter($context_lines);
779
780             $this->_content = $fmt->format($diff2);
781             // FIXME: integrate this into class BlockDiffFormatter
782             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
783                                           $this->_content);
784             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
785                                           $this->_content);
786
787             $this->_currentVersion = $this->current->getVersion();
788             $this->version = $this->_currentVersion;
789             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
790         }
791
792         if ($this->editaction == 'edit_convert')
793             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
794         if ($this->editaction == 'preview')
795             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
796
797         // FIXME: NOT_CURRENT_MESSAGE?
798         $tokens = array_merge($tokens, $this->getFormElements());
799         // we need all GET params for loadfile overwrite
800         if ($this->request->getArg('action') == 'loadfile') {
801                 
802             $this->tokens['HIDDEN_INPUTS'] = 
803                 HTML(HiddenInputs
804                     (array('source' => $this->request->getArg('source'),
805                            'merge'  => 1)),
806                      $this->tokens['HIDDEN_INPUTS']);
807             // add two conflict resolution buttons before preview and save.
808             $tokens['PREVIEW_B'] = HTML(
809                                     Button('submit:edit[keep_old]', 
810                                            _("Keep old"), 'wikiaction'),
811                                     $tokens['SEP'],
812                                     Button('submit:edit[overwrite]', 
813                                            _("Overwrite with new"), 'wikiaction'),
814                                     $tokens['SEP'],
815                                     $tokens['PREVIEW_B']);
816         }
817         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
818             include_once("lib/EditToolbar.php");
819             $toolbar = new EditToolbar();
820             $tokens = array_merge($tokens, $toolbar->getTokens());
821         }
822
823         return $this->output('editpage', _("Merge and Edit: %s"));
824     }
825
826     function output ($template, $title_fs) {
827         $selected = &$this->selected;
828         $current = &$this->current;
829
830         if ($selected && $selected->getVersion() != $current->getVersion()) {
831             $rev = $selected;
832             $pagelink = WikiLink($selected);
833         }
834         else {
835             $rev = $current;
836             $pagelink = WikiLink($this->page);
837         }
838
839         $title = new FormattedText ($title_fs, $pagelink);
840         $this->tokens['HEADER'] = $title;
841         //hack! there's no TITLE in editpage, but in the previous top template
842         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
843             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
844         else
845             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
846                                                         $this->tokens['PAGE_LOCKED_MESSAGE']);
847         $template = Template($template, $this->tokens);
848
849         //GeneratePage($template, $title, $rev);
850         PrintXML($template);
851         return true;
852     }
853
854     function getConflictMessage () {
855         $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.",
856                                     "<<<<<<<",
857                                     "======="),
858                                 HTML::p(_("Please check it through before saving."))));
859         return $message;
860     }
861 }
862
863 /**
864  $Log: not supported by cvs2svn $
865  Revision 1.111  2007/06/03 17:12:00  rurban
866  convenience: only check above 5 external links for blocked domains
867
868  Revision 1.110  2007/01/07 18:42:00  rurban
869  Print ModeratedPage message on edit. Use GOOGLE_LINKS_NOFOLLOW. Improve id: edit: to edit-
870
871  Revision 1.109  2007/01/02 13:21:39  rurban
872  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.
873
874  Revision 1.108  2006/12/22 17:47:34  rurban
875  Display Warnings only once.
876  Add button accesskeys
877
878  Revision 1.107  2006/05/13 19:59:54  rurban
879  added wysiwyg_editor-1.3a feature by Jean-Nicolas GEREONE <jean-nicolas.gereone@st.com>
880  converted wysiwyg_editor-1.3a js to WysiwygEdit framework
881  changed default ENABLE_WYSIWYG = true and added WYSIWYG_BACKEND = Wikiwyg
882
883  Revision 1.106  2005/11/21 22:03:08  rurban
884  fix syntax error inside ENABLE_SPAMBLOCKLIST
885
886  Revision 1.105  2005/11/21 20:53:59  rurban
887  beautify request pref lines, no antispam if admin (netznetz request), user is a member anyway
888
889  Revision 1.104  2005/10/31 17:20:40  rurban
890  fix ConvertBefore
891
892  Revision 1.103  2005/10/31 17:09:13  rurban
893  use better constant WYSIWYG_DEFAULT_PAGETYPE_HTML
894
895  Revision 1.102  2005/10/31 16:47:14  rurban
896  enable wysiwyg html converters
897
898  Revision 1.101  2005/10/30 16:12:28  rurban
899  simplify viewsource tokens
900
901  Revision 1.100  2005/10/30 14:20:42  rurban
902  move Captcha specific vars and methods into a Captcha object
903  randomize Captcha chars positions and angles (smoothly)
904
905  Revision 1.99  2005/10/29 08:21:58  rurban
906  ENABLE_SPAMBLOCKLIST:
907    Check for links to blocked external tld domains in new edits, against
908    multi.surbl.org and bl.spamcop.net.
909
910  Revision 1.98  2005/10/10 19:37:04  rurban
911  change USE_HTMLAREA to ENABLE WYSIWYG, add NUM_SPAM_LINKS=20
912
913  Revision 1.97  2005/09/26 06:32:22  rurban
914  [] is forbidden in id tags. Renamed to use :
915
916  Revision 1.96  2005/05/06 17:54:22  rurban
917  silence Preview warnings for PAGE_LOCKED_MESSAGE, CONCURRENT_UPDATE_MESSAGE (thanks to schorni)
918
919  Revision 1.95  2005/04/25 20:17:14  rurban
920  captcha feature by Benjamin Drieu. Patch #1110699
921
922  Revision 1.94  2005/02/28 20:23:31  rurban
923  fix error_stack
924
925  Revision 1.93  2005/02/27 19:31:52  rurban
926  hack: display errorstack without sideeffects (save and restore)
927
928  Revision 1.92  2005/01/29 20:37:21  rurban
929  no edit toolbar at all if ENABLE_EDITTOOLBAR = false
930
931  Revision 1.91  2005/01/25 07:05:49  rurban
932  extract toolbar code, support new tags to get rid of php inside templates
933
934  Revision 1.90  2005/01/22 12:46:15  rurban
935  fix oldmakrup button label
936  update pref[edit*] settings
937
938  Revision 1.89  2005/01/21 14:07:49  rurban
939  reformatting
940
941  Revision 1.88  2004/12/17 16:39:03  rurban
942  minor reformatting
943
944  Revision 1.87  2004/12/16 18:28:05  rurban
945  keep wikiblog summary = page title
946
947  Revision 1.86  2004/12/11 14:50:15  rurban
948  new edit_convert button, to get rid of old markup eventually
949
950  Revision 1.85  2004/12/06 19:49:56  rurban
951  enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
952  renamed delete_page to purge_page.
953  enable action=edit&version=-1 to force creation of a new version.
954  added BABYCART_PATH config
955  fixed magiqc in adodb.inc.php
956  and some more docs
957
958  Revision 1.84  2004/12/04 12:58:26  rurban
959  enable babycart Blog::SpamAssassin module on ENABLE_SPAMASSASSIN=true
960  (currently only for php >= 4.3.0)
961
962  Revision 1.83  2004/12/04 11:55:39  rurban
963  First simple AntiSpam prevention:
964    No more than 20 new http:// links allowed
965
966  Revision 1.82  2004/11/30 22:21:56  rurban
967  changed gif to optimized (pngout) png
968
969  Revision 1.81  2004/11/29 17:57:27  rurban
970  translated pulldown buttons
971
972  Revision 1.80  2004/11/25 17:20:51  rurban
973  and again a couple of more native db args: backlinks
974
975  Revision 1.79  2004/11/21 11:59:20  rurban
976  remove final \n to be ob_cache independent
977
978  Revision 1.78  2004/11/16 17:57:45  rurban
979  fix search&replace button
980  use new addTagButton machinery
981  new showPulldown for categories, TODO: in a seperate request
982
983  Revision 1.77  2004/11/15 15:52:35  rurban
984  improve js stability
985
986  Revision 1.76  2004/11/15 15:37:34  rurban
987  fix JS_SEARCHREPLACE
988    don't use document.write for replace, otherwise self.opener is not defined.
989
990  Revision 1.75  2004/09/16 08:00:52  rurban
991  just some comments
992
993  Revision 1.74  2004/07/03 07:36:28  rurban
994  do not get unneccessary content
995
996  Revision 1.73  2004/06/16 21:23:44  rurban
997  fixed non-object fatal #215
998
999  Revision 1.72  2004/06/14 11:31:37  rurban
1000  renamed global $Theme to $WikiTheme (gforge nameclash)
1001  inherit PageList default options from PageList
1002    default sortby=pagename
1003  use options in PageList_Selectable (limit, sortby, ...)
1004  added action revert, with button at action=diff
1005  added option regex to WikiAdminSearchReplace
1006
1007  Revision 1.71  2004/06/03 18:06:29  rurban
1008  fix file locking issues (only needed on write)
1009  fixed immediate LANG and THEME in-session updates if not stored in prefs
1010  advanced editpage toolbars (search & replace broken)
1011
1012  Revision 1.70  2004/06/02 20:47:47  rurban
1013  dont use the wikiaction class
1014
1015  Revision 1.69  2004/06/02 10:17:56  rurban
1016  integrated search/replace into toolbar
1017  added save+preview buttons
1018
1019  Revision 1.68  2004/06/01 15:28:00  rurban
1020  AdminUser only ADMIN_USER not member of Administrators
1021  some RateIt improvements by dfrankow
1022  edit_toolbar buttons
1023
1024  Revision _1.6  2004/05/26 15:48:00  syilek
1025  fixed problem with creating page with slashes from one true page
1026
1027  Revision _1.5  2004/05/25 16:51:53  syilek
1028  added ability to create a page from the category page and not have to edit it
1029
1030  Revision 1.67  2004/05/27 17:49:06  rurban
1031  renamed DB_Session to DbSession (in CVS also)
1032  added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
1033  remove leading slash in error message
1034  added force_unlock parameter to File_Passwd (no return on stale locks)
1035  fixed adodb session AffectedRows
1036  added FileFinder helpers to unify local filenames and DATA_PATH names
1037  editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
1038
1039  Revision 1.66  2004/04/29 23:25:12  rurban
1040  re-ordered locale init (as in 1.3.9)
1041  fixed loadfile with subpages, and merge/restore anyway
1042    (sf.net bug #844188)
1043
1044  Revision 1.65  2004/04/18 01:11:52  rurban
1045  more numeric pagename fixes.
1046  fixed action=upload with merge conflict warnings.
1047  charset changed from constant to global (dynamic utf-8 switching)
1048
1049  Revision 1.64  2004/04/06 19:48:56  rurban
1050  temp workaround for action=edit AddComment form
1051
1052  Revision 1.63  2004/03/24 19:39:02  rurban
1053  php5 workaround code (plus some interim debugging code in XmlElement)
1054    php5 doesn't work yet with the current XmlElement class constructors,
1055    WikiUserNew does work better than php4.
1056  rewrote WikiUserNew user upgrading to ease php5 update
1057  fixed pref handling in WikiUserNew
1058  added Email Notification
1059  added simple Email verification
1060  removed emailVerify userpref subclass: just a email property
1061  changed pref binary storage layout: numarray => hash of non default values
1062  print optimize message only if really done.
1063  forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
1064    prefs should be stored in db or homepage, besides the current session.
1065
1066  Revision 1.62  2004/03/17 18:41:05  rurban
1067  initial_content and template support for CreatePage
1068
1069  Revision 1.61  2004/03/12 20:59:17  rurban
1070  important cookie fix by Konstantin Zadorozhny
1071  new editpage feature: JS_SEARCHREPLACE
1072
1073  Revision 1.60  2004/02/15 21:34:37  rurban
1074  PageList enhanced and improved.
1075  fixed new WikiAdmin... plugins
1076  editpage, Theme with exp. htmlarea framework
1077    (htmlarea yet committed, this is really questionable)
1078  WikiUser... code with better session handling for prefs
1079  enhanced UserPreferences (again)
1080  RecentChanges for show_deleted: how should pages be deleted then?
1081
1082  Revision 1.59  2003/12/07 20:35:26  carstenklapp
1083  Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
1084  error: Call to undefined function: gettransformedcontent() in
1085  /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
1086  205.
1087
1088  Revision 1.58  2003/03/10 18:25:22  dairiki
1089  Bug/typo fix.  If you use the edit page to un/lock a page, it
1090  failed with: Fatal error: Call to a member function on a
1091  non-object in editpage.php on line 136
1092
1093  Revision 1.57  2003/02/26 03:40:22  dairiki
1094  New action=create.  Essentially the same as action=edit, except that if the
1095  page already exists, it falls back to action=browse.
1096
1097  This is for use in the "question mark" links for unknown wiki words
1098  to avoid problems and confusion when following links from stale pages.
1099  (If the "unknown page" has been created in the interim, the user probably
1100  wants to view the page before editing it.)
1101
1102  Revision 1.56  2003/02/21 18:07:14  dairiki
1103  Minor, nitpicky, currently inconsequential changes.
1104
1105  Revision 1.55  2003/02/21 04:10:58  dairiki
1106  Fixes for new cached markup.
1107  Some minor code cleanups.
1108
1109  Revision 1.54  2003/02/16 19:47:16  dairiki
1110  Update WikiDB timestamp when editing or deleting pages.
1111
1112  Revision 1.53  2003/02/15 23:20:27  dairiki
1113  Redirect back to browse current version of page upon save,
1114  even when no changes were made.
1115
1116  Revision 1.52  2003/01/03 22:22:00  carstenklapp
1117  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
1118
1119  Revision 1.51  2003/01/03 02:43:26  carstenklapp
1120  New class LoadFileConflictPageEditor, for merging / comparing a loaded
1121  pgsrc file with an existing page.
1122
1123  */
1124
1125 // Local Variables:
1126 // mode: php
1127 // tab-width: 8
1128 // c-basic-offset: 4
1129 // c-hanging-comment-ender-p: nil
1130 // indent-tabs-mode: nil
1131 // End:
1132 ?>