]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
No underscore for private function
[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
394         if ($this->meta['markup'] != $current->get('markup'))
395             return false;
396
397         return $this->_content == $current->getPackedContent();
398     }
399
400     /**
401      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
402      * Need to check dynamically some blacklist wikipage settings
403      * (plugin WikiAccessRestrictions) and some static blacklist.
404      * DONE:
405      *   More than NUM_SPAM_LINKS (default: 20) new external links.
406      *        Disabled if NUM_SPAM_LINKS is 0
407      *   ENABLE_SPAMASSASSIN:  content patterns by babycart (only php >= 4.3 for now)
408      *   ENABLE_SPAMBLOCKLIST: content domain blacklist
409      */
410     function isSpam()
411     {
412         $current = &$this->current;
413         $request = &$this->request;
414
415         $oldtext = $current->getPackedContent();
416         $newtext =& $this->_content;
417         $numlinks = $this->numLinks($newtext);
418         $newlinks = $numlinks - $this->numLinks($oldtext);
419         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
420         //        better use a certain text : link ratio.
421
422         // 1. Not more than NUM_SPAM_LINKS (default: 20) new external links
423         if ((NUM_SPAM_LINKS > 0) and ($newlinks >= NUM_SPAM_LINKS)) {
424             // Allow strictly authenticated users?
425             // TODO: mail the admin?
426             $this->tokens['PAGE_LOCKED_MESSAGE'] =
427                 HTML($this->getSpamMessage(),
428                     HTML::p(HTML::strong(_("Too many external links."))));
429             return true;
430         }
431         // 2. external babycart (SpamAssassin) check
432         // This will probably prevent from discussing sex or viagra related topics. So beware.
433         if (ENABLE_SPAMASSASSIN) {
434             require_once 'lib/spam_babycart.php';
435             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"),
436                 $this->user->getId())
437             ) {
438                 // TODO: mail the admin
439                 if (is_array($babycart))
440                     $this->tokens['PAGE_LOCKED_MESSAGE'] =
441                         HTML($this->getSpamMessage(),
442                             HTML::p(HTML::em(_("SpamAssassin reports: "),
443                                 join("\n", $babycart))));
444                 return true;
445             }
446         }
447         // 3. extract (new) links and check surbl for blocked domains
448         if (ENABLE_SPAMBLOCKLIST and ($newlinks > 5)) {
449             require_once 'lib/SpamBlocklist.php';
450             require_once 'lib/InlineParser.php';
451             $parsed = TransformLinks($newtext);
452             $oldparsed = TransformLinks($oldtext);
453             $oldlinks = array();
454             foreach ($oldparsed->_content as $link) {
455                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
456                     $uri = $link->_getURL($this->page->getName());
457                     $oldlinks[$uri]++;
458                 }
459             }
460             unset($oldparsed);
461             foreach ($parsed->_content as $link) {
462                 if (isa($link, 'Cached_ExternalLink') and !isa($link, 'Cached_InterwikiLink')) {
463                     $uri = $link->_getURL($this->page->getName());
464                     // only check new links, so admins may add blocked links.
465                     if (!array_key_exists($uri, $oldlinks) and ($res = IsBlackListed($uri))) {
466                         // TODO: mail the admin
467                         $this->tokens['PAGE_LOCKED_MESSAGE'] =
468                             HTML($this->getSpamMessage(),
469                                 HTML::p(HTML::strong(_("External links contain blocked domains:")),
470                                     HTML::ul(HTML::li(sprintf(_("%s is listed at %s with %s"),
471                                         $uri . " [" . $res[2] . "]", $res[0], $res[1])))));
472                         return true;
473                     }
474                 }
475             }
476             unset($oldlinks);
477             unset($parsed);
478             unset($oldparsed);
479         }
480
481         return false;
482     }
483
484     /** Number of external links in the wikitext
485      */
486     function numLinks(&$text)
487     {
488         return substr_count($text, "http://") + substr_count($text, "https://");
489     }
490
491     /** Header of the Anti Spam message
492      */
493     function getSpamMessage()
494     {
495         return
496             HTML(HTML::h2(_("Spam Prevention")),
497                 HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
498                     HTML::br(),
499                     _("Sorry for the inconvenience.")),
500                 HTML::p(""));
501     }
502
503     function getPreview()
504     {
505         require_once 'lib/PageType.php';
506         $this->_content = $this->getContent();
507         return new TransformedText($this->page, $this->_content, $this->meta);
508     }
509
510     function getConvertedPreview()
511     {
512         require_once 'lib/PageType.php';
513         $this->_content = $this->getContent();
514         $this->meta['markup'] = 2.0;
515         $this->_content = ConvertOldMarkup($this->_content);
516         return new TransformedText($this->page, $this->_content, $this->meta);
517     }
518
519     function getDiff()
520     {
521         require_once 'lib/diff.php';
522         $html = HTML();
523
524         $diff = new Diff($this->current->getContent(), explode("\n", $this->getContent()));
525         if ($diff->isEmpty()) {
526             $html->pushContent(HTML::hr(),
527                                HTML::p(array('class' => 'warning_msg'),
528                                        _("Versions are identical")));
529         } else {
530             // New CSS formatted unified diffs
531             $fmt = new HtmlUnifiedDiffFormatter;
532             $html->pushContent($fmt->format($diff));
533         }
534         return $html;
535     }
536
537     // possibly convert HTMLAREA content back to Wiki markup
538     function getContent()
539     {
540         if (ENABLE_WYSIWYG) {
541             // don't store everything as html
542             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
543                 // Wikiwyg shortcut to avoid the InlineTransformer:
544                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
545                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
546                 $this->_content = join("", $xml_output->_content);
547             } else {
548                 $this->meta['pagetype'] = 'html';
549             }
550             return $this->_content;
551         } else {
552             return $this->_content;
553         }
554     }
555
556     function getLockedMessage()
557     {
558         return
559             HTML(HTML::h2(_("Page Locked")),
560                 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
561                 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
562                 HTML::p(_("Sorry for the inconvenience.")));
563     }
564
565     function isModerated()
566     {
567         return $this->page->get('moderation');
568     }
569
570     function getModeratedMessage()
571     {
572         return
573             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
574                 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")))),
575                 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.",
576                     WikiLink(_("UserPreferences")))));
577     }
578
579     function getConflictMessage($unresolved = false)
580     {
581         /*
582          xgettext only knows about c/c++ line-continuation strings
583          it does not know about php's dot operator.
584          We want to translate this entire paragraph as one string, of course.
585          */
586
587         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
588
589         if ($unresolved)
590             $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.",
591                 "<<<<<<< " . _("Your version"),
592                 ">>>>>>> " . _("Other version")));
593         else
594             $message = HTML::p(_("Please check it through before saving."));
595
596         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
597           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
598                        $re_edit_link)),
599           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
600           HTML::li(_("Save your updated changes.")));
601         */
602         return
603             HTML(HTML::h2(_("Conflicting Edits!")),
604                 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
605                 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.")),
606                 $message);
607     }
608
609     function getTextArea()
610     {
611         $request = &$this->request;
612
613         $readonly = !$this->canEdit(); // || $this->isConcurrentUpdate();
614
615         // WYSIWYG will need two pagetypes: raw wikitest and converted html
616         if (ENABLE_WYSIWYG) {
617             $this->_wikicontent = $this->_content;
618             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
619             //                $this->getPreview();
620             //$this->_htmlcontent = $this->_content->asXML();
621         }
622
623         $textarea = HTML::textarea(array('class' => 'wikiedit',
624                 'name' => 'edit[content]',
625                 'id' => 'edit-content',
626                 'rows' => $request->getPref('editHeight'),
627                 'cols' => $request->getPref('editWidth'),
628                 'readonly' => (bool)$readonly),
629             $this->_content);
630         if (ENABLE_WYSIWYG) {
631             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent,
632                 $textarea->getAttr('name'));
633         } else
634             return $textarea;
635     }
636
637     function getFormElements()
638     {
639         global $WikiTheme;
640         $request = &$this->request;
641         $page = &$this->page;
642
643         $h = array('action' => 'edit',
644             'pagename' => $page->getName(),
645             'version' => $this->version,
646             'edit[pagetype]' => $this->meta['pagetype'],
647             'edit[current_version]' => $this->_currentVersion);
648
649         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
650         $el['EDIT_TEXTAREA'] = $this->getTextArea();
651         if (ENABLE_CAPTCHA) {
652             $el = array_merge($el, $this->Captcha->getFormElements());
653         }
654         $el['SUMMARY_INPUT']
655             = HTML::input(array('type' => 'text',
656             'class' => 'wikitext',
657             'id' => 'edit-summary',
658             'name' => 'edit[summary]',
659             'size' => 50,
660             'maxlength' => 256,
661             'value' => $this->meta['summary']));
662         $el['MINOR_EDIT_CB']
663             = HTML::input(array('type' => 'checkbox',
664             'name' => 'edit[minor_edit]',
665             'id' => 'edit-minor_edit',
666             'checked' => (bool)$this->meta['is_minor_edit']));
667         $el['OLD_MARKUP_CB']
668             = HTML::input(array('type' => 'checkbox',
669             'name' => 'edit[markup]',
670             'value' => 'old',
671             'checked' => $this->meta['markup'] < 2.0,
672             'id' => 'useOldMarkup',
673             'onclick' => 'showOldMarkupRules(this.checked)'));
674         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0)
675             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
676         $el['LOCKED_CB']
677             = HTML::input(array('type' => 'checkbox',
678             'name' => 'edit[locked]',
679             'id' => 'edit-locked',
680             'disabled' => (bool)!$this->user->isAdmin(),
681             'checked' => (bool)$this->locked));
682         if (ENABLE_PAGE_PUBLIC) {
683             $el['PUBLIC_CB']
684                 = HTML::input(array('type' => 'checkbox',
685                 'name' => 'edit[public]',
686                 'id' => 'edit-public',
687                 'disabled' => (bool)!$this->user->isAdmin(),
688                 'checked' => (bool)$this->page->get('public')));
689         }
690         if (ENABLE_EXTERNAL_PAGES) {
691             $el['EXTERNAL_CB']
692                 = HTML::input(array('type' => 'checkbox',
693                 'name' => 'edit[external]',
694                 'id' => 'edit-external',
695                 'disabled' => (bool)!$this->user->isAdmin(),
696                 'checked' => (bool)$this->page->get('external')));
697         }
698         if (ENABLE_WYSIWYG) {
699             if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
700                 $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
701             }
702         }
703
704         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
705             'wikiaction',
706             array('accesskey' => 'p',
707                 'title' => 'Preview the current content [alt-p]'));
708
709         //if (!$this->isConcurrentUpdate() && $this->canEdit())
710         $el['SAVE_B'] = Button('submit:edit[save]',
711             _("Save"), 'wikiaction',
712             array('accesskey' => 's',
713                 'title' => 'Save the current content as wikipage [alt-s]'));
714         $el['CHANGES_B'] = Button('submit:edit[diff]',
715             _("Changes"), 'wikiaction',
716             array('accesskey' => 'c',
717                 'title' => 'Preview the current changes as diff [alt-c]'));
718         $el['UPLOAD_B'] = Button('submit:edit[upload]',
719             _("Upload"), 'wikiaction',
720             array('title' => 'Select a local file and press Upload to attach into this page'));
721         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
722             _("Spell Check"), 'wikiaction',
723             array('title' => 'Check the spelling'));
724         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
725
726         $el['WIDTH_PREF']
727             = HTML::input(array('type' => 'text',
728             'size' => 3,
729             'maxlength' => 4,
730             'class' => "numeric",
731             'name' => 'pref[editWidth]',
732             'id' => 'pref-editWidth',
733             'value' => $request->getPref('editWidth'),
734             'onchange' => 'this.form.submit();'));
735         $el['HEIGHT_PREF']
736             = HTML::input(array('type' => 'text',
737             'size' => 3,
738             'maxlength' => 4,
739             'class' => "numeric",
740             'name' => 'pref[editHeight]',
741             'id' => 'pref-editHeight',
742             'value' => $request->getPref('editHeight'),
743             'onchange' => 'this.form.submit();'));
744         $el['SEP'] = $WikiTheme->getButtonSeparator();
745         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.",
746             HTML::em($this->user->getId()));
747
748         return $el;
749     }
750
751     function _redirectToBrowsePage()
752     {
753         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
754     }
755
756     function _restoreState()
757     {
758         $request = &$this->request;
759
760         $posted = $request->getArg('edit');
761         $request->setArg('edit', false);
762
763         if (!$posted
764             || !$request->isPost()
765             || !in_array($request->getArg('action'), array('edit', 'loadfile'))
766         )
767             return false;
768
769         if (!isset($posted['content']) || !is_string($posted['content']))
770             return false;
771         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
772             rtrim($posted['content']));
773         $this->_content = $this->getContent();
774
775         $this->_currentVersion = (int)$posted['current_version'];
776
777         if ($this->_currentVersion < 0)
778             return false;
779         if ($this->_currentVersion > $this->current->getVersion())
780             return false; // FIXME: some kind of warning?
781
782         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
783         $meta['markup'] = $is_old_markup ? false : 2.0;
784         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
785         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
786         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
787         if (ENABLE_CAPTCHA)
788             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
789                 $posted['captcha_input'] : '';
790
791         $this->meta = array_merge($this->meta, $meta);
792         $this->locked = !empty($posted['locked']);
793         if (ENABLE_PAGE_PUBLIC)
794             $this->public = !empty($posted['public']);
795         if (ENABLE_EXTERNAL_PAGES)
796             $this->external = !empty($posted['external']);
797
798         foreach (array('preview', 'save', 'edit_convert',
799                      'keep_old', 'overwrite', 'diff', 'upload') as $o) {
800             if (!empty($posted[$o]))
801                 $this->editaction = $o;
802         }
803         if (empty($this->editaction))
804             $this->editaction = 'edit';
805
806         return true;
807     }
808
809     function _initializeState()
810     {
811         $request = &$this->request;
812         $current = &$this->current;
813         $selected = &$this->selected;
814         $user = &$this->user;
815
816         if (!$selected)
817             NoSuchRevision($request, $this->page, $this->version); // noreturn
818
819         $this->_currentVersion = $current->getVersion();
820         $this->_content = $selected->getPackedContent();
821
822         $this->locked = $this->page->get('locked');
823
824         // If author same as previous author, default minor_edit to on.
825         $age = $this->meta['mtime'] - $current->get('mtime');
826         $this->meta['is_minor_edit'] = ($age < MINOR_EDIT_TIMEOUT
827             && $current->get('author') == $user->getId()
828         );
829
830         // Default for new pages is new-style markup.
831         if ($selected->hasDefaultContents())
832             $is_new_markup = true;
833         else
834             $is_new_markup = $selected->get('markup') >= 2.0;
835
836         $this->meta['markup'] = $is_new_markup ? 2.0 : false;
837         $this->meta['pagetype'] = $selected->get('pagetype');
838         if ($this->meta['pagetype'] == 'wikiblog')
839             $this->meta['summary'] = $selected->get('summary'); // keep blog title
840         else
841             $this->meta['summary'] = '';
842         $this->editaction = 'edit';
843     }
844 }
845
846 class LoadFileConflictPageEditor
847     extends PageEditor
848 {
849     function editPage($saveFailed = true)
850     {
851         $tokens = &$this->tokens;
852
853         if (!$this->canEdit()) {
854             if ($this->isInitialEdit()) {
855                 return $this->viewSource();
856             }
857             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
858         } elseif ($this->editaction == 'save') {
859             if ($this->savePage()) {
860                 return true; // Page saved.
861             }
862             $saveFailed = true;
863         }
864
865         if ($saveFailed || $this->isConcurrentUpdate()) {
866             // Get the text of the original page, and the two conflicting edits
867             // The diff class takes arrays as input.  So retrieve content as
868             // an array, or convert it as necesary.
869             $orig = $this->page->getRevision($this->_currentVersion);
870             $this_content = explode("\n", $this->_content);
871             $other_content = $this->current->getContent();
872             require_once 'lib/diff.php';
873             $diff2 = new Diff($other_content, $this_content);
874             $context_lines = max(4, count($other_content) + 1,
875                 count($this_content) + 1);
876             $fmt = new BlockDiffFormatter($context_lines);
877
878             $this->_content = $fmt->format($diff2);
879             // FIXME: integrate this into class BlockDiffFormatter
880             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
881                 $this->_content);
882             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
883                 $this->_content);
884
885             $this->_currentVersion = $this->current->getVersion();
886             $this->version = $this->_currentVersion;
887             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
888         }
889
890         if ($this->editaction == 'edit_convert')
891             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
892         if ($this->editaction == 'preview')
893             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
894
895         // FIXME: NOT_CURRENT_MESSAGE?
896         $tokens = array_merge($tokens, $this->getFormElements());
897         // we need all GET params for loadfile overwrite
898         if ($this->request->getArg('action') == 'loadfile') {
899
900             $this->tokens['HIDDEN_INPUTS'] =
901                 HTML(HiddenInputs
902                     (array('source' => $this->request->getArg('source'),
903                         'merge' => 1)),
904                     $this->tokens['HIDDEN_INPUTS']);
905             // add two conflict resolution buttons before preview and save.
906             $tokens['PREVIEW_B'] = HTML(
907                 Button('submit:edit[keep_old]',
908                     _("Keep old"), 'wikiaction'),
909                 $tokens['SEP'],
910                 Button('submit:edit[overwrite]',
911                     _("Overwrite with new"), 'wikiaction'),
912                 $tokens['SEP'],
913                 $tokens['PREVIEW_B']);
914         }
915         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
916             include_once 'lib/EditToolbar.php';
917             $toolbar = new EditToolbar();
918             $tokens = array_merge($tokens, $toolbar->getTokens());
919         }
920
921         return $this->output('editpage', _("Merge and Edit: %s"));
922     }
923
924     function output($template, $title_fs)
925     {
926         $selected = &$this->selected;
927         $current = &$this->current;
928
929         if ($selected && $selected->getVersion() != $current->getVersion()) {
930             $rev = $selected;
931             $pagelink = WikiLink($selected);
932         } else {
933             $rev = $current;
934             $pagelink = WikiLink($this->page);
935         }
936
937         $title = new FormattedText ($title_fs, $pagelink);
938         $this->tokens['HEADER'] = $title;
939         //hack! there's no TITLE in editpage, but in the previous top template
940         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
941             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
942         else
943             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
944                 $this->tokens['PAGE_LOCKED_MESSAGE']);
945         $template = Template($template, $this->tokens);
946
947         //GeneratePage($template, $title, $rev);
948         PrintXML($template);
949         return true;
950     }
951
952     function getConflictMessage($unresolved = false)
953     {
954         $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.",
955                 "<<<<<<<",
956                 "======="),
957             HTML::p(_("Please check it through before saving."))));
958         return $message;
959     }
960 }
961
962 // Local Variables:
963 // mode: php
964 // tab-width: 8
965 // c-basic-offset: 4
966 // c-hanging-comment-ender-p: nil
967 // indent-tabs-mode: nil
968 // End: