]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Declare Javascript variables
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 require_once 'lib/Template.php';
3 require_once 'lib/WikiUser.php';
4
5 class PageEditor
6 {
7     public $request;
8     public $user;
9     public $page;
10     /**
11      * @var WikiDB_PageRevision $current
12      */
13     public $current;
14     public $editaction;
15     public $locked;
16     public $public;
17     public $external;
18     public $_currentVersion;
19     /**
20      * @var UserPreferences $_prefs
21      */
22     private $_prefs;
23     private $_isSpam;
24     private $_wikicontent;
25
26     /**
27      * @param WikiRequest $request
28      */
29     function __construct(&$request)
30     {
31         $this->request = &$request;
32
33         $this->user = $request->getUser();
34         $this->page = $request->getPage();
35
36         $this->current = $this->page->getCurrentRevision(false);
37
38         // HACKish short circuit to browse on action=create
39         if ($request->getArg('action') == 'create') {
40             if (!$this->current->hasDefaultContents())
41                 $request->redirect(WikiURL($this->page->getName())); // noreturn
42         }
43
44         $this->meta = array('author' => $this->user->getId(),
45             'author_id' => $this->user->getAuthenticatedId(),
46             'mtime' => time());
47
48         $this->tokens = array();
49
50         if (defined('ENABLE_WYSIWYG') and ENABLE_WYSIWYG) {
51             $backend = WYSIWYG_BACKEND;
52             // TODO: error message
53             require_once("lib/WysiwygEdit/$backend.php");
54             $class = "WysiwygEdit_$backend";
55             $this->WysiwygEdit = new $class();
56         }
57         if (defined('ENABLE_CAPTCHA' and ENABLE_CAPTCHA)) {
58             require_once 'lib/Captcha.php';
59             $this->Captcha = new Captcha($this->meta);
60         }
61
62         $version = $request->getArg('version');
63         if ($version !== false) {
64             $this->selected = $this->page->getRevision($version);
65             $this->version = $version;
66         } else {
67             $this->version = $this->current->getVersion();
68             $this->selected = $this->page->getRevision($this->version);
69         }
70
71         if ($this->_restoreState()) {
72             $this->_initialEdit = false;
73         } else {
74             $this->_initializeState();
75             $this->_initialEdit = true;
76
77             // The edit request has specified some initial content from a template
78             if (($template = $request->getArg('template'))
79                 and $request->_dbi->isWikiPage($template)
80             ) {
81                 $page = $request->_dbi->getPage($template);
82                 $current = $page->getCurrentRevision();
83                 $this->_content = $current->getPackedContent();
84             } elseif ($initial_content = $request->getArg('initial_content')) {
85                 $this->_content = $initial_content;
86                 $this->_redirect_to = $request->getArg('save_and_redirect_to');
87             }
88         }
89         if (!headers_sent())
90             header("Content-Type: text/html; charset=UTF-8");
91     }
92
93     public function editPage()
94     {
95         $saveFailed = false;
96         $tokens = &$this->tokens;
97         $tokens['PAGE_LOCKED_MESSAGE'] = '';
98         $tokens['LOCK_CHANGED_MSG'] = '';
99         $tokens['CONCURRENT_UPDATE_MESSAGE'] = '';
100         $r =& $this->request;
101
102         if (isset($r->args['pref']['editWidth'])
103             and ($r->getPref('editWidth') != $r->args['pref']['editWidth'])
104         ) {
105             $r->_prefs->set('editWidth', $r->args['pref']['editWidth']);
106         }
107         if (isset($r->args['pref']['editHeight'])
108             and ($r->getPref('editHeight') != $r->args['pref']['editHeight'])
109         ) {
110             $r->_prefs->set('editHeight', $r->args['pref']['editHeight']);
111         }
112
113         if ($this->isModerated())
114             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getModeratedMessage();
115
116         if (!$this->canEdit()) {
117             if ($this->isInitialEdit())
118                 return $this->viewSource();
119             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
120         } elseif ($r->getArg('save_and_redirect_to') != "") {
121             if (defined('ENABLE_CAPTCHA') and ENABLE_CAPTCHA && $this->Captcha->Failed()) {
122                 $this->tokens['PAGE_LOCKED_MESSAGE'] =
123                     HTML::p(HTML::h1($this->Captcha->failed_msg));
124             } elseif ($this->savePage()) {
125                 // noreturn
126                 global $request;
127                 $request->setArg('action', false);
128                 $r->redirect(WikiURL($r->getArg('save_and_redirect_to')));
129                 return true; // Page saved.
130             }
131             $saveFailed = true;
132         } elseif ($this->editaction == 'save') {
133             if (defined('ENABLE_CAPTCHA') and ENABLE_CAPTCHA && $this->Captcha->Failed()) {
134                 $this->tokens['PAGE_LOCKED_MESSAGE'] =
135                     HTML::p(HTML::h1($this->Captcha->failed_msg));
136             } elseif ($this->savePage()) {
137                 return true; // Page saved.
138             } else {
139                 $saveFailed = true;
140             }
141         } // coming from loadfile conflicts
142         elseif ($this->editaction == 'keep_old') {
143             // keep old page and do nothing
144             $this->_redirectToBrowsePage();
145             return true;
146         } elseif ($this->editaction == 'overwrite') {
147             // take the new content without diff
148             $source = $this->request->getArg('loadfile');
149             require_once 'lib/loadsave.php';
150             $this->request->setArg('loadfile', 1);
151             $this->request->setArg('overwrite', 1);
152             $this->request->setArg('merge', 0);
153             LoadFileOrDir($this->request);
154             $this->_redirectToBrowsePage();
155             return true;
156         } elseif ($this->editaction == 'upload') {
157             // run plugin UpLoad
158             $plugin = new WikiPluginLoader("UpLoad");
159             $plugin->run();
160             // add link to content
161             ;
162         }
163
164         if ($saveFailed and $this->isConcurrentUpdate()) {
165             // Get the text of the original page, and the two conflicting edits
166             // The diff3 class takes arrays as input.  So retrieve content as
167             // an array, or convert it as necesary.
168             $orig = $this->page->getRevision($this->_currentVersion);
169             // FIXME: what if _currentVersion has be deleted?
170             $orig_content = $orig->getContent();
171             $this_content = explode("\n", $this->_content);
172             $other_content = $this->current->getContent();
173             require_once 'lib/diff3.php';
174             $diff = new diff3($orig_content, $this_content, $other_content);
175             $output = $diff->merged_output(_("Your version"), _("Other version"));
176             // Set the content of the textarea to the merged diff
177             // output, and update the version
178             $this->_content = implode("\n", $output);
179             $this->_currentVersion = $this->current->getVersion();
180             $this->version = $this->_currentVersion;
181             $unresolved = $diff->ConflictingBlocks;
182             $tokens['CONCURRENT_UPDATE_MESSAGE']
183                 = $this->getConflictMessage($unresolved);
184         } elseif ($saveFailed && !$this->_isSpam) {
185             $tokens['CONCURRENT_UPDATE_MESSAGE'] =
186                 HTML(HTML::h2(_("Some internal editing error")),
187                     HTML::p(_("Your are probably trying to edit/create an invalid version of this page.")),
188                     HTML::p(HTML::em(_("&version=-1 might help."))));
189         }
190
191         if ($this->editaction == 'edit_convert')
192             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
193         if ($this->editaction == 'preview')
194             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
195         if ($this->editaction == 'diff')
196             $tokens['PREVIEW_CONTENT'] = $this->getDiff();
197
198         // FIXME: NOT_CURRENT_MESSAGE?
199         $tokens = array_merge($tokens, $this->getFormElements());
200
201         return $this->output('editpage', _("Edit: %s"));
202     }
203
204     public function output($template, $title_fs)
205     {
206         global $WikiTheme;
207         $selected = &$this->selected;
208         $current = &$this->current;
209
210         if ($selected && $selected->getVersion() != $current->getVersion()) {
211             $rev = $selected;
212             $pagelink = WikiLink($selected);
213         } else {
214             $rev = $current;
215             $pagelink = WikiLink($this->page);
216         }
217
218         $title = new FormattedText ($title_fs, $pagelink);
219         // not for dumphtml or viewsource
220         if (defined('ENABLE_WYSIWYG') and ENABLE_WYSIWYG and $template == 'editpage') {
221             $WikiTheme->addMoreHeaders($this->WysiwygEdit->Head());
222             //$tokens['PAGE_SOURCE'] = $this->WysiwygEdit->ConvertBefore($this->_content);
223         }
224         $template = Template($template, $this->tokens);
225         /* Tell google (and others) not to take notice of edit links */
226         if (defined('GOOGLE_LINKS_NOFOLLOW') and GOOGLE_LINKS_NOFOLLOW)
227             $args = array('ROBOTS_META' => "noindex,nofollow");
228         GeneratePage($template, $title, $rev);
229         return true;
230     }
231
232     public function viewSource()
233     {
234         assert($this->isInitialEdit());
235         assert($this->selected);
236
237         $this->tokens['PAGE_SOURCE'] = $this->_content;
238         $this->tokens['HIDDEN_INPUTS'] = HiddenInputs($this->request->getArgs());
239         return $this->output('viewsource', _("View Source: %s"));
240     }
241
242     private function updateLock()
243     {
244         $changed = false;
245         if (!ENABLE_PAGE_PUBLIC && !ENABLE_EXTERNAL_PAGES) {
246             if ((bool)$this->page->get('locked') == (bool)$this->locked)
247                 return false; // Not changed.
248         }
249
250         if (!$this->user->isAdmin()) {
251             // FIXME: some sort of message
252             return false; // not allowed.
253         }
254         if ((bool)$this->page->get('locked') != (bool)$this->locked) {
255             $this->page->set('locked', (bool)$this->locked);
256             $this->tokens['LOCK_CHANGED_MSG']
257                 .= ($this->locked
258                 ? _("Page now locked.")
259                 : _("Page now unlocked.") . " ");
260             $changed = true;
261         }
262         if (defined('ENABLE_PAGE_PUBLIC') and ENABLE_PAGE_PUBLIC and (bool)$this->page->get('public') != (bool)$this->public) {
263             $this->page->set('public', (bool)$this->public);
264             $this->tokens['LOCK_CHANGED_MSG']
265                 .= ($this->public
266                 ? _("Page now public.")
267                 : _("Page now not-public."));
268             $changed = true;
269         }
270
271         if (defined('ENABLE_EXTERNAL_PAGES') and ENABLE_EXTERNAL_PAGES) {
272             if ((bool)$this->page->get('external') != (bool)$this->external) {
273                 $this->page->set('external', (bool)$this->external);
274                 $this->tokens['LOCK_CHANGED_MSG']
275                     = ($this->external
276                     ? _("Page now external.")
277                     : _("Page now not-external.")) . " ";
278                 $changed = true;
279             }
280         }
281         return $changed; // lock changed.
282     }
283
284     public function savePage()
285     {
286         $request = &$this->request;
287
288         if ($this->isUnchanged()) {
289             // Allow admin lock/unlock even if
290             // no text changes were made.
291             if ($this->updateLock()) {
292                 $dbi = $request->getDbh();
293                 $dbi->touch();
294             }
295             // Save failed. No changes made.
296             $this->_redirectToBrowsePage();
297             return true;
298         }
299
300         if (!$this->user->isAdmin() and $this->isSpam()) {
301             $this->_isSpam = true;
302             return false;
303         }
304
305         $page = &$this->page;
306
307         // Include any meta-data from original page version which
308         // has not been explicitly updated.
309         $meta = $this->selected->getMetaData();
310         $meta = array_merge($meta, $this->meta);
311
312         // Save new revision
313         $this->_content = $this->getContent();
314         $newrevision = $page->save($this->_content,
315             $this->version == -1
316                 ? -1
317                 : $this->_currentVersion + 1,
318             // force new?
319             $meta);
320         if (!is_a($newrevision, 'WikiDB_PageRevision')) {
321             // Save failed.  (Concurrent updates).
322             return false;
323         }
324
325         // New contents successfully saved...
326         $this->updateLock();
327
328         // Clean out archived versions of this page.
329         require_once 'lib/ArchiveCleaner.php';
330         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
331         $cleaner->cleanPageRevisions($page);
332
333         /* generate notification emails done in WikiDB::save to catch
334          all direct calls (admin plugins) */
335
336         // look at the errorstack
337         $errors = $GLOBALS['ErrorManager']->_postponed_errors;
338         $warnings = $GLOBALS['ErrorManager']->getPostponedErrorsAsHTML();
339         $GLOBALS['ErrorManager']->_postponed_errors = $errors;
340
341         $dbi = $request->getDbh();
342         $dbi->touch();
343
344         global $WikiTheme;
345         if (empty($warnings->_content) && !$WikiTheme->getImageURL('signature')) {
346             // Do redirect to browse page if no signature has
347             // been defined.  In this case, the user will most
348             // likely not see the rest of the HTML we generate
349             // (below).
350             $request->setArg('action', false);
351             $this->_redirectToBrowsePage();
352             return true;
353         }
354
355         // Force browse of current page version.
356         $request->setArg('version', false);
357         // testme: does preview and more need action=edit?
358         $request->setArg('action', false);
359
360         $template = Template('savepage', $this->tokens);
361         $template->replace('CONTENT', $newrevision->getTransformedContent());
362         if (!empty($warnings->_content)) {
363             $template->replace('WARNINGS', $warnings);
364             unset($GLOBALS['ErrorManager']->_postponed_errors);
365         }
366
367         $pagelink = WikiLink($page);
368
369         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
370         return true;
371     }
372
373     protected function isConcurrentUpdate()
374     {
375         assert($this->current->getVersion() >= $this->_currentVersion);
376         return $this->current->getVersion() != $this->_currentVersion;
377     }
378
379     protected function canEdit()
380     {
381         return !$this->page->get('locked') || $this->user->isAdmin();
382     }
383
384     protected function isInitialEdit()
385     {
386         return $this->_initialEdit;
387     }
388
389     private function isUnchanged()
390     {
391         $current = &$this->current;
392         return $this->_content == $current->getPackedContent();
393     }
394
395     /**
396      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
397      * Need to check dynamically some blacklist wikipage settings
398      * (plugin WikiAccessRestrictions) and some static blacklist.
399      * DONE:
400      *   More than NUM_SPAM_LINKS (default: 20) new external links.
401      *        Disabled if NUM_SPAM_LINKS is 0
402      *   ENABLE_SPAMASSASSIN:  content patterns by babycart (only php >= 4.3 for now)
403      *   ENABLE_SPAMBLOCKLIST: content domain blacklist
404      */
405     private function isSpam()
406     {
407         $current = &$this->current;
408         $request = &$this->request;
409
410         $oldtext = $current->getPackedContent();
411         $newtext =& $this->_content;
412         $numlinks = $this->numLinks($newtext);
413         $newlinks = $numlinks - $this->numLinks($oldtext);
414         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
415         //        better use a certain text : link ratio.
416
417         // 1. Not more than NUM_SPAM_LINKS (default: 20) new external links
418         if ((NUM_SPAM_LINKS > 0) and ($newlinks >= NUM_SPAM_LINKS)) {
419             // Allow strictly authenticated users?
420             // TODO: mail the admin?
421             $this->tokens['PAGE_LOCKED_MESSAGE'] =
422                 HTML($this->getSpamMessage(),
423                     HTML::p(HTML::strong(_("Too many external links."))));
424             return true;
425         }
426         // 2. external babycart (SpamAssassin) check
427         // This will probably prevent from discussing sex or viagra related topics. So beware.
428         if (ENABLE_SPAMASSASSIN) {
429             require_once 'lib/spam_babycart.php';
430             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"),
431                 $this->user->getId())
432             ) {
433                 // TODO: mail the admin
434                 if (is_array($babycart))
435                     $this->tokens['PAGE_LOCKED_MESSAGE'] =
436                         HTML($this->getSpamMessage(),
437                             HTML::p(HTML::em(_("SpamAssassin reports: "),
438                                 join("\n", $babycart))));
439                 return true;
440             }
441         }
442         // 3. extract (new) links and check surbl for blocked domains
443         if (defined('ENABLE_SPAMBLOCKLIST') and ENABLE_SPAMBLOCKLIST and ($newlinks > 5)) {
444             require_once 'lib/SpamBlocklist.php';
445             require_once 'lib/InlineParser.php';
446             $parsed = TransformLinks($newtext);
447             $oldparsed = TransformLinks($oldtext);
448             $oldlinks = array();
449             foreach ($oldparsed->_content as $link) {
450                 if (is_a($link, 'Cached_ExternalLink') and !is_a($link, 'Cached_InterwikiLink')) {
451                     $uri = $link->_getURL($this->page->getName());
452                     $oldlinks[$uri]++;
453                 }
454             }
455             unset($oldparsed);
456             foreach ($parsed->_content as $link) {
457                 if (is_a($link, 'Cached_ExternalLink') and !is_a($link, 'Cached_InterwikiLink')) {
458                     $uri = $link->_getURL($this->page->getName());
459                     // only check new links, so admins may add blocked links.
460                     if (!array_key_exists($uri, $oldlinks) and ($res = IsBlackListed($uri))) {
461                         // TODO: mail the admin
462                         $this->tokens['PAGE_LOCKED_MESSAGE'] =
463                             HTML($this->getSpamMessage(),
464                                 HTML::p(HTML::strong(_("External links contain blocked domains:")),
465                                     HTML::ul(HTML::li(sprintf(_("%s is listed at %s with %s"),
466                                         $uri . " [" . $res[2] . "]", $res[0], $res[1])))));
467                         return true;
468                     }
469                 }
470             }
471             unset($oldlinks);
472             unset($parsed);
473             unset($oldparsed);
474         }
475
476         return false;
477     }
478
479     /** Number of external links in the wikitext
480      */
481     private function numLinks(&$text)
482     {
483         return substr_count($text, "http://") + substr_count($text, "https://");
484     }
485
486     /** Header of the Anti Spam message
487      */
488     private function getSpamMessage()
489     {
490         return
491             HTML(HTML::h2(_("Spam Prevention")),
492                 HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
493                     HTML::br(),
494                     _("Sorry for the inconvenience.")),
495                 HTML::p(""));
496     }
497
498     protected function getPreview()
499     {
500         require_once 'lib/PageType.php';
501         $this->_content = $this->getContent();
502         return new TransformedText($this->page, $this->_content, $this->meta);
503     }
504
505     protected function getConvertedPreview()
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     private function getDiff()
513     {
514         require_once 'lib/diff.php';
515         $html = HTML();
516
517         $diff = new Diff($this->current->getContent(), explode("\n", $this->getContent()));
518         if ($diff->isEmpty()) {
519             $html->pushContent(HTML::hr(),
520                                HTML::p(array('class' => 'warning_msg'),
521                                        _("Versions are identical")));
522         } else {
523             // New CSS formatted unified diffs
524             $fmt = new HtmlUnifiedDiffFormatter;
525             $html->pushContent($fmt->format($diff));
526         }
527         return $html;
528     }
529
530     // possibly convert HTMLAREA content back to Wiki markup
531     private function getContent()
532     {
533         if (ENABLE_WYSIWYG) {
534             // don't store everything as html
535             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
536                 // Wikiwyg shortcut to avoid the InlineTransformer:
537                 if (WYSIWYG_BACKEND == "Wikiwyg") return $this->_content;
538                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
539                 $this->_content = join("", $xml_output->_content);
540             } else {
541                 $this->meta['pagetype'] = 'html';
542             }
543             return $this->_content;
544         } else {
545             return $this->_content;
546         }
547     }
548
549     protected function getLockedMessage()
550     {
551         return
552             HTML(HTML::h2(_("Page Locked")),
553                 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
554                 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
555                 HTML::p(_("Sorry for the inconvenience.")));
556     }
557
558     private function isModerated()
559     {
560         return $this->page->get('moderation');
561     }
562
563     private function getModeratedMessage()
564     {
565         return
566             HTML(HTML::h2(WikiLink(_("ModeratedPage"))),
567                 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")))),
568                 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.",
569                     WikiLink(_("UserPreferences")))));
570     }
571
572     protected function getConflictMessage($unresolved = false)
573     {
574         /*
575          xgettext only knows about c/c++ line-continuation strings
576          it does not know about php's dot operator.
577          We want to translate this entire paragraph as one string, of course.
578          */
579
580         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
581
582         if ($unresolved)
583             $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.",
584                 "<<<<<<< " . _("Your version"),
585                 ">>>>>>> " . _("Other version")));
586         else
587             $message = HTML::p(_("Please check it through before saving."));
588
589         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
590           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
591                        $re_edit_link)),
592           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
593           HTML::li(_("Save your updated changes.")));
594         */
595         return
596             HTML(HTML::h2(_("Conflicting Edits!")),
597                 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
598                 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.")),
599                 $message);
600     }
601
602     private function getTextArea()
603     {
604         global $WikiTheme;
605
606         $request = &$this->request;
607
608         $readonly = !$this->canEdit(); // || $this->isConcurrentUpdate();
609
610         // WYSIWYG will need two pagetypes: raw wikitest and converted html
611         if (ENABLE_WYSIWYG) {
612             $this->_wikicontent = $this->_content;
613             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
614             //                $this->getPreview();
615             //$this->_htmlcontent = $this->_content->asXML();
616         }
617
618         $textarea = HTML::textarea(array('class' => 'wikiedit',
619                 'name' => 'edit[content]',
620                 'id' => 'edit-content',
621                 'rows' => $request->getPref('editHeight'),
622                 'cols' => $request->getPref('editWidth'),
623                 'readonly' => (bool)$readonly),
624             $this->_content);
625
626         if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
627             $this->tokens['JS_SEARCHREPLACE'] = 1;
628             $undo_btn = $WikiTheme->getImageURL("ed_undo.png");
629             $undo_d_btn = $WikiTheme->getImageURL("ed_undo_d.png");
630             // JS_SEARCHREPLACE from walterzorn.de
631             $js = Javascript("
632 uri_undo_btn   = '" . $undo_btn . "'
633 msg_undo_alt   = '" . _("Undo") . "'
634 uri_undo_d_btn = '" . $undo_d_btn . "'
635 msg_undo_d_alt = '" . _("Undo disabled") . "'
636 msg_do_undo    = '" . _("Operation undone") . "'
637 msg_replfound  = '" . _("Substring ā€œ\\1ā€ found \\2 times. Replace with ā€œ\\3ā€?") . "'
638 msg_replnot    = '" . _("String ā€œ%sā€ not found.") . "'
639 msg_repl_title     = '" . _("Search & Replace") . "'
640 msg_repl_search    = '" . _("Search for") . "'
641 msg_repl_replace_with = '" . _("Replace with") . "'
642 msg_repl_ok        = '" . _("OK") . "'
643 msg_repl_close     = '" . _("Close") . "'
644 ");
645             if (empty($WikiTheme->_headers_printed)) {
646                 $WikiTheme->addMoreHeaders($js);
647                 $WikiTheme->addMoreAttr('body', "SearchReplace", " onload='define_f()'");
648             } else { // from an actionpage: WikiBlog, AddComment, WikiForum
649                 printXML($js);
650             }
651         } else {
652             $WikiTheme->addMoreAttr('body', "editfocus", "document.getElementById('edit-content]').editarea.focus()");
653         }
654
655         if (defined('ENABLE_WYSIWYG') and ENABLE_WYSIWYG) {
656             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent,
657                 $textarea->getAttr('name'));
658         } elseif (defined('ENABLE_EDIT_TOOLBAR') and ENABLE_EDIT_TOOLBAR) {
659             $init = JavaScript("var data_path = '" . javascript_quote_string(DATA_PATH) . "';\n");
660             $js = JavaScript('', array('src' => $WikiTheme->_findData("toolbar.js")));
661             if (empty($WikiTheme->_headers_printed)) {
662                 $WikiTheme->addMoreHeaders($init);
663                 $WikiTheme->addMoreHeaders($js);
664             } else { // from an actionpage: WikiBlog, AddComment, WikiForum
665                 printXML($init);
666                 printXML($js);
667                 printXML(JavaScript('define_f()'));
668             }
669             $toolbar = HTML::div(array('class' => 'edit-toolbar', 'id' => 'toolbar'));
670             $toolbar->pushContent(HTML::input(array('src' => $WikiTheme->getImageURL("ed_save.png"),
671                                                     'name' => 'edit[save]',
672                                                     'class' => 'toolbar',
673                                                     'alt' => _('Save'),
674                                                     'title' => _('Save'),
675                                                     'type' => 'image')));
676             $toolbar->pushContent(HTML::input(array('src' => $WikiTheme->getImageURL("ed_preview.png"),
677                                                     'name' => 'edit[preview]',
678                                                     'class' => 'toolbar',
679                                                     'alt' => _('Preview'),
680                                                     'title' => _('Preview'),
681                                                     'type' => 'image')));
682             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_format_bold.png"),
683                                                   'class' => 'toolbar',
684                                                   'alt' => _('Bold text'),
685                                                   'title' => _('Bold text'),
686                                                   'onclick' => "insertTags('**','**','"._('Bold text')."'); return true;")));
687             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_format_italic.png"),
688                                                   'class' => 'toolbar',
689                                                   'alt' => _('Italic text'),
690                                                   'title' => _('Italic text'),
691                                                   'onclick' => "insertTags('//','//','"._('Italic text')."'); return true;")));
692             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_format_strike.png"),
693                                                   'class' => 'toolbar',
694                                                   'alt' => _('Strike-through text'),
695                                                   'title' => _('Strike-through text'),
696                                                   'onclick' => "insertTags('<s>','</s>','"._('Strike-through text')."'); return true;")));
697             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_format_color.png"),
698                                                   'class' => 'toolbar',
699                                                   'alt' => _('Color text'),
700                                                   'title' => _('Color text'),
701                                                   'onclick' => "insertTags('%color=green%','%%','"._('Color text')."'); return true;")));
702             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_pagelink.png"),
703                                                   'class' => 'toolbar',
704                                                   'alt' => _('Link to page'),
705                                                   'title' => _('Link to page'),
706                                                   'onclick' => "insertTags('[[',']]','"._('PageName|optional label')."'); return true;")));
707             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_link.png"),
708                                                   'class' => 'toolbar',
709                                                   'alt' => _('External link (remember http:// prefix)'),
710                                                   'title' => _('External link (remember http:// prefix)'),
711                                                   'onclick' => "insertTags('[[',']]','"._('http://www.example.com|optional label')."'); return true;")));
712             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_headline.png"),
713                                                   'class' => 'toolbar',
714                                                   'alt' => _('Level 1 headline'),
715                                                   'title' => _('Level 1 headline'),
716                                                   'onclick' => 'insertTags("\n== "," ==\n","'._("Headline text").'"); return true;')));
717             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_nowiki.png"),
718                                                   'class' => 'toolbar',
719                                                   'alt' => _('Ignore wiki formatting'),
720                                                   'title' => _('Ignore wiki formatting'),
721                                                   'onclick' => 'insertTags("<verbatim>\n","\n</verbatim>","'._("Insert non-formatted text here").'"); return true;')));
722             global $request;
723             $username = $request->_user->UserName();
724             $signature = " ā€“ā€“[[" . $username . "]] " . CTime() . '\n';
725             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_sig.png"),
726                                                   'class' => 'toolbar',
727                                                   'alt' => _('Your signature'),
728                                                   'title' => _('Your signature'),
729                                                   'onclick' => "insertTags('".$signature."','',''); return true;")));
730             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_hr.png"),
731                                                   'class' => 'toolbar',
732                                                   'alt' => _('Horizontal line'),
733                                                   'title' => _('Horizontal line'),
734                                                   'onclick' => 'insertTags("\n----\n","",""); return true;')));
735             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_table.png"),
736                                                   'class' => 'toolbar',
737                                                   'alt' => _('Sample table'),
738                                                   'title' => _('Sample table'),
739                                                   'onclick' => 'insertTags("\n{| class=\"bordered\"\n|+ This is the table caption\n|-\n! Header A !! Header B !! Header C\n|-\n| Cell A1 || Cell B1 || Cell C1\n|-\n| Cell A2 || Cell B2 || Cell C2\n|-\n| Cell A3 || Cell B3 || Cell C3\n|}\n","",""); return true;')));
740             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_enumlist.png"),
741                                                   'class' => 'toolbar',
742                                                   'alt' => _('Enumeration'),
743                                                   'title' => _('Enumeration'),
744                                                   'onclick' => 'insertTags("\n# Item 1\n# Item 2\n# Item 3\n","",""); return true;')));
745             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_list.png"),
746                                                   'class' => 'toolbar',
747                                                   'alt' => _('List'),
748                                                   'title' => _('List'),
749                                                   'onclick' => 'insertTags("\n* Item 1\n* Item 2\n* Item 3\n","",""); return true;')));
750             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_toc.png"),
751                                                   'class' => 'toolbar',
752                                                   'alt' => _('Table of Contents'),
753                                                   'title' => _('Table of Contents'),
754                                                   'onclick' => 'insertTags("<<CreateToc with_toclink||=1>>\n","",""); return true;')));
755             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_redirect.png"),
756                                                   'class' => 'toolbar',
757                                                   'alt' => _('Redirect'),
758                                                   'title' => _('Redirect'),
759                                                   'onclick' => "insertTags('<<RedirectTo page=&quot;','&quot;>>','"._('Page Name')."'); return true;")));
760             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_templateplugin.png"),
761                                                   'class' => 'toolbar',
762                                                   'alt' => _('Template'),
763                                                   'title' => _('Template'),
764                                                   'onclick' => "insertTags('{{','}}','"._('Template Name')."'); return true;")));
765
766             $toolbar->pushContent($this->categoriesPulldown());
767             $toolbar->pushContent($this->pluginPulldown());
768             if (defined('TOOLBAR_PAGELINK_PULLDOWN') and TOOLBAR_PAGELINK_PULLDOWN) {
769                 $toolbar->pushContent($this->pagesPulldown(TOOLBAR_PAGELINK_PULLDOWN));
770             }
771             if (defined('TOOLBAR_TEMPLATE_PULLDOWN') and TOOLBAR_TEMPLATE_PULLDOWN) {
772                 $toolbar->pushContent($this->templatePulldown(TOOLBAR_TEMPLATE_PULLDOWN));
773             }
774             if (defined('TOOLBAR_IMAGE_PULLDOWN') and TOOLBAR_IMAGE_PULLDOWN) {
775                 $toolbar->pushContent($this->imagePulldown());
776             }
777             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_replace.png"),
778                                                   'class' => 'toolbar',
779                                                   'alt' => _('Search & Replace'),
780                                                   'title' => _('Search & Replace'),
781                                                   'onclick' => "replace();")));
782             $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_undo_d.png"),
783                                                   'class' => 'toolbar',
784                                                   'id' => 'sr_undo',
785                                                   'alt' => _('Undo Search & Replace'),
786                                                   'title' => _('Undo Search & Replace'),
787                                                   'onclick' => "do_undo();")));
788             return HTML($toolbar, $textarea);
789         } else {
790             return $textarea;
791         }
792     }
793
794     private function categoriesPulldown()
795     {
796         /**
797          * @var WikiRequest $request
798          */
799         global $request;
800         global $WikiTheme;
801
802         require_once 'lib/TextSearchQuery.php';
803         $dbi =& $request->_dbi;
804         // KEYWORDS formerly known as $KeywordLinkRegexp
805         $pages = $dbi->titleSearch(new TextSearchQuery(KEYWORDS, true));
806         if ($pages) {
807             $categories = array();
808             while ($p = $pages->next()) {
809                 $page = $p->getName();
810                 $categories[] = "['$page', '%0A----%0A%5B%5B" . $page . "%5D%5D']";
811             }
812             if (!$categories) return '';
813             // Ensure this to be inserted at the very end. Hence we added the id to the function.
814             $more_buttons = HTML::img(array('class' => "toolbar",
815                 'id' => 'tb-categories',
816                 'src' => $WikiTheme->getImageURL("ed_category.png"),
817                 'title' => _("Insert Categories"),
818                 'alt' => _("Insert Categories"), // to detect this at js
819                 'onclick' => "showPulldown('" .
820                     _("Insert Categories")
821                     . "',[" . join(",", $categories) . "],'"
822                     . _("Insert") . "','"
823                     . _("Close") . "','tb-categories')"));
824             return $more_buttons;
825         }
826         return '';
827     }
828
829     private function pluginPulldown()
830     {
831         global $WikiTheme;
832         global $AllAllowedPlugins;
833
834         $plugin_dir = 'lib/plugin';
835         if (defined('PHPWIKI_DIR'))
836             $plugin_dir = PHPWIKI_DIR . "/$plugin_dir";
837         $pd = new fileSet($plugin_dir, '*.php');
838         $plugins = $pd->getFiles();
839         unset($pd);
840         sort($plugins);
841         if (!empty($plugins)) {
842             $plugin_js = '';
843             require_once 'lib/WikiPlugin.php';
844             $w = new WikiPluginLoader();
845             foreach ($plugins as $plugin) {
846                 $pluginName = str_replace(".php", "", $plugin);
847                 if (in_array($pluginName, $AllAllowedPlugins)) {
848                     $p = $w->getPlugin($pluginName, false); // second arg?
849                     // trap php files which aren't WikiPlugin~s
850                     if (strtolower(substr(get_parent_class($p), 0, 10)) == 'wikiplugin') {
851                         $plugin_args = '';
852                         $desc = $p->getArgumentsDescription();
853                         $src = array("\n", '"', "'", '|', '[', ']', '\\');
854                         $replace = array('%0A', '%22', '%27', '%7C', '%5B', '%5D', '%5C');
855                         $desc = str_replace("<br />", ' ', $desc->asXML());
856                         if ($desc)
857                             $plugin_args = ' ' . str_replace($src, $replace, $desc);
858                         $toinsert = "%0A<<" . $pluginName . $plugin_args . ">>"; // args?
859                         $plugin_js .= ",['$pluginName','$toinsert']";
860                     }
861                 }
862             }
863             $plugin_js = substr($plugin_js, 1);
864             $more_buttons = HTML::img(array('class' => "toolbar",
865                 'id' => 'tb-plugins',
866                 'src' => $WikiTheme->getImageURL("ed_plugins.png"),
867                 'title' => _("Insert Plugin"),
868                 'alt' => _("Insert Plugin"),
869                 'onclick' => "showPulldown('" .
870                     _("Insert Plugin")
871                     . "',[" . $plugin_js . "],'"
872                     . _("Insert") . "','"
873                     . _("Close") . "','tb-plugins')"));
874             return $more_buttons;
875         }
876         return '';
877     }
878
879     private function pagesPulldown($query)
880     {
881         /**
882          * @var WikiRequest $request
883          */
884         global $request;
885
886         require_once 'lib/TextSearchQuery.php';
887         $dbi =& $request->_dbi;
888         $page_iter = $dbi->titleSearch(new TextSearchQuery($query, false, 'auto'));
889         if ($page_iter->count() > 0) {
890             global $WikiTheme;
891             $pages = array();
892             while ($p = $page_iter->next()) {
893                 $page = $p->getName();
894                 $pages[] = "['$page', '%5B%5B" . $page . "%5D%5D']";
895             }
896             return HTML::img(array('class' => "toolbar",
897                 'id' => 'tb-pages',
898                 'src' => $WikiTheme->getImageURL("ed_pages.png"),
899                 'title' => _("Insert PageLink"),
900                 'alt' => _("Insert PageLink"),
901                 'onclick' => "showPulldown('" .
902                     _("Insert PageLink")
903                     . "',[" . join(",", $pages) . "],'"
904                     . _("Insert") . "','"
905                     . _("Close") . "','tb-pages')"));
906         }
907         return '';
908     }
909
910     private function templatePulldown($query)
911     {
912         global $request;
913         require_once 'lib/TextSearchQuery.php';
914         $dbi =& $request->_dbi;
915         $page_iter = $dbi->titleSearch(new TextSearchQuery($query, false, 'auto'));
916         if ($page_iter->count()) {
917             global $WikiTheme;
918             $pages_js = '';
919             while ($p = $page_iter->next()) {
920                 $rev = $p->getCurrentRevision();
921                 $toinsert = str_replace(array("\n", '"'), array('_nl', '_quot'), $rev->_get_content());
922                 $pages_js .= ",['" . $p->getName() . "','_nl$toinsert']";
923             }
924             $pages_js = substr($pages_js, 1);
925             if (!empty($pages_js))
926                 return HTML::img
927                 (array('class' => "toolbar",
928                     'id' => 'tb-templates',
929                     'src' => $WikiTheme->getImageURL("ed_template.png"),
930                     'title' => _("Insert Template"),
931                     'alt' => _("Insert Template"),
932                     'onclick' => "showPulldown('" .
933                         _("Insert Template")
934                         . "',[" . $pages_js . "],'"
935                         . _("Insert") . "','"
936                         . _("Close") . "','tb-templates')"));
937         }
938         return '';
939     }
940
941     private function imagePulldown()
942     {
943         global $WikiTheme, $request;
944
945         $image_dir = getUploadFilePath();
946         $pd = new imageOrVideoSet($image_dir, '*');
947         $images = $pd->getFiles();
948         unset($pd);
949         if (defined('UPLOAD_USERDIR') and UPLOAD_USERDIR) {
950             $image_dir .= "/" . $request->_user->_userid;
951             $pd = new imageOrVideoSet($image_dir, '*');
952             $images = array_merge($images, $pd->getFiles());
953             unset($pd);
954         }
955         sort($images);
956         if (!empty($images)) {
957             $image_js = '';
958             foreach ($images as $image) {
959                 $image_js .= ",['$image','{{" . $image . "}}']";
960             }
961             $image_js = substr($image_js, 1);
962             $more_buttons = HTML::img(array('class' => "toolbar",
963                 'id' => 'tb-images',
964                 'src' => $WikiTheme->getImageURL("ed_image.png"),
965                 'title' => _("Insert Image or Video"),
966                 'alt' => _("Insert Image or Video"),
967                 'onclick' => "showPulldown('" .
968                     _("Insert Image or Video")
969                     . "',[" . $image_js . "],'"
970                     . _("Insert") . "','"
971                     . _("Close") . "','tb-images')"));
972             return $more_buttons;
973         }
974         return '';
975     }
976
977     protected function getFormElements()
978     {
979         global $WikiTheme;
980         $request = &$this->request;
981         $page = &$this->page;
982
983         $h = array('action' => 'edit',
984             'pagename' => $page->getName(),
985             'version' => $this->version,
986             'edit[pagetype]' => $this->meta['pagetype'],
987             'edit[current_version]' => $this->_currentVersion);
988
989         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
990         $el['EDIT_TEXTAREA'] = $this->getTextArea();
991         if (ENABLE_CAPTCHA) {
992             $el = array_merge($el, $this->Captcha->getFormElements());
993         }
994         $el['SUMMARY_INPUT']
995             = HTML::input(array('type' => 'text',
996             'class' => 'wikitext',
997             'id' => 'edit-summary',
998             'name' => 'edit[summary]',
999             'size' => 50,
1000             'maxlength' => 256,
1001             'value' => $this->meta['summary']));
1002         $el['MINOR_EDIT_CB']
1003             = HTML::input(array('type' => 'checkbox',
1004             'name' => 'edit[minor_edit]',
1005             'id' => 'edit-minor_edit',
1006             'checked' => (bool)$this->meta['is_minor_edit']));
1007         $el['LOCKED_CB']
1008             = HTML::input(array('type' => 'checkbox',
1009             'name' => 'edit[locked]',
1010             'id' => 'edit-locked',
1011             'disabled' => (bool)!$this->user->isAdmin(),
1012             'checked' => (bool)$this->locked));
1013         if (ENABLE_PAGE_PUBLIC) {
1014             $el['PUBLIC_CB']
1015                 = HTML::input(array('type' => 'checkbox',
1016                 'name' => 'edit[public]',
1017                 'id' => 'edit-public',
1018                 'disabled' => (bool)!$this->user->isAdmin(),
1019                 'checked' => (bool)$this->page->get('public')));
1020         }
1021         if (ENABLE_EXTERNAL_PAGES) {
1022             $el['EXTERNAL_CB']
1023                 = HTML::input(array('type' => 'checkbox',
1024                 'name' => 'edit[external]',
1025                 'id' => 'edit-external',
1026                 'disabled' => (bool)!$this->user->isAdmin(),
1027                 'checked' => (bool)$this->page->get('external')));
1028         }
1029         if (ENABLE_WYSIWYG) {
1030             if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
1031                 $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
1032             }
1033         }
1034         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
1035             'wikiaction',
1036             array('accesskey' => 'p',
1037                 'title' => _('Preview the current content [alt-p]')));
1038         $el['SAVE_B'] = Button('submit:edit[save]',
1039             _("Save"), 'wikiaction',
1040             array('accesskey' => 's',
1041                 'title' => _('Save the current content as wikipage [alt-s]')));
1042         $el['CHANGES_B'] = Button('submit:edit[diff]',
1043             _("Changes"), 'wikiaction',
1044             array('accesskey' => 'c',
1045                 'title' => _('Preview the current changes as diff [alt-c]')));
1046         $el['UPLOAD_B'] = Button('submit:edit[upload]',
1047             _("Upload"), 'wikiaction',
1048             array('title' => _('Select a local file and press Upload to attach into this page')));
1049         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
1050             _("Spell Check"), 'wikiaction',
1051             array('title' => _('Check the spelling')));
1052         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
1053         $el['SEP'] = $WikiTheme->getButtonSeparator();
1054         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.",
1055             HTML::em($this->user->getId()));
1056
1057         return $el;
1058     }
1059
1060     private function _redirectToBrowsePage()
1061     {
1062         $this->request->redirect(WikiURL($this->page, array(), 'absolute_url'));
1063     }
1064
1065     private function _restoreState()
1066     {
1067         $request = &$this->request;
1068
1069         $posted = $request->getArg('edit');
1070         $request->setArg('edit', false);
1071
1072         if (!$posted
1073             || !$request->isPost()
1074             || !in_array($request->getArg('action'), array('edit', 'loadfile'))
1075         )
1076             return false;
1077
1078         if (!isset($posted['content']) || !is_string($posted['content']))
1079             return false;
1080         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
1081             rtrim($posted['content']));
1082         $this->_content = $this->getContent();
1083
1084         $this->_currentVersion = (int)$posted['current_version'];
1085
1086         if ($this->_currentVersion < 0)
1087             return false;
1088         if ($this->_currentVersion > $this->current->getVersion())
1089             return false; // FIXME: some kind of warning?
1090
1091         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
1092         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
1093         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
1094         if (ENABLE_CAPTCHA)
1095             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
1096                 $posted['captcha_input'] : '';
1097
1098         $this->meta = array_merge($this->meta, $meta);
1099         $this->locked = !empty($posted['locked']);
1100         if (ENABLE_PAGE_PUBLIC)
1101             $this->public = !empty($posted['public']);
1102         if (ENABLE_EXTERNAL_PAGES)
1103             $this->external = !empty($posted['external']);
1104
1105         foreach (array('preview', 'save', 'edit_convert',
1106                      'keep_old', 'overwrite', 'diff', 'upload') as $o) {
1107             if (!empty($posted[$o]))
1108                 $this->editaction = $o;
1109         }
1110         if (empty($this->editaction))
1111             $this->editaction = 'edit';
1112
1113         return true;
1114     }
1115
1116     private function _initializeState()
1117     {
1118         $request = &$this->request;
1119         $current = &$this->current;
1120         $selected = &$this->selected;
1121         $user = &$this->user;
1122
1123         if (!$selected)
1124             NoSuchRevision($request, $this->page, $this->version); // noreturn
1125
1126         $this->_currentVersion = $current->getVersion();
1127         $this->_content = $selected->getPackedContent();
1128
1129         $this->locked = $this->page->get('locked');
1130
1131         // If author same as previous author, default minor_edit to on.
1132         $age = $this->meta['mtime'] - $current->get('mtime');
1133         $this->meta['is_minor_edit'] = ($age < MINOR_EDIT_TIMEOUT
1134             && $current->get('author') == $user->getId()
1135         );
1136
1137         $this->meta['pagetype'] = $selected->get('pagetype');
1138         if ($this->meta['pagetype'] == 'wikiblog')
1139             $this->meta['summary'] = $selected->get('summary'); // keep blog title
1140         else
1141             $this->meta['summary'] = '';
1142         $this->editaction = 'edit';
1143     }
1144 }
1145
1146 class LoadFileConflictPageEditor
1147     extends PageEditor
1148 {
1149     public function editPage($saveFailed = true)
1150     {
1151         $tokens = &$this->tokens;
1152
1153         if (!$this->canEdit()) {
1154             if ($this->isInitialEdit()) {
1155                 return $this->viewSource();
1156             }
1157             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
1158         } elseif ($this->editaction == 'save') {
1159             if ($this->savePage()) {
1160                 return true; // Page saved.
1161             }
1162             $saveFailed = true;
1163         }
1164
1165         if ($saveFailed || $this->isConcurrentUpdate()) {
1166             // Get the text of the original page, and the two conflicting edits
1167             // The diff class takes arrays as input.  So retrieve content as
1168             // an array, or convert it as necesary.
1169             $orig = $this->page->getRevision($this->_currentVersion);
1170             $this_content = explode("\n", $this->_content);
1171             $other_content = $this->current->getContent();
1172             require_once 'lib/diff.php';
1173             $diff2 = new Diff($other_content, $this_content);
1174             $context_lines = max(4, count($other_content) + 1,
1175                 count($this_content) + 1);
1176             $fmt = new BlockDiffFormatter($context_lines);
1177
1178             $this->_content = $fmt->format($diff2);
1179             // FIXME: integrate this into class BlockDiffFormatter
1180             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
1181                 $this->_content);
1182             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
1183                 $this->_content);
1184
1185             $this->_currentVersion = $this->current->getVersion();
1186             $this->version = $this->_currentVersion;
1187             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
1188         }
1189
1190         if ($this->editaction == 'edit_convert')
1191             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
1192         if ($this->editaction == 'preview')
1193             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
1194
1195         // FIXME: NOT_CURRENT_MESSAGE?
1196         $tokens = array_merge($tokens, $this->getFormElements());
1197         // we need all GET params for loadfile overwrite
1198         if ($this->request->getArg('action') == 'loadfile') {
1199
1200             $this->tokens['HIDDEN_INPUTS'] =
1201                 HTML(HiddenInputs
1202                     (array('source' => $this->request->getArg('source'),
1203                         'merge' => 1)),
1204                     $this->tokens['HIDDEN_INPUTS']);
1205             // add two conflict resolution buttons before preview and save.
1206             $tokens['PREVIEW_B'] = HTML(
1207                 Button('submit:edit[keep_old]',
1208                     _("Keep old"), 'wikiaction'),
1209                 $tokens['SEP'],
1210                 Button('submit:edit[overwrite]',
1211                     _("Overwrite with new"), 'wikiaction'),
1212                 $tokens['SEP'],
1213                 $tokens['PREVIEW_B']);
1214         }
1215         return $this->output('editpage', _("Merge and Edit: %s"));
1216     }
1217
1218     public function output($template, $title_fs)
1219     {
1220         $selected = &$this->selected;
1221         $current = &$this->current;
1222
1223         if ($selected && $selected->getVersion() != $current->getVersion()) {
1224             $pagelink = WikiLink($selected);
1225         } else {
1226             $pagelink = WikiLink($this->page);
1227         }
1228
1229         $title = new FormattedText ($title_fs, $pagelink);
1230         $this->tokens['HEADER'] = $title;
1231         //hack! there's no TITLE in editpage, but in the previous top template
1232         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
1233             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
1234         else
1235             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
1236                 $this->tokens['PAGE_LOCKED_MESSAGE']);
1237         $template = Template($template, $this->tokens);
1238
1239         //GeneratePage($template, $title, $rev);
1240         PrintXML($template);
1241         return true;
1242     }
1243
1244     protected function getConflictMessage($unresolved = false)
1245     {
1246         $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.",
1247                 "<<<<<<<",
1248                 "======="),
1249             HTML::p(_("Please check it through before saving."))));
1250         return $message;
1251     }
1252 }
1253
1254 // Local Variables:
1255 // mode: php
1256 // tab-width: 8
1257 // c-basic-offset: 4
1258 // c-hanging-comment-ender-p: nil
1259 // indent-tabs-mode: nil
1260 // End: