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