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