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