]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Error banner when bad action
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id$');
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['LOCK_CHANGED_MSG'] = '';
79         $tokens['CONCURRENT_UPDATE_MESSAGE'] = '';
80         $r =& $this->request;
81
82         if (isset($r->args['pref']['editWidth'])
83             and ($r->getPref('editWidth') != $r->args['pref']['editWidth'])) {
84             $r->_prefs->set('editWidth', $r->args['pref']['editWidth']);
85         }
86         if (isset($r->args['pref']['editHeight'])
87             and ($r->getPref('editHeight') != $r->args['pref']['editHeight'])) {
88             $r->_prefs->set('editHeight', $r->args['pref']['editHeight']);
89         }
90         
91         if ($this->isModerated())
92             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getModeratedMessage();
93
94         if (! $this->canEdit()) {
95             if ($this->isInitialEdit())
96                 return $this->viewSource();
97             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
98         }
99         elseif ($r->getArg('save_and_redirect_to') != "") {
100             if (ENABLE_CAPTCHA && $this->Captcha->Failed()) {
101                 $this->tokens['PAGE_LOCKED_MESSAGE'] = 
102                     HTML::p(HTML::h1($this->Captcha->failed_msg));
103             }
104             elseif ( $this->savePage()) {
105                 // noreturn
106                 $request->setArg('action', false);
107                 $r->redirect(WikiURL($r->getArg('save_and_redirect_to')));
108                 return true;    // Page saved.
109             }
110             $saveFailed = true;
111         }
112         elseif ($this->editaction == 'save') {
113             if (ENABLE_CAPTCHA && $this->Captcha->Failed()) {
114                 $this->tokens['PAGE_LOCKED_MESSAGE'] = 
115                     HTML::p(HTML::h1($this->Captcha->failed_msg));
116             }
117             elseif ($this->savePage()) {
118                 return true;    // Page saved.
119             }
120             else {
121                 $saveFailed = true;
122             }
123         }
124         // coming from loadfile conflicts 
125         elseif ($this->editaction == 'keep_old') {
126             // keep old page and do nothing
127             $this->_redirectToBrowsePage();
128             //$r->redirect(WikiURL($r->getArg('save_and_redirect_to')));
129             return true;
130         }
131         elseif ($this->editaction == 'overwrite') { 
132             // take the new content without diff
133             $source = $this->request->getArg('loadfile');
134             require_once('lib/loadsave.php');
135             $this->request->setArg('loadfile', 1);
136             $this->request->setArg('overwrite', 1);
137             $this->request->setArg('merge', 0);
138             LoadFileOrDir($this->request);
139             $this->_redirectToBrowsePage();
140             //$r->redirect(WikiURL($r->getArg('save_and_redirect_to')));
141             return true;
142         }
143         elseif ($this->editaction == 'upload') {
144             // run plugin UpLoad
145             $plugin = WikiPluginLoader("UpLoad");
146             $plugin->run();
147             // add link to content       
148             ;
149         }
150
151         if ($saveFailed and $this->isConcurrentUpdate())
152         {
153             // Get the text of the original page, and the two conflicting edits
154             // The diff3 class takes arrays as input.  So retrieve content as
155             // an array, or convert it as necesary.
156             $orig = $this->page->getRevision($this->_currentVersion);
157             // FIXME: what if _currentVersion has be deleted?
158             $orig_content = $orig->getContent();
159             $this_content = explode("\n", $this->_content);
160             $other_content = $this->current->getContent();
161             require_once("lib/diff3.php");
162             $diff = new diff3($orig_content, $this_content, $other_content);
163             $output = $diff->merged_output(_("Your version"), _("Other version"));
164             // Set the content of the textarea to the merged diff
165             // output, and update the version
166             $this->_content = implode ("\n", $output);
167             $this->_currentVersion = $this->current->getVersion();
168             $this->version = $this->_currentVersion;
169             $unresolved = $diff->ConflictingBlocks;
170             $tokens['CONCURRENT_UPDATE_MESSAGE'] 
171                 = $this->getConflictMessage($unresolved);
172         } elseif ($saveFailed && !$this->_isSpam) {
173             $tokens['CONCURRENT_UPDATE_MESSAGE'] = 
174                 HTML(HTML::h2(_("Some internal editing error")),
175                      HTML::p(_("Your are probably trying to edit/create an invalid version of this page.")),
176                      HTML::p(HTML::em(_("&version=-1 might help."))));
177         }
178
179         if ($this->editaction == 'edit_convert')
180             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
181         if ($this->editaction == 'preview')
182             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
183         if ($this->editaction == 'diff')
184             $tokens['PREVIEW_CONTENT'] = $this->getDiff();
185
186         // FIXME: NOT_CURRENT_MESSAGE?
187         $tokens = array_merge($tokens, $this->getFormElements());
188
189         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
190             require_once("lib/EditToolbar.php");
191             $toolbar = new EditToolbar();
192             $tokens = array_merge($tokens, $toolbar->getTokens());
193         }
194
195         return $this->output('editpage', _("Edit: %s"));
196     }
197
198     function output ($template, $title_fs) {
199         global $WikiTheme;
200         $selected = &$this->selected;
201         $current = &$this->current;
202
203         if ($selected && $selected->getVersion() != $current->getVersion()) {
204             $rev = $selected;
205             $pagelink = WikiLink($selected);
206         }
207         else {
208             $rev = $current;
209             $pagelink = WikiLink($this->page);
210         }
211
212         $title = new FormattedText ($title_fs, $pagelink);
213         // not for dumphtml or viewsource
214         if (ENABLE_WYSIWYG and $template == 'editpage') {
215             $WikiTheme->addMoreHeaders($this->WysiwygEdit->Head());
216             //$tokens['PAGE_SOURCE'] = $this->WysiwygEdit->ConvertBefore($this->_content);
217         }
218         $template = Template($template, $this->tokens);
219         /* Tell google (and others) not to take notice of edit links */
220         if (GOOGLE_LINKS_NOFOLLOW)
221             $args = array('ROBOTS_META' => "noindex,nofollow");
222         GeneratePage($template, $title, $rev);
223         return true;
224     }
225
226
227     function viewSource () {
228         assert($this->isInitialEdit());
229         assert($this->selected);
230
231         $this->tokens['PAGE_SOURCE'] = $this->_content;
232         $this->tokens['HIDDEN_INPUTS'] = HiddenInputs($this->request->getArgs());
233         return $this->output('viewsource', _("View Source: %s"));
234     }
235
236     function updateLock() {
237         $changed = false;
238         if (!$this->user->isAdmin()) {
239             // FIXME: some sort of message
240             return false;         // not allowed.
241         }
242
243         if ((bool)$this->page->get('locked') != (bool)$this->locked) {
244             $this->page->set('locked', (bool)$this->locked);
245             $this->tokens['LOCK_CHANGED_MSG']
246                 .= $this->locked
247                 ? _("Page now locked.") 
248                 : _("Page now unlocked.");
249             $changed = true;
250         }
251
252         if (ENABLE_PAGE_PUBLIC) {
253             if ((bool)$this->page->get('public') != (bool)$this->public) {
254                 $this->page->set('public', (bool)$this->public);
255                 $this->tokens['LOCK_CHANGED_MSG']
256                     = ($this->public 
257                        ? _("Page now public.")
258                        : _("Page now not-public.")) . " ";
259                 $changed = true;
260             }
261         }
262         return $changed;            // lock changed.
263     }
264
265     function savePage () {
266         $request = &$this->request;
267
268         if ($this->isUnchanged()) {
269             // Allow admin lock/unlock even if
270             // no text changes were made.
271             if ($this->updateLock()) {
272                 $dbi = $request->getDbh();
273                 $dbi->touch();
274             }
275             // Save failed. No changes made.
276             $this->_redirectToBrowsePage();
277             // user will probably not see the rest of this...
278             require_once('lib/display.php');
279             // force browse of current version:
280             $request->setArg('action', false);
281             $request->setArg('version', false);
282             displayPage($request, 'nochanges');
283             return true;
284         }
285
286         if (!$this->user->isAdmin() and $this->isSpam()) {
287             $this->_isSpam = true;
288             return false;
289             /*
290             // Save failed. No changes made.
291             $this->_redirectToBrowsePage();
292             // user will probably not see the rest of this...
293             require_once('lib/display.php');
294             // force browse of current version:
295             $request->setArg('version', false);
296             displayPage($request, 'nochanges');
297             return true;
298             */
299         }
300
301         $page = &$this->page;
302
303         // Include any meta-data from original page version which
304         // has not been explicitly updated.
305         // (Except don't propagate pgsrc_version --- moot for now,
306         //  because at present it never gets into the db...)
307         $meta = $this->selected->getMetaData();
308         unset($meta['pgsrc_version']);
309         $meta = array_merge($meta, $this->meta);
310         
311         // Save new revision
312         $this->_content = $this->getContent();
313         $newrevision = $page->save($this->_content, 
314                                    $this->version == -1 
315                                      ? -1 
316                                      : $this->_currentVersion + 1, 
317                                    // force new?
318                                    $meta);
319         if (!isa($newrevision, 'WikiDB_PageRevision')) {
320             // Save failed.  (Concurrent updates).
321             return false;
322         }
323         
324         // New contents successfully saved...
325         $this->updateLock();
326
327         // Clean out archived versions of this page.
328         require_once('lib/ArchiveCleaner.php');
329         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
330         $cleaner->cleanPageRevisions($page);
331
332         /* generate notification emails done in WikiDB::save to catch 
333          all direct calls (admin plugins) */
334
335         // look at the errorstack
336         $errors   = $GLOBALS['ErrorManager']->_postponed_errors;
337         $warnings = $GLOBALS['ErrorManager']->getPostponedErrorsAsHTML(); 
338         $GLOBALS['ErrorManager']->_postponed_errors = $errors;
339
340         $dbi = $request->getDbh();
341         $dbi->touch();
342         
343         global $WikiTheme;
344         if (empty($warnings->_content) && ! $WikiTheme->getImageURL('signature')) {
345             // Do redirect to browse page if no signature has
346             // been defined.  In this case, the user will most
347             // likely not see the rest of the HTML we generate
348             // (below).
349             $request->setArg('action', false);
350             $this->_redirectToBrowsePage();
351         }
352
353         // Force browse of current page version.
354         $request->setArg('version', false);
355         // testme: does preview and more need action=edit?
356         $request->setArg('action', false);
357
358         $template = Template('savepage', $this->tokens);
359         $template->replace('CONTENT', $newrevision->getTransformedContent());
360         if (!empty($warnings->_content)) {
361             $template->replace('WARNINGS', $warnings);
362             unset($GLOBALS['ErrorManager']->_postponed_errors); 
363         }
364
365         $pagelink = WikiLink($page);
366
367         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
368         return true;
369     }
370
371     function isConcurrentUpdate () {
372         assert($this->current->getVersion() >= $this->_currentVersion);
373         return $this->current->getVersion() != $this->_currentVersion;
374     }
375
376     function canEdit () {
377         return !$this->page->get('locked') || $this->user->isAdmin();
378     }
379
380     function isInitialEdit () {
381         return $this->_initialEdit;
382     }
383
384     function isUnchanged () {
385         $current = &$this->current;
386
387         if ($this->meta['markup'] !=  $current->get('markup'))
388             return false;
389
390         return $this->_content == $current->getPackedContent();
391     }
392
393     /** 
394      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
395      * Need to check dynamically some blacklist wikipage settings 
396      * (plugin WikiAccessRestrictions) and some static blacklist.
397      * DONE: 
398      *   Always: More then 20 new external links
399      *   ENABLE_SPAMASSASSIN:  content patterns by babycart (only php >= 4.3 for now)
400      *   ENABLE_SPAMBLOCKLIST: content domain blacklist
401      */
402     function isSpam () {
403         $current = &$this->current;
404         $request = &$this->request;
405
406         $oldtext = $current->getPackedContent();
407         $newtext =& $this->_content;
408         $numlinks = $this->numLinks($newtext);
409         $newlinks = $numlinks - $this->numLinks($oldtext);
410         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
411         //        better use a certain text : link ratio.
412
413         // 1. Not more then 20 new external links
414         if ($newlinks >= NUM_SPAM_LINKS)
415         {
416             // Allow strictly authenticated users?
417             // TODO: mail the admin?
418             $this->tokens['PAGE_LOCKED_MESSAGE'] = 
419                 HTML($this->getSpamMessage(),
420                      HTML::p(HTML::strong(_("Too many external links."))));
421             return true;
422         }
423         // 2. external babycart (SpamAssassin) check
424         // This will probably prevent from discussing sex or viagra related topics. So beware.
425         if (ENABLE_SPAMASSASSIN) {
426             require_once("lib/spam_babycart.php");
427             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"), 
428                                            $this->user->getId())) {
429                 // TODO: mail the admin
430                 if (is_array($babycart))
431                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
432                         HTML($this->getSpamMessage(),
433                              HTML::p(HTML::em(_("SpamAssassin reports: "), 
434                                                 join("\n", $babycart))));
435                 return true;
436             }
437         }
438         // 3. extract (new) links and check surbl for blocked domains
439         if (ENABLE_SPAMBLOCKLIST and ($newlinks > 5)) {
440             require_once("lib/SpamBlocklist.php");
441             require_once("lib/InlineParser.php");
442             $parsed = TransformLinks($newtext);
443             $oldparsed = TransformLinks($oldtext);
444             $oldlinks = array();
445             foreach ($oldparsed->_content as $link) {
446                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
447                     $uri = $link->_getURL($this->page->getName());
448                     $oldlinks[$uri]++;
449                 }
450             }
451             unset($oldparsed);
452             foreach ($parsed->_content as $link) {
453                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
454                     $uri = $link->_getURL($this->page->getName());
455                     // only check new links, so admins may add blocked links.
456                     if (!array_key_exists($uri, $oldlinks) and ($res = IsBlackListed($uri))) {
457                         // TODO: mail the admin
458                         $this->tokens['PAGE_LOCKED_MESSAGE'] = 
459                             HTML($this->getSpamMessage(),
460                                  HTML::p(HTML::strong(_("External links contain blocked domains:")),
461                                          HTML::ul(HTML::li(sprintf(_("%s is listed at %s with %s"), 
462                                                                    $uri." [".$res[2]."]", $res[0], $res[1])))));
463                         return true;
464                     }
465                 }
466             }
467             unset($oldlinks);
468             unset($parsed);
469             unset($oldparsed);
470         }
471
472         return false;
473     }
474
475     /** Number of external links in the wikitext
476      */
477     function numLinks(&$text) {
478         return substr_count($text, "http://") + substr_count($text, "https://");
479     }
480
481     /** Header of the Anti Spam message 
482      */
483     function getSpamMessage () {
484         return
485             HTML(HTML::h2(_("Spam Prevention")),
486                  HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
487                          HTML::br(),
488                          _("Sorry for the inconvenience.")),
489                  HTML::p(""));
490     }
491
492     function getPreview () {
493         require_once('lib/PageType.php');
494         $this->_content = $this->getContent();
495         return new TransformedText($this->page, $this->_content, $this->meta);
496     }
497
498     function getConvertedPreview () {
499         require_once('lib/PageType.php');
500         $this->_content = $this->getContent();
501         $this->meta['markup'] = 2.0;
502         $this->_content = ConvertOldMarkup($this->_content);
503         return new TransformedText($this->page, $this->_content, $this->meta);
504     }
505
506     function getDiff () {
507         require_once('lib/diff.php');
508         $html = HTML();
509         
510         $diff = new Diff($this->current->getContent(), explode("\n", $this->getContent()));
511         if ($diff->isEmpty()) {
512             $html->pushContent(HTML::hr(),
513                                HTML::p('[', _("Versions are identical"),
514                                        ']'));
515         }
516         else {
517             // New CSS formatted unified diffs
518             $fmt = new HtmlUnifiedDiffFormatter;
519             // Use this for old table-formatted diffs.
520             //$fmt = new TableUnifiedDiffFormatter;
521             $html->pushContent($fmt->format($diff));
522         }
523         return $html;
524     }
525
526     // possibly convert HTMLAREA content back to Wiki markup
527     function getContent () {
528         if (ENABLE_WYSIWYG) {
529             // don't store everything as html
530             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
531                 // Wikiwyg shortcut to avoid the InlineTransformer:
532                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
533                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
534                 $this->_content = join("", $xml_output->_content);
535             } else {
536                 $this->meta['pagetype'] = 'html';
537             }
538             return $this->_content;
539         } else {
540             return $this->_content;
541         }
542     }
543
544     function getLockedMessage () {
545         return
546             HTML(HTML::h2(_("Page Locked")),
547                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
548                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
549                  HTML::p(_("Sorry for the inconvenience.")));
550     }
551
552     function isModerated() {
553         return $this->page->get('moderation');
554     }
555     function getModeratedMessage() {
556         return
557             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
558                  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")))),
559                  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.", 
560                          WikiLink(_("UserPreferences")))));
561     }
562     function getConflictMessage ($unresolved = false) {
563         /*
564          xgettext only knows about c/c++ line-continuation strings
565          it does not know about php's dot operator.
566          We want to translate this entire paragraph as one string, of course.
567          */
568
569         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
570
571         if ($unresolved)
572             $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.",
573                                 "<<<<<<< ". _("Your version"),
574                                 ">>>>>>> ". _("Other version")));
575         else
576             $message = HTML::p(_("Please check it through before saving."));
577
578
579
580         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
581           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
582                        $re_edit_link)),
583           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
584           HTML::li(_("Save your updated changes.")));
585         */
586         return
587             HTML(HTML::h2(_("Conflicting Edits!")),
588                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
589                  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.")),
590                  $message);
591     }
592
593
594     function getTextArea () {
595         $request = &$this->request;
596
597         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
598
599         // WYSIWYG will need two pagetypes: raw wikitest and converted html
600         if (ENABLE_WYSIWYG) {
601             $this->_wikicontent = $this->_content;
602             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
603             //                $this->getPreview();
604             //$this->_htmlcontent = $this->_content->asXML();
605         }
606
607         $textarea = HTML::textarea(array('class'=> 'wikiedit',
608                                          'name' => 'edit[content]',
609                                          'id'   => 'edit-content',
610                                          'rows' => $request->getPref('editHeight'),
611                                          'cols' => $request->getPref('editWidth'),
612                                          'readonly' => (bool) $readonly),
613                                    $this->_content);
614         if (ENABLE_WYSIWYG) {
615             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent, 
616                                                 $textarea->getAttr('name'));
617         } else
618             return $textarea;
619     }
620
621     function getFormElements () {
622         global $WikiTheme;
623         $request = &$this->request;
624         $page = &$this->page;
625
626         $h = array('action'   => 'edit',
627                    'pagename' => $page->getName(),
628                    'version'  => $this->version,
629                    'edit[pagetype]' => $this->meta['pagetype'],
630                    'edit[current_version]' => $this->_currentVersion);
631
632         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
633         $el['EDIT_TEXTAREA'] = $this->getTextArea();
634         if ( ENABLE_CAPTCHA ) {
635             $el = array_merge($el, $this->Captcha->getFormElements());
636         }
637         $el['SUMMARY_INPUT']
638             = HTML::input(array('type'  => 'text',
639                                 'class' => 'wikitext',
640                                 'id' => 'edit-summary',
641                                 'name'  => 'edit[summary]',
642                                 'size'  => 50,
643                                 'maxlength' => 256,
644                                 'value' => $this->meta['summary']));
645         $el['MINOR_EDIT_CB']
646             = HTML::input(array('type' => 'checkbox',
647                                 'name'  => 'edit[minor_edit]',
648                                 'id' => 'edit-minor_edit',
649                                 'checked' => (bool) $this->meta['is_minor_edit']));
650         $el['OLD_MARKUP_CB']
651             = HTML::input(array('type' => 'checkbox',
652                                 'name' => 'edit[markup]',
653                                 'value' => 'old',
654                                 'checked' => $this->meta['markup'] < 2.0,
655                                 'id' => 'useOldMarkup',
656                                 'onclick' => 'showOldMarkupRules(this.checked)'));
657         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0) 
658             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
659         $el['LOCKED_CB']
660             = HTML::input(array('type' => 'checkbox',
661                                 'name' => 'edit[locked]',
662                                 'id'   => 'edit-locked',
663                                 'disabled' => (bool) !$this->user->isadmin(),
664                                 'checked'  => (bool) $this->locked));
665
666         if (ENABLE_PAGE_PUBLIC) {
667             $el['PUBLIC_CB']
668             = HTML::input(array('type' => 'checkbox',
669                                 'name' => 'edit[public]',
670                                 'id'   => 'edit-public',
671                                 'disabled' => (bool) !$this->user->isAdmin(),
672                                 'checked'  => (bool) $this->page->get('public')));
673         }
674         if (ENABLE_WYSIWYG) {
675             if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
676                 $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
677             }
678         }
679
680         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
681                                   'wikiaction',
682                                   array('accesskey'=> 'p', 
683                                      'title' => 'Preview the current content [alt-p]'));
684
685         //if (!$this->isConcurrentUpdate() && $this->canEdit())
686         $el['SAVE_B'] = Button('submit:edit[save]',
687                                _("Save"), 'wikiaction',
688                                array('accesskey'=> 's', 
689                                      'title' => 'Save the current content as wikipage [alt-s]'));
690         $el['CHANGES_B'] = Button('submit:edit[diff]',
691                                _("Changes"), 'wikiaction',
692                                array('accesskey'=> 'c', 
693                                      'title' => 'Preview the current changes as diff [alt-c]'));
694         $el['UPLOAD_B'] = Button('submit:edit[upload]',
695                                _("Upload"), 'wikiaction',
696                                 array('title' => 'Select a local file and press Upload to attach into this page'));
697         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
698                                _("Spell Check"), 'wikiaction',
699                                 array('title' => 'Check the spelling'));
700         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
701
702         $el['WIDTH_PREF'] 
703             = HTML::input(array('type'     => 'text',
704                                 'size'     => 3,
705                                 'maxlength'=> 4,
706                                 'class'    => "numeric",
707                                 'name'     => 'pref[editWidth]',
708                                 'id'       => 'pref-editWidth',
709                                 'value'    => $request->getPref('editWidth'),
710                                 'onchange' => 'this.form.submit();'));
711         $el['HEIGHT_PREF'] 
712             = HTML::input(array('type'     => 'text',
713                                 'size'     => 3,
714                                 'maxlength'=> 4,
715                                 'class'    => "numeric",
716                                 'name'     => 'pref[editHeight]',
717                                 'id'       => 'pref-editHeight',
718                                 'value'    => $request->getPref('editHeight'),
719                                 'onchange' => 'this.form.submit();'));
720         $el['SEP'] = $WikiTheme->getButtonSeparator();
721         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.", 
722                                     HTML::em($this->user->getId()));
723         
724         return $el;
725     }
726
727     function _redirectToBrowsePage() {
728         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
729     }
730
731     function _restoreState () {
732         $request = &$this->request;
733
734         $posted = $request->getArg('edit');
735         $request->setArg('edit', false);
736
737         if (!$posted 
738             || !$request->isPost()
739             || !in_array($request->getArg('action'),array('edit','loadfile')))
740             return false;
741
742         if (!isset($posted['content']) || !is_string($posted['content']))
743             return false;
744         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
745                                         rtrim($posted['content']));
746         $this->_content = $this->getContent();
747
748         $this->_currentVersion = (int) $posted['current_version'];
749
750         if ($this->_currentVersion < 0)
751             return false;
752         if ($this->_currentVersion > $this->current->getVersion())
753             return false;       // FIXME: some kind of warning?
754
755         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
756         $meta['markup'] = $is_old_markup ? false : 2.0;
757         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
758         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
759         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
760         if ( ENABLE_CAPTCHA )
761             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
762                 $posted['captcha_input'] : '';
763
764         $this->meta = array_merge($this->meta, $meta);
765         $this->locked = !empty($posted['locked']);
766         if (ENABLE_PAGE_PUBLIC)
767             $this->public = !empty($posted['public']);
768
769         foreach (array('preview','save','edit_convert',
770                        'keep_old','overwrite','diff','upload') as $o) 
771         {
772             if (!empty($posted[$o]))
773                 $this->editaction = $o;
774         }
775         if (empty($this->editaction))
776             $this->editaction = 'edit';
777
778         return true;
779     }
780
781     function _initializeState () {
782         $request = &$this->request;
783         $current = &$this->current;
784         $selected = &$this->selected;
785         $user = &$this->user;
786
787         if (!$selected)
788             NoSuchRevision($request, $this->page, $this->version); // noreturn
789
790         $this->_currentVersion = $current->getVersion();
791         $this->_content = $selected->getPackedContent();
792
793         $this->locked = $this->page->get('locked');
794
795         // If author same as previous author, default minor_edit to on.
796         $age = $this->meta['mtime'] - $current->get('mtime');
797         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
798                                          && $current->get('author') == $user->getId()
799                                          );
800
801         // Default for new pages is new-style markup.
802         if ($selected->hasDefaultContents())
803             $is_new_markup = true;
804         else
805             $is_new_markup = $selected->get('markup') >= 2.0;
806
807         $this->meta['markup'] = $is_new_markup ? 2.0: false;
808         $this->meta['pagetype'] = $selected->get('pagetype');
809         if ($this->meta['pagetype'] == 'wikiblog')
810             $this->meta['summary'] = $selected->get('summary'); // keep blog title
811         else
812             $this->meta['summary'] = '';
813         $this->editaction = 'edit';
814     }
815 }
816
817 class LoadFileConflictPageEditor
818 extends PageEditor
819 {
820     function editPage ($saveFailed = true) {
821         $tokens = &$this->tokens;
822
823         if (!$this->canEdit()) {
824             if ($this->isInitialEdit()) {
825                 return $this->viewSource();
826             }
827             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
828         }
829         elseif ($this->editaction == 'save') {
830             if ($this->savePage()) {
831                 return true;    // Page saved.
832             }
833             $saveFailed = true;
834         }
835
836         if ($saveFailed || $this->isConcurrentUpdate())
837         {
838             // Get the text of the original page, and the two conflicting edits
839             // The diff class takes arrays as input.  So retrieve content as
840             // an array, or convert it as necesary.
841             $orig = $this->page->getRevision($this->_currentVersion);
842             $this_content = explode("\n", $this->_content);
843             $other_content = $this->current->getContent();
844             require_once("lib/diff.php");
845             $diff2 = new Diff($other_content, $this_content);
846             $context_lines = max(4, count($other_content) + 1,
847                                  count($this_content) + 1);
848             $fmt = new BlockDiffFormatter($context_lines);
849
850             $this->_content = $fmt->format($diff2);
851             // FIXME: integrate this into class BlockDiffFormatter
852             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
853                                           $this->_content);
854             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
855                                           $this->_content);
856
857             $this->_currentVersion = $this->current->getVersion();
858             $this->version = $this->_currentVersion;
859             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
860         }
861
862         if ($this->editaction == 'edit_convert')
863             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
864         if ($this->editaction == 'preview')
865             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
866
867         // FIXME: NOT_CURRENT_MESSAGE?
868         $tokens = array_merge($tokens, $this->getFormElements());
869         // we need all GET params for loadfile overwrite
870         if ($this->request->getArg('action') == 'loadfile') {
871                 
872             $this->tokens['HIDDEN_INPUTS'] = 
873                 HTML(HiddenInputs
874                     (array('source' => $this->request->getArg('source'),
875                            'merge'  => 1)),
876                      $this->tokens['HIDDEN_INPUTS']);
877             // add two conflict resolution buttons before preview and save.
878             $tokens['PREVIEW_B'] = HTML(
879                                     Button('submit:edit[keep_old]', 
880                                            _("Keep old"), 'wikiaction'),
881                                     $tokens['SEP'],
882                                     Button('submit:edit[overwrite]', 
883                                            _("Overwrite with new"), 'wikiaction'),
884                                     $tokens['SEP'],
885                                     $tokens['PREVIEW_B']);
886         }
887         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
888             include_once("lib/EditToolbar.php");
889             $toolbar = new EditToolbar();
890             $tokens = array_merge($tokens, $toolbar->getTokens());
891         }
892
893         return $this->output('editpage', _("Merge and Edit: %s"));
894     }
895
896     function output ($template, $title_fs) {
897         $selected = &$this->selected;
898         $current = &$this->current;
899
900         if ($selected && $selected->getVersion() != $current->getVersion()) {
901             $rev = $selected;
902             $pagelink = WikiLink($selected);
903         }
904         else {
905             $rev = $current;
906             $pagelink = WikiLink($this->page);
907         }
908
909         $title = new FormattedText ($title_fs, $pagelink);
910         $this->tokens['HEADER'] = $title;
911         //hack! there's no TITLE in editpage, but in the previous top template
912         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
913             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
914         else
915             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
916                                                         $this->tokens['PAGE_LOCKED_MESSAGE']);
917         $template = Template($template, $this->tokens);
918
919         //GeneratePage($template, $title, $rev);
920         PrintXML($template);
921         return true;
922     }
923
924     function getConflictMessage () {
925         $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.",
926                                     "<<<<<<<",
927                                     "======="),
928                                 HTML::p(_("Please check it through before saving."))));
929         return $message;
930     }
931 }
932
933 // Local Variables:
934 // mode: php
935 // tab-width: 8
936 // c-basic-offset: 4
937 // c-hanging-comment-ender-p: nil
938 // indent-tabs-mode: nil
939 // End:
940 ?>