]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Implement external pages (ENABLE_EXTERNAL_PAGES)
[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      *   Always: More then 20 new external links
411      *   ENABLE_SPAMASSASSIN:  content patterns by babycart (only php >= 4.3 for now)
412      *   ENABLE_SPAMBLOCKLIST: content domain blacklist
413      */
414     function isSpam () {
415         $current = &$this->current;
416         $request = &$this->request;
417
418         $oldtext = $current->getPackedContent();
419         $newtext =& $this->_content;
420         $numlinks = $this->numLinks($newtext);
421         $newlinks = $numlinks - $this->numLinks($oldtext);
422         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
423         //        better use a certain text : link ratio.
424
425         // 1. Not more then 20 new external links
426         if ($newlinks >= NUM_SPAM_LINKS)
427         {
428             // Allow strictly authenticated users?
429             // TODO: mail the admin?
430             $this->tokens['PAGE_LOCKED_MESSAGE'] = 
431                 HTML($this->getSpamMessage(),
432                      HTML::p(HTML::strong(_("Too many external links."))));
433             return true;
434         }
435         // 2. external babycart (SpamAssassin) check
436         // This will probably prevent from discussing sex or viagra related topics. So beware.
437         if (ENABLE_SPAMASSASSIN) {
438             require_once("lib/spam_babycart.php");
439             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"), 
440                                            $this->user->getId())) {
441                 // TODO: mail the admin
442                 if (is_array($babycart))
443                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
444                         HTML($this->getSpamMessage(),
445                              HTML::p(HTML::em(_("SpamAssassin reports: "), 
446                                                 join("\n", $babycart))));
447                 return true;
448             }
449         }
450         // 3. extract (new) links and check surbl for blocked domains
451         if (ENABLE_SPAMBLOCKLIST and ($newlinks > 5)) {
452             require_once("lib/SpamBlocklist.php");
453             require_once("lib/InlineParser.php");
454             $parsed = TransformLinks($newtext);
455             $oldparsed = TransformLinks($oldtext);
456             $oldlinks = array();
457             foreach ($oldparsed->_content as $link) {
458                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
459                     $uri = $link->_getURL($this->page->getName());
460                     $oldlinks[$uri]++;
461                 }
462             }
463             unset($oldparsed);
464             foreach ($parsed->_content as $link) {
465                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
466                     $uri = $link->_getURL($this->page->getName());
467                     // only check new links, so admins may add blocked links.
468                     if (!array_key_exists($uri, $oldlinks) and ($res = IsBlackListed($uri))) {
469                         // TODO: mail the admin
470                         $this->tokens['PAGE_LOCKED_MESSAGE'] = 
471                             HTML($this->getSpamMessage(),
472                                  HTML::p(HTML::strong(_("External links contain blocked domains:")),
473                                          HTML::ul(HTML::li(sprintf(_("%s is listed at %s with %s"), 
474                                                                    $uri." [".$res[2]."]", $res[0], $res[1])))));
475                         return true;
476                     }
477                 }
478             }
479             unset($oldlinks);
480             unset($parsed);
481             unset($oldparsed);
482         }
483
484         return false;
485     }
486
487     /** Number of external links in the wikitext
488      */
489     function numLinks(&$text) {
490         return substr_count($text, "http://") + substr_count($text, "https://");
491     }
492
493     /** Header of the Anti Spam message 
494      */
495     function getSpamMessage () {
496         return
497             HTML(HTML::h2(_("Spam Prevention")),
498                  HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
499                          HTML::br(),
500                          _("Sorry for the inconvenience.")),
501                  HTML::p(""));
502     }
503
504     function getPreview () {
505         require_once('lib/PageType.php');
506         $this->_content = $this->getContent();
507         return new TransformedText($this->page, $this->_content, $this->meta);
508     }
509
510     function getConvertedPreview () {
511         require_once('lib/PageType.php');
512         $this->_content = $this->getContent();
513         $this->meta['markup'] = 2.0;
514         $this->_content = ConvertOldMarkup($this->_content);
515         return new TransformedText($this->page, $this->_content, $this->meta);
516     }
517
518     function getDiff () {
519         require_once('lib/diff.php');
520         $html = HTML();
521         
522         $diff = new Diff($this->current->getContent(), explode("\n", $this->getContent()));
523         if ($diff->isEmpty()) {
524             $html->pushContent(HTML::hr(),
525                                HTML::p('[', _("Versions are identical"),
526                                        ']'));
527         }
528         else {
529             // New CSS formatted unified diffs (ugly in NS4).
530             $fmt = new HtmlUnifiedDiffFormatter;
531             // Use this for old table-formatted diffs.
532             //$fmt = new TableUnifiedDiffFormatter;
533             $html->pushContent($fmt->format($diff));
534         }
535         return $html;
536     }
537
538     // possibly convert HTMLAREA content back to Wiki markup
539     function getContent () {
540         if (ENABLE_WYSIWYG) {
541             // don't store everything as html
542             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
543                 // Wikiwyg shortcut to avoid the InlineTransformer:
544                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
545                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
546                 $this->_content = join("", $xml_output->_content);
547             } else {
548                 $this->meta['pagetype'] = 'html';
549             }
550             return $this->_content;
551         } else {
552             return $this->_content;
553         }
554     }
555
556     function getLockedMessage () {
557         return
558             HTML(HTML::h2(_("Page Locked")),
559                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
560                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
561                  HTML::p(_("Sorry for the inconvenience.")));
562     }
563
564     function isModerated() {
565         return $this->page->get('moderation');
566     }
567     function getModeratedMessage() {
568         return
569             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
570                  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")))),
571                  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.", 
572                          WikiLink(_("UserPreferences")))));
573     }
574     function getConflictMessage ($unresolved = false) {
575         /*
576          xgettext only knows about c/c++ line-continuation strings
577          it does not know about php's dot operator.
578          We want to translate this entire paragraph as one string, of course.
579          */
580
581         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
582
583         if ($unresolved)
584             $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.",
585                                 "<<<<<<< ". _("Your version"),
586                                 ">>>>>>> ". _("Other version")));
587         else
588             $message = HTML::p(_("Please check it through before saving."));
589
590
591
592         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
593           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
594                        $re_edit_link)),
595           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
596           HTML::li(_("Save your updated changes.")));
597         */
598         return
599             HTML(HTML::h2(_("Conflicting Edits!")),
600                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
601                  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.")),
602                  $message);
603     }
604
605
606     function getTextArea () {
607         $request = &$this->request;
608
609         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
610
611         // WYSIWYG will need two pagetypes: raw wikitest and converted html
612         if (ENABLE_WYSIWYG) {
613             $this->_wikicontent = $this->_content;
614             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
615             //                $this->getPreview();
616             //$this->_htmlcontent = $this->_content->asXML();
617         }
618
619         $textarea = HTML::textarea(array('class'=> 'wikiedit',
620                                          'name' => 'edit[content]',
621                                          'id'   => 'edit-content',
622                                          'rows' => $request->getPref('editHeight'),
623                                          'cols' => $request->getPref('editWidth'),
624                                          'readonly' => (bool) $readonly),
625                                    $this->_content);
626         /** <textarea wrap="virtual"> is not valid XHTML but Netscape 4 requires it
627          * to wrap long lines.
628          */
629         if (isBrowserNS4())
630             $textarea->setAttr('wrap', 'virtual');
631         if (ENABLE_WYSIWYG) {
632             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent, 
633                                                 $textarea->getAttr('name'));
634         } else
635             return $textarea;
636     }
637
638     function getFormElements () {
639         global $WikiTheme;
640         $request = &$this->request;
641         $page = &$this->page;
642
643         $h = array('action'   => 'edit',
644                    'pagename' => $page->getName(),
645                    'version'  => $this->version,
646                    'edit[pagetype]' => $this->meta['pagetype'],
647                    'edit[current_version]' => $this->_currentVersion);
648
649         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
650         $el['EDIT_TEXTAREA'] = $this->getTextArea();
651         if ( ENABLE_CAPTCHA ) {
652             $el = array_merge($el, $this->Captcha->getFormElements());
653         }
654         $el['SUMMARY_INPUT']
655             = HTML::input(array('type'  => 'text',
656                                 'class' => 'wikitext',
657                                 'id' => 'edit-summary',
658                                 'name'  => 'edit[summary]',
659                                 'size'  => 50,
660                                 'maxlength' => 256,
661                                 'value' => $this->meta['summary']));
662         $el['MINOR_EDIT_CB']
663             = HTML::input(array('type' => 'checkbox',
664                                 'name'  => 'edit[minor_edit]',
665                                 'id' => 'edit-minor_edit',
666                                 'checked' => (bool) $this->meta['is_minor_edit']));
667         $el['OLD_MARKUP_CB']
668             = HTML::input(array('type' => 'checkbox',
669                                 'name' => 'edit[markup]',
670                                 'value' => 'old',
671                                 'checked' => $this->meta['markup'] < 2.0,
672                                 'id' => 'useOldMarkup',
673                                 'onclick' => 'showOldMarkupRules(this.checked)'));
674         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0) 
675             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
676         $el['LOCKED_CB']
677             = HTML::input(array('type' => 'checkbox',
678                                 'name' => 'edit[locked]',
679                                 'id'   => 'edit-locked',
680                                 'disabled' => (bool) !$this->user->isAdmin(),
681                                 'checked'  => (bool) $this->locked));
682         if (ENABLE_PAGE_PUBLIC) {
683             $el['PUBLIC_CB']
684             = HTML::input(array('type' => 'checkbox',
685                                 'name' => 'edit[public]',
686                                 'id'   => 'edit-public',
687                                 'disabled' => (bool) !$this->user->isAdmin(),
688                                 'checked'  => (bool) $this->page->get('public')));
689         }
690         if (ENABLE_EXTERNAL_PAGES) {
691             $el['EXTERNAL_CB']
692             = HTML::input(array('type' => 'checkbox',
693                                 'name' => 'edit[external]',
694                                 'id'   => 'edit-external',
695                                 'disabled' => (bool) !$this->user->isAdmin(),
696                                 'checked'  => (bool) $this->page->get('external')));
697         }
698         if (ENABLE_WYSIWYG) {
699             if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
700                 $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
701             }
702         }
703
704         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
705                                   'wikiaction',
706                                   array('accesskey'=> 'p', 
707                                      'title' => 'Preview the current content [alt-p]'));
708
709         //if (!$this->isConcurrentUpdate() && $this->canEdit())
710         $el['SAVE_B'] = Button('submit:edit[save]',
711                                _("Save"), 'wikiaction',
712                                array('accesskey'=> 's', 
713                                      'title' => 'Save the current content as wikipage [alt-s]'));
714         $el['CHANGES_B'] = Button('submit:edit[diff]',
715                                _("Changes"), 'wikiaction',
716                                array('accesskey'=> 'c', 
717                                      'title' => 'Preview the current changes as diff [alt-c]'));
718         $el['UPLOAD_B'] = Button('submit:edit[upload]',
719                                _("Upload"), 'wikiaction',
720                                 array('title' => 'Select a local file and press Upload to attach into this page'));
721         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
722                                _("Spell Check"), 'wikiaction',
723                                 array('title' => 'Check the spelling'));
724         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
725
726         $el['WIDTH_PREF'] 
727             = HTML::input(array('type'     => 'text',
728                                 'size'     => 3,
729                                 'maxlength'=> 4,
730                                 'class'    => "numeric",
731                                 'name'     => 'pref[editWidth]',
732                                 'id'       => 'pref-editWidth',
733                                 'value'    => $request->getPref('editWidth'),
734                                 'onchange' => 'this.form.submit();'));
735         $el['HEIGHT_PREF'] 
736             = HTML::input(array('type'     => 'text',
737                                 'size'     => 3,
738                                 'maxlength'=> 4,
739                                 'class'    => "numeric",
740                                 'name'     => 'pref[editHeight]',
741                                 'id'       => 'pref-editHeight',
742                                 'value'    => $request->getPref('editHeight'),
743                                 'onchange' => 'this.form.submit();'));
744         $el['SEP'] = $WikiTheme->getButtonSeparator();
745         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.", 
746                                     HTML::em($this->user->getId()));
747         
748         return $el;
749     }
750
751     function _redirectToBrowsePage() {
752         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
753     }
754
755     function _restoreState () {
756         $request = &$this->request;
757
758         $posted = $request->getArg('edit');
759         $request->setArg('edit', false);
760
761         if (!$posted 
762             || !$request->isPost()
763             || !in_array($request->getArg('action'),array('edit','loadfile')))
764             return false;
765
766         if (!isset($posted['content']) || !is_string($posted['content']))
767             return false;
768         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
769                                         rtrim($posted['content']));
770         $this->_content = $this->getContent();
771
772         $this->_currentVersion = (int) $posted['current_version'];
773
774         if ($this->_currentVersion < 0)
775             return false;
776         if ($this->_currentVersion > $this->current->getVersion())
777             return false;       // FIXME: some kind of warning?
778
779         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
780         $meta['markup'] = $is_old_markup ? false : 2.0;
781         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
782         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
783         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
784         if ( ENABLE_CAPTCHA )
785             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
786                 $posted['captcha_input'] : '';
787
788         $this->meta = array_merge($this->meta, $meta);
789         $this->locked = !empty($posted['locked']);
790         if (ENABLE_PAGE_PUBLIC)
791             $this->public = !empty($posted['public']);
792         if (ENABLE_EXTERNAL_PAGES)
793             $this->external = !empty($posted['external']);
794
795         foreach (array('preview','save','edit_convert',
796                        'keep_old','overwrite','diff','upload') as $o) 
797         {
798             if (!empty($posted[$o]))
799                 $this->editaction = $o;
800         }
801         if (empty($this->editaction))
802             $this->editaction = 'edit';
803
804         return true;
805     }
806
807     function _initializeState () {
808         $request = &$this->request;
809         $current = &$this->current;
810         $selected = &$this->selected;
811         $user = &$this->user;
812
813         if (!$selected)
814             NoSuchRevision($request, $this->page, $this->version); // noreturn
815
816         $this->_currentVersion = $current->getVersion();
817         $this->_content = $selected->getPackedContent();
818
819         $this->locked = $this->page->get('locked');
820
821         // If author same as previous author, default minor_edit to on.
822         $age = $this->meta['mtime'] - $current->get('mtime');
823         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
824                                          && $current->get('author') == $user->getId()
825                                          );
826
827         // Default for new pages is new-style markup.
828         if ($selected->hasDefaultContents())
829             $is_new_markup = true;
830         else
831             $is_new_markup = $selected->get('markup') >= 2.0;
832
833         $this->meta['markup'] = $is_new_markup ? 2.0: false;
834         $this->meta['pagetype'] = $selected->get('pagetype');
835         if ($this->meta['pagetype'] == 'wikiblog')
836             $this->meta['summary'] = $selected->get('summary'); // keep blog title
837         else
838             $this->meta['summary'] = '';
839         $this->editaction = 'edit';
840     }
841 }
842
843 class LoadFileConflictPageEditor
844 extends PageEditor
845 {
846     function editPage ($saveFailed = true) {
847         $tokens = &$this->tokens;
848
849         if (!$this->canEdit()) {
850             if ($this->isInitialEdit()) {
851                 return $this->viewSource();
852             }
853             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
854         }
855         elseif ($this->editaction == 'save') {
856             if ($this->savePage()) {
857                 return true;    // Page saved.
858             }
859             $saveFailed = true;
860         }
861
862         if ($saveFailed || $this->isConcurrentUpdate())
863         {
864             // Get the text of the original page, and the two conflicting edits
865             // The diff class takes arrays as input.  So retrieve content as
866             // an array, or convert it as necesary.
867             $orig = $this->page->getRevision($this->_currentVersion);
868             $this_content = explode("\n", $this->_content);
869             $other_content = $this->current->getContent();
870             require_once("lib/diff.php");
871             $diff2 = new Diff($other_content, $this_content);
872             $context_lines = max(4, count($other_content) + 1,
873                                  count($this_content) + 1);
874             $fmt = new BlockDiffFormatter($context_lines);
875
876             $this->_content = $fmt->format($diff2);
877             // FIXME: integrate this into class BlockDiffFormatter
878             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
879                                           $this->_content);
880             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
881                                           $this->_content);
882
883             $this->_currentVersion = $this->current->getVersion();
884             $this->version = $this->_currentVersion;
885             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
886         }
887
888         if ($this->editaction == 'edit_convert')
889             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
890         if ($this->editaction == 'preview')
891             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
892
893         // FIXME: NOT_CURRENT_MESSAGE?
894         $tokens = array_merge($tokens, $this->getFormElements());
895         // we need all GET params for loadfile overwrite
896         if ($this->request->getArg('action') == 'loadfile') {
897                 
898             $this->tokens['HIDDEN_INPUTS'] = 
899                 HTML(HiddenInputs
900                     (array('source' => $this->request->getArg('source'),
901                            'merge'  => 1)),
902                      $this->tokens['HIDDEN_INPUTS']);
903             // add two conflict resolution buttons before preview and save.
904             $tokens['PREVIEW_B'] = HTML(
905                                     Button('submit:edit[keep_old]', 
906                                            _("Keep old"), 'wikiaction'),
907                                     $tokens['SEP'],
908                                     Button('submit:edit[overwrite]', 
909                                            _("Overwrite with new"), 'wikiaction'),
910                                     $tokens['SEP'],
911                                     $tokens['PREVIEW_B']);
912         }
913         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
914             include_once("lib/EditToolbar.php");
915             $toolbar = new EditToolbar();
916             $tokens = array_merge($tokens, $toolbar->getTokens());
917         }
918
919         return $this->output('editpage', _("Merge and Edit: %s"));
920     }
921
922     function output ($template, $title_fs) {
923         $selected = &$this->selected;
924         $current = &$this->current;
925
926         if ($selected && $selected->getVersion() != $current->getVersion()) {
927             $rev = $selected;
928             $pagelink = WikiLink($selected);
929         }
930         else {
931             $rev = $current;
932             $pagelink = WikiLink($this->page);
933         }
934
935         $title = new FormattedText ($title_fs, $pagelink);
936         $this->tokens['HEADER'] = $title;
937         //hack! there's no TITLE in editpage, but in the previous top template
938         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
939             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
940         else
941             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
942                                                         $this->tokens['PAGE_LOCKED_MESSAGE']);
943         $template = Template($template, $this->tokens);
944
945         //GeneratePage($template, $title, $rev);
946         PrintXML($template);
947         return true;
948     }
949
950     function getConflictMessage () {
951         $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.",
952                                     "<<<<<<<",
953                                     "======="),
954                                 HTML::p(_("Please check it through before saving."))));
955         return $message;
956     }
957 }
958
959 // Local Variables:
960 // mode: php
961 // tab-width: 8
962 // c-basic-offset: 4
963 // c-hanging-comment-ender-p: nil
964 // indent-tabs-mode: nil
965 // End:
966 ?>