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