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