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