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