]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
No tabs
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 // $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
531         $fmt = new HtmlUnifiedDiffFormatter;
532         $html->pushContent($fmt->format($diff));
533     }
534         return $html;
535     }
536
537     // possibly convert HTMLAREA content back to Wiki markup
538     function getContent () {
539         if (ENABLE_WYSIWYG) {
540             // don't store everything as html
541             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
542                 // Wikiwyg shortcut to avoid the InlineTransformer:
543                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
544                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
545                 $this->_content = join("", $xml_output->_content);
546             } else {
547                 $this->meta['pagetype'] = 'html';
548             }
549             return $this->_content;
550         } else {
551             return $this->_content;
552         }
553     }
554
555     function getLockedMessage () {
556         return
557             HTML(HTML::h2(_("Page Locked")),
558                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
559                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
560                  HTML::p(_("Sorry for the inconvenience.")));
561     }
562
563     function isModerated() {
564         return $this->page->get('moderation');
565     }
566     function getModeratedMessage() {
567         return
568             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
569                  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")))),
570                  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.",
571                          WikiLink(_("UserPreferences")))));
572     }
573     function getConflictMessage ($unresolved = false) {
574         /*
575          xgettext only knows about c/c++ line-continuation strings
576          it does not know about php's dot operator.
577          We want to translate this entire paragraph as one string, of course.
578          */
579
580         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
581
582         if ($unresolved)
583             $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.",
584                                 "<<<<<<< ". _("Your version"),
585                                 ">>>>>>> ". _("Other version")));
586         else
587             $message = HTML::p(_("Please check it through before saving."));
588
589
590
591         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
592           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
593                        $re_edit_link)),
594           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
595           HTML::li(_("Save your updated changes.")));
596         */
597         return
598             HTML(HTML::h2(_("Conflicting Edits!")),
599                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
600                  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.")),
601                  $message);
602     }
603
604
605     function getTextArea () {
606         $request = &$this->request;
607
608         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
609
610         // WYSIWYG will need two pagetypes: raw wikitest and converted html
611         if (ENABLE_WYSIWYG) {
612             $this->_wikicontent = $this->_content;
613             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
614             //                $this->getPreview();
615             //$this->_htmlcontent = $this->_content->asXML();
616         }
617
618         $textarea = HTML::textarea(array('class'=> 'wikiedit',
619                                          'name' => 'edit[content]',
620                                          'id'   => 'edit-content',
621                                          'rows' => $request->getPref('editHeight'),
622                                          'cols' => $request->getPref('editWidth'),
623                                          'readonly' => (bool) $readonly),
624                                    $this->_content);
625         if (ENABLE_WYSIWYG) {
626             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent,
627                                                 $textarea->getAttr('name'));
628         } else
629             return $textarea;
630     }
631
632     function getFormElements () {
633         global $WikiTheme;
634         $request = &$this->request;
635         $page = &$this->page;
636
637         $h = array('action'   => 'edit',
638                    'pagename' => $page->getName(),
639                    'version'  => $this->version,
640                    'edit[pagetype]' => $this->meta['pagetype'],
641                    'edit[current_version]' => $this->_currentVersion);
642
643         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
644         $el['EDIT_TEXTAREA'] = $this->getTextArea();
645         if ( ENABLE_CAPTCHA ) {
646             $el = array_merge($el, $this->Captcha->getFormElements());
647         }
648         $el['SUMMARY_INPUT']
649             = HTML::input(array('type'  => 'text',
650                                 'class' => 'wikitext',
651                                 'id' => 'edit-summary',
652                                 'name'  => 'edit[summary]',
653                                 'size'  => 50,
654                                 'maxlength' => 256,
655                                 'value' => $this->meta['summary']));
656         $el['MINOR_EDIT_CB']
657             = HTML::input(array('type' => 'checkbox',
658                                 'name'  => 'edit[minor_edit]',
659                                 'id' => 'edit-minor_edit',
660                                 'checked' => (bool) $this->meta['is_minor_edit']));
661         $el['OLD_MARKUP_CB']
662             = HTML::input(array('type' => 'checkbox',
663                                 'name' => 'edit[markup]',
664                                 'value' => 'old',
665                                 'checked' => $this->meta['markup'] < 2.0,
666                                 'id' => 'useOldMarkup',
667                                 'onclick' => 'showOldMarkupRules(this.checked)'));
668         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0)
669             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
670         $el['LOCKED_CB']
671             = HTML::input(array('type' => 'checkbox',
672                                 'name' => 'edit[locked]',
673                                 'id'   => 'edit-locked',
674                                 'disabled' => (bool) !$this->user->isAdmin(),
675                                 'checked'  => (bool) $this->locked));
676         if (ENABLE_PAGE_PUBLIC) {
677             $el['PUBLIC_CB']
678             = HTML::input(array('type' => 'checkbox',
679                                 'name' => 'edit[public]',
680                                 'id'   => 'edit-public',
681                                 'disabled' => (bool) !$this->user->isAdmin(),
682                                 'checked'  => (bool) $this->page->get('public')));
683         }
684         if (ENABLE_EXTERNAL_PAGES) {
685             $el['EXTERNAL_CB']
686             = HTML::input(array('type' => 'checkbox',
687                                 'name' => 'edit[external]',
688                                 'id'   => 'edit-external',
689                                 'disabled' => (bool) !$this->user->isAdmin(),
690                                 'checked'  => (bool) $this->page->get('external')));
691         }
692         if (ENABLE_WYSIWYG) {
693         if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
694         $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
695         }
696     }
697
698         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
699                                   'wikiaction',
700                                   array('accesskey'=> 'p',
701                                      'title' => 'Preview the current content [alt-p]'));
702
703         //if (!$this->isConcurrentUpdate() && $this->canEdit())
704         $el['SAVE_B'] = Button('submit:edit[save]',
705                                _("Save"), 'wikiaction',
706                                array('accesskey'=> 's',
707                                      'title' => 'Save the current content as wikipage [alt-s]'));
708         $el['CHANGES_B'] = Button('submit:edit[diff]',
709                                _("Changes"), 'wikiaction',
710                                array('accesskey'=> 'c',
711                                      'title' => 'Preview the current changes as diff [alt-c]'));
712         $el['UPLOAD_B'] = Button('submit:edit[upload]',
713                                _("Upload"), 'wikiaction',
714                                 array('title' => 'Select a local file and press Upload to attach into this page'));
715         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
716                                _("Spell Check"), 'wikiaction',
717                                 array('title' => 'Check the spelling'));
718         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
719
720         $el['WIDTH_PREF']
721             = HTML::input(array('type'     => 'text',
722                                 'size'     => 3,
723                                 'maxlength'=> 4,
724                                 'class'    => "numeric",
725                                 'name'     => 'pref[editWidth]',
726                                 'id'       => 'pref-editWidth',
727                                 'value'    => $request->getPref('editWidth'),
728                                 'onchange' => 'this.form.submit();'));
729         $el['HEIGHT_PREF']
730             = HTML::input(array('type'     => 'text',
731                                 'size'     => 3,
732                                 'maxlength'=> 4,
733                                 'class'    => "numeric",
734                                 'name'     => 'pref[editHeight]',
735                                 'id'       => 'pref-editHeight',
736                                 'value'    => $request->getPref('editHeight'),
737                                 'onchange' => 'this.form.submit();'));
738         $el['SEP'] = $WikiTheme->getButtonSeparator();
739         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.",
740                                     HTML::em($this->user->getId()));
741
742         return $el;
743     }
744
745     function _redirectToBrowsePage() {
746         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
747     }
748
749     function _restoreState () {
750         $request = &$this->request;
751
752         $posted = $request->getArg('edit');
753         $request->setArg('edit', false);
754
755         if (!$posted
756         || !$request->isPost()
757             || !in_array($request->getArg('action'),array('edit','loadfile')))
758             return false;
759
760         if (!isset($posted['content']) || !is_string($posted['content']))
761             return false;
762         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
763                                         rtrim($posted['content']));
764         $this->_content = $this->getContent();
765
766         $this->_currentVersion = (int) $posted['current_version'];
767
768         if ($this->_currentVersion < 0)
769             return false;
770         if ($this->_currentVersion > $this->current->getVersion())
771             return false;       // FIXME: some kind of warning?
772
773         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
774         $meta['markup'] = $is_old_markup ? false : 2.0;
775         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
776         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
777         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
778         if ( ENABLE_CAPTCHA )
779         $meta['captcha_input'] = !empty($posted['captcha_input']) ?
780         $posted['captcha_input'] : '';
781
782         $this->meta = array_merge($this->meta, $meta);
783         $this->locked = !empty($posted['locked']);
784         if (ENABLE_PAGE_PUBLIC)
785             $this->public = !empty($posted['public']);
786         if (ENABLE_EXTERNAL_PAGES)
787             $this->external = !empty($posted['external']);
788
789     foreach (array('preview','save','edit_convert',
790                'keep_old','overwrite','diff','upload') as $o)
791     {
792         if (!empty($posted[$o]))
793         $this->editaction = $o;
794     }
795         if (empty($this->editaction))
796             $this->editaction = 'edit';
797
798         return true;
799     }
800
801     function _initializeState () {
802         $request = &$this->request;
803         $current = &$this->current;
804         $selected = &$this->selected;
805         $user = &$this->user;
806
807         if (!$selected)
808             NoSuchRevision($request, $this->page, $this->version); // noreturn
809
810         $this->_currentVersion = $current->getVersion();
811         $this->_content = $selected->getPackedContent();
812
813         $this->locked = $this->page->get('locked');
814
815         // If author same as previous author, default minor_edit to on.
816         $age = $this->meta['mtime'] - $current->get('mtime');
817         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
818                                          && $current->get('author') == $user->getId()
819                                          );
820
821         // Default for new pages is new-style markup.
822         if ($selected->hasDefaultContents())
823             $is_new_markup = true;
824         else
825             $is_new_markup = $selected->get('markup') >= 2.0;
826
827         $this->meta['markup'] = $is_new_markup ? 2.0: false;
828         $this->meta['pagetype'] = $selected->get('pagetype');
829         if ($this->meta['pagetype'] == 'wikiblog')
830             $this->meta['summary'] = $selected->get('summary'); // keep blog title
831         else
832             $this->meta['summary'] = '';
833         $this->editaction = 'edit';
834     }
835 }
836
837 class LoadFileConflictPageEditor
838 extends PageEditor
839 {
840     function editPage ($saveFailed = true) {
841         $tokens = &$this->tokens;
842
843         if (!$this->canEdit()) {
844             if ($this->isInitialEdit()) {
845                 return $this->viewSource();
846         }
847             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
848         }
849         elseif ($this->editaction == 'save') {
850             if ($this->savePage()) {
851                 return true;    // Page saved.
852         }
853             $saveFailed = true;
854         }
855
856         if ($saveFailed || $this->isConcurrentUpdate())
857         {
858             // Get the text of the original page, and the two conflicting edits
859             // The diff class takes arrays as input.  So retrieve content as
860             // an array, or convert it as necesary.
861             $orig = $this->page->getRevision($this->_currentVersion);
862             $this_content = explode("\n", $this->_content);
863             $other_content = $this->current->getContent();
864             require_once("lib/diff.php");
865             $diff2 = new Diff($other_content, $this_content);
866             $context_lines = max(4, count($other_content) + 1,
867                                  count($this_content) + 1);
868             $fmt = new BlockDiffFormatter($context_lines);
869
870             $this->_content = $fmt->format($diff2);
871             // FIXME: integrate this into class BlockDiffFormatter
872             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
873                                           $this->_content);
874             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
875                                           $this->_content);
876
877             $this->_currentVersion = $this->current->getVersion();
878             $this->version = $this->_currentVersion;
879             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
880         }
881
882         if ($this->editaction == 'edit_convert')
883             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
884         if ($this->editaction == 'preview')
885             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
886
887         // FIXME: NOT_CURRENT_MESSAGE?
888         $tokens = array_merge($tokens, $this->getFormElements());
889     // we need all GET params for loadfile overwrite
890     if ($this->request->getArg('action') == 'loadfile') {
891
892         $this->tokens['HIDDEN_INPUTS'] =
893         HTML(HiddenInputs
894             (array('source' => $this->request->getArg('source'),
895                    'merge'  => 1)),
896              $this->tokens['HIDDEN_INPUTS']);
897         // add two conflict resolution buttons before preview and save.
898         $tokens['PREVIEW_B'] = HTML(
899                     Button('submit:edit[keep_old]',
900                        _("Keep old"), 'wikiaction'),
901                     $tokens['SEP'],
902                     Button('submit:edit[overwrite]',
903                        _("Overwrite with new"), 'wikiaction'),
904                     $tokens['SEP'],
905                     $tokens['PREVIEW_B']);
906     }
907     if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
908             include_once("lib/EditToolbar.php");
909             $toolbar = new EditToolbar();
910             $tokens = array_merge($tokens, $toolbar->getTokens());
911     }
912
913         return $this->output('editpage', _("Merge and Edit: %s"));
914     }
915
916     function output ($template, $title_fs) {
917         $selected = &$this->selected;
918         $current = &$this->current;
919
920         if ($selected && $selected->getVersion() != $current->getVersion()) {
921             $rev = $selected;
922             $pagelink = WikiLink($selected);
923         }
924         else {
925             $rev = $current;
926             $pagelink = WikiLink($this->page);
927         }
928
929         $title = new FormattedText ($title_fs, $pagelink);
930     $this->tokens['HEADER'] = $title;
931     //hack! there's no TITLE in editpage, but in the previous top template
932     if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
933         $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
934     else
935         $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
936                             $this->tokens['PAGE_LOCKED_MESSAGE']);
937         $template = Template($template, $this->tokens);
938
939         //GeneratePage($template, $title, $rev);
940         PrintXML($template);
941         return true;
942     }
943
944     function getConflictMessage () {
945         $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.",
946                                     "<<<<<<<",
947                                     "======="),
948                                 HTML::p(_("Please check it through before saving."))));
949         return $message;
950     }
951 }
952
953 // Local Variables:
954 // mode: php
955 // tab-width: 8
956 // c-basic-offset: 4
957 // c-hanging-comment-ender-p: nil
958 // indent-tabs-mode: nil
959 // End:
960 ?>