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