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