]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Display Search&Replace icons only if JS_SEARCHREPLACE is true
[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             if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
778                 $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_replace.png"),
779                                                       'class' => 'toolbar',
780                                                       'alt' => _('Search & Replace'),
781                                                       'title' => _('Search & Replace'),
782                                                       'onclick' => "replace();")));
783                 $toolbar->pushContent(HTML::img(array('src' => $WikiTheme->getImageURL("ed_undo_d.png"),
784                                                       'class' => 'toolbar',
785                                                       'id' => 'sr_undo',
786                                                       'alt' => _('Undo Search & Replace'),
787                                                       'title' => _('Undo Search & Replace'),
788                                                       'onclick' => "do_undo();")));
789             }
790             return HTML($toolbar, $textarea);
791         } else {
792             return $textarea;
793         }
794     }
795
796     private function categoriesPulldown()
797     {
798         /**
799          * @var WikiRequest $request
800          */
801         global $request;
802         global $WikiTheme;
803
804         require_once 'lib/TextSearchQuery.php';
805         $dbi =& $request->_dbi;
806         // KEYWORDS formerly known as $KeywordLinkRegexp
807         $pages = $dbi->titleSearch(new TextSearchQuery(KEYWORDS, true));
808         if ($pages) {
809             $categories = array();
810             while ($p = $pages->next()) {
811                 $page = $p->getName();
812                 $categories[] = "['$page', '%0A----%0A%5B%5B" . $page . "%5D%5D']";
813             }
814             if (!$categories) return '';
815             // Ensure this to be inserted at the very end. Hence we added the id to the function.
816             $more_buttons = HTML::img(array('class' => "toolbar",
817                 'id' => 'tb-categories',
818                 'src' => $WikiTheme->getImageURL("ed_category.png"),
819                 'title' => _("Insert Categories"),
820                 'alt' => _("Insert Categories"), // to detect this at js
821                 'onclick' => "showPulldown('" .
822                     _("Insert Categories")
823                     . "',[" . join(",", $categories) . "],'"
824                     . _("Insert") . "','"
825                     . _("Close") . "','tb-categories')"));
826             return $more_buttons;
827         }
828         return '';
829     }
830
831     private function pluginPulldown()
832     {
833         global $WikiTheme;
834         global $AllAllowedPlugins;
835
836         $plugin_dir = 'lib/plugin';
837         if (defined('PHPWIKI_DIR'))
838             $plugin_dir = PHPWIKI_DIR . "/$plugin_dir";
839         $pd = new fileSet($plugin_dir, '*.php');
840         $plugins = $pd->getFiles();
841         unset($pd);
842         sort($plugins);
843         if (!empty($plugins)) {
844             $plugin_js = '';
845             require_once 'lib/WikiPlugin.php';
846             $w = new WikiPluginLoader();
847             foreach ($plugins as $plugin) {
848                 $pluginName = str_replace(".php", "", $plugin);
849                 if (in_array($pluginName, $AllAllowedPlugins)) {
850                     $p = $w->getPlugin($pluginName, false); // second arg?
851                     // trap php files which aren't WikiPlugin~s
852                     if (strtolower(substr(get_parent_class($p), 0, 10)) == 'wikiplugin') {
853                         $plugin_args = '';
854                         $desc = $p->getArgumentsDescription();
855                         $src = array("\n", '"', "'", '|', '[', ']', '\\');
856                         $replace = array('%0A', '%22', '%27', '%7C', '%5B', '%5D', '%5C');
857                         $desc = str_replace("<br />", ' ', $desc->asXML());
858                         if ($desc)
859                             $plugin_args = ' ' . str_replace($src, $replace, $desc);
860                         $toinsert = "%0A<<" . $pluginName . $plugin_args . ">>"; // args?
861                         $plugin_js .= ",['$pluginName','$toinsert']";
862                     }
863                 }
864             }
865             $plugin_js = substr($plugin_js, 1);
866             $more_buttons = HTML::img(array('class' => "toolbar",
867                 'id' => 'tb-plugins',
868                 'src' => $WikiTheme->getImageURL("ed_plugins.png"),
869                 'title' => _("Insert Plugin"),
870                 'alt' => _("Insert Plugin"),
871                 'onclick' => "showPulldown('" .
872                     _("Insert Plugin")
873                     . "',[" . $plugin_js . "],'"
874                     . _("Insert") . "','"
875                     . _("Close") . "','tb-plugins')"));
876             return $more_buttons;
877         }
878         return '';
879     }
880
881     private function pagesPulldown($query)
882     {
883         /**
884          * @var WikiRequest $request
885          */
886         global $request;
887
888         require_once 'lib/TextSearchQuery.php';
889         $dbi =& $request->_dbi;
890         $page_iter = $dbi->titleSearch(new TextSearchQuery($query, false, 'auto'));
891         if ($page_iter->count() > 0) {
892             global $WikiTheme;
893             $pages = array();
894             while ($p = $page_iter->next()) {
895                 $page = $p->getName();
896                 $pages[] = "['$page', '%5B%5B" . $page . "%5D%5D']";
897             }
898             return HTML::img(array('class' => "toolbar",
899                 'id' => 'tb-pages',
900                 'src' => $WikiTheme->getImageURL("ed_pages.png"),
901                 'title' => _("Insert PageLink"),
902                 'alt' => _("Insert PageLink"),
903                 'onclick' => "showPulldown('" .
904                     _("Insert PageLink")
905                     . "',[" . join(",", $pages) . "],'"
906                     . _("Insert") . "','"
907                     . _("Close") . "','tb-pages')"));
908         }
909         return '';
910     }
911
912     private function templatePulldown($query)
913     {
914         global $request;
915         require_once 'lib/TextSearchQuery.php';
916         $dbi =& $request->_dbi;
917         $page_iter = $dbi->titleSearch(new TextSearchQuery($query, false, 'auto'));
918         if ($page_iter->count()) {
919             global $WikiTheme;
920             $pages_js = '';
921             while ($p = $page_iter->next()) {
922                 $rev = $p->getCurrentRevision();
923                 $toinsert = str_replace(array("\n", '"'), array('_nl', '_quot'), $rev->_get_content());
924                 $pages_js .= ",['" . $p->getName() . "','_nl$toinsert']";
925             }
926             $pages_js = substr($pages_js, 1);
927             if (!empty($pages_js))
928                 return HTML::img
929                 (array('class' => "toolbar",
930                     'id' => 'tb-templates',
931                     'src' => $WikiTheme->getImageURL("ed_template.png"),
932                     'title' => _("Insert Template"),
933                     'alt' => _("Insert Template"),
934                     'onclick' => "showPulldown('" .
935                         _("Insert Template")
936                         . "',[" . $pages_js . "],'"
937                         . _("Insert") . "','"
938                         . _("Close") . "','tb-templates')"));
939         }
940         return '';
941     }
942
943     private function imagePulldown()
944     {
945         global $WikiTheme, $request;
946
947         $image_dir = getUploadFilePath();
948         $pd = new imageOrVideoSet($image_dir, '*');
949         $images = $pd->getFiles();
950         unset($pd);
951         if (defined('UPLOAD_USERDIR') and UPLOAD_USERDIR) {
952             $image_dir .= "/" . $request->_user->_userid;
953             $pd = new imageOrVideoSet($image_dir, '*');
954             $images = array_merge($images, $pd->getFiles());
955             unset($pd);
956         }
957         sort($images);
958         if (!empty($images)) {
959             $image_js = '';
960             foreach ($images as $image) {
961                 $image_js .= ",['$image','{{" . $image . "}}']";
962             }
963             $image_js = substr($image_js, 1);
964             $more_buttons = HTML::img(array('class' => "toolbar",
965                 'id' => 'tb-images',
966                 'src' => $WikiTheme->getImageURL("ed_image.png"),
967                 'title' => _("Insert Image or Video"),
968                 'alt' => _("Insert Image or Video"),
969                 'onclick' => "showPulldown('" .
970                     _("Insert Image or Video")
971                     . "',[" . $image_js . "],'"
972                     . _("Insert") . "','"
973                     . _("Close") . "','tb-images')"));
974             return $more_buttons;
975         }
976         return '';
977     }
978
979     protected function getFormElements()
980     {
981         global $WikiTheme;
982         $request = &$this->request;
983         $page = &$this->page;
984
985         $h = array('action' => 'edit',
986             'pagename' => $page->getName(),
987             'version' => $this->version,
988             'edit[pagetype]' => $this->meta['pagetype'],
989             'edit[current_version]' => $this->_currentVersion);
990
991         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
992         $el['EDIT_TEXTAREA'] = $this->getTextArea();
993         if (ENABLE_CAPTCHA) {
994             $el = array_merge($el, $this->Captcha->getFormElements());
995         }
996         $el['SUMMARY_INPUT']
997             = HTML::input(array('type' => 'text',
998             'class' => 'wikitext',
999             'id' => 'edit-summary',
1000             'name' => 'edit[summary]',
1001             'size' => 50,
1002             'maxlength' => 256,
1003             'value' => $this->meta['summary']));
1004         $el['MINOR_EDIT_CB']
1005             = HTML::input(array('type' => 'checkbox',
1006             'name' => 'edit[minor_edit]',
1007             'id' => 'edit-minor_edit',
1008             'checked' => (bool)$this->meta['is_minor_edit']));
1009         $el['LOCKED_CB']
1010             = HTML::input(array('type' => 'checkbox',
1011             'name' => 'edit[locked]',
1012             'id' => 'edit-locked',
1013             'disabled' => (bool)!$this->user->isAdmin(),
1014             'checked' => (bool)$this->locked));
1015         if (ENABLE_PAGE_PUBLIC) {
1016             $el['PUBLIC_CB']
1017                 = HTML::input(array('type' => 'checkbox',
1018                 'name' => 'edit[public]',
1019                 'id' => 'edit-public',
1020                 'disabled' => (bool)!$this->user->isAdmin(),
1021                 'checked' => (bool)$this->page->get('public')));
1022         }
1023         if (ENABLE_EXTERNAL_PAGES) {
1024             $el['EXTERNAL_CB']
1025                 = HTML::input(array('type' => 'checkbox',
1026                 'name' => 'edit[external]',
1027                 'id' => 'edit-external',
1028                 'disabled' => (bool)!$this->user->isAdmin(),
1029                 'checked' => (bool)$this->page->get('external')));
1030         }
1031         if (ENABLE_WYSIWYG) {
1032             if (($this->version == 0) and ($request->getArg('mode') != 'wysiwyg')) {
1033                 $el['WYSIWYG_B'] = Button(array("action" => "edit", "mode" => "wysiwyg"), "Wysiwyg Editor");
1034             }
1035         }
1036         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
1037             'wikiaction',
1038             array('accesskey' => 'p',
1039                 'title' => _('Preview the current content [alt-p]')));
1040         $el['SAVE_B'] = Button('submit:edit[save]',
1041             _("Save"), 'wikiaction',
1042             array('accesskey' => 's',
1043                 'title' => _('Save the current content as wikipage [alt-s]')));
1044         $el['CHANGES_B'] = Button('submit:edit[diff]',
1045             _("Changes"), 'wikiaction',
1046             array('accesskey' => 'c',
1047                 'title' => _('Preview the current changes as diff [alt-c]')));
1048         $el['UPLOAD_B'] = Button('submit:edit[upload]',
1049             _("Upload"), 'wikiaction',
1050             array('title' => _('Select a local file and press Upload to attach into this page')));
1051         $el['SPELLCHECK_B'] = Button('submit:edit[SpellCheck]',
1052             _("Spell Check"), 'wikiaction',
1053             array('title' => _('Check the spelling')));
1054         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
1055         $el['SEP'] = $WikiTheme->getButtonSeparator();
1056         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.",
1057             HTML::em($this->user->getId()));
1058
1059         return $el;
1060     }
1061
1062     private function _redirectToBrowsePage()
1063     {
1064         $this->request->redirect(WikiURL($this->page, array(), 'absolute_url'));
1065     }
1066
1067     private function _restoreState()
1068     {
1069         $request = &$this->request;
1070
1071         $posted = $request->getArg('edit');
1072         $request->setArg('edit', false);
1073
1074         if (!$posted
1075             || !$request->isPost()
1076             || !in_array($request->getArg('action'), array('edit', 'loadfile'))
1077         )
1078             return false;
1079
1080         if (!isset($posted['content']) || !is_string($posted['content']))
1081             return false;
1082         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
1083             rtrim($posted['content']));
1084         $this->_content = $this->getContent();
1085
1086         $this->_currentVersion = (int)$posted['current_version'];
1087
1088         if ($this->_currentVersion < 0)
1089             return false;
1090         if ($this->_currentVersion > $this->current->getVersion())
1091             return false; // FIXME: some kind of warning?
1092
1093         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
1094         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
1095         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
1096         if (ENABLE_CAPTCHA)
1097             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
1098                 $posted['captcha_input'] : '';
1099
1100         $this->meta = array_merge($this->meta, $meta);
1101         $this->locked = !empty($posted['locked']);
1102         if (ENABLE_PAGE_PUBLIC)
1103             $this->public = !empty($posted['public']);
1104         if (ENABLE_EXTERNAL_PAGES)
1105             $this->external = !empty($posted['external']);
1106
1107         foreach (array('preview', 'save', 'edit_convert',
1108                      'keep_old', 'overwrite', 'diff', 'upload') as $o) {
1109             if (!empty($posted[$o]))
1110                 $this->editaction = $o;
1111         }
1112         if (empty($this->editaction))
1113             $this->editaction = 'edit';
1114
1115         return true;
1116     }
1117
1118     private function _initializeState()
1119     {
1120         $request = &$this->request;
1121         $current = &$this->current;
1122         $selected = &$this->selected;
1123         $user = &$this->user;
1124
1125         if (!$selected)
1126             NoSuchRevision($request, $this->page, $this->version); // noreturn
1127
1128         $this->_currentVersion = $current->getVersion();
1129         $this->_content = $selected->getPackedContent();
1130
1131         $this->locked = $this->page->get('locked');
1132
1133         // If author same as previous author, default minor_edit to on.
1134         $age = $this->meta['mtime'] - $current->get('mtime');
1135         $this->meta['is_minor_edit'] = ($age < MINOR_EDIT_TIMEOUT
1136             && $current->get('author') == $user->getId()
1137         );
1138
1139         $this->meta['pagetype'] = $selected->get('pagetype');
1140         if ($this->meta['pagetype'] == 'wikiblog')
1141             $this->meta['summary'] = $selected->get('summary'); // keep blog title
1142         else
1143             $this->meta['summary'] = '';
1144         $this->editaction = 'edit';
1145     }
1146 }
1147
1148 class LoadFileConflictPageEditor
1149     extends PageEditor
1150 {
1151     public function editPage($saveFailed = true)
1152     {
1153         $tokens = &$this->tokens;
1154
1155         if (!$this->canEdit()) {
1156             if ($this->isInitialEdit()) {
1157                 return $this->viewSource();
1158             }
1159             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
1160         } elseif ($this->editaction == 'save') {
1161             if ($this->savePage()) {
1162                 return true; // Page saved.
1163             }
1164             $saveFailed = true;
1165         }
1166
1167         if ($saveFailed || $this->isConcurrentUpdate()) {
1168             // Get the text of the original page, and the two conflicting edits
1169             // The diff class takes arrays as input.  So retrieve content as
1170             // an array, or convert it as necesary.
1171             $orig = $this->page->getRevision($this->_currentVersion);
1172             $this_content = explode("\n", $this->_content);
1173             $other_content = $this->current->getContent();
1174             require_once 'lib/diff.php';
1175             $diff2 = new Diff($other_content, $this_content);
1176             $context_lines = max(4, count($other_content) + 1,
1177                 count($this_content) + 1);
1178             $fmt = new BlockDiffFormatter($context_lines);
1179
1180             $this->_content = $fmt->format($diff2);
1181             // FIXME: integrate this into class BlockDiffFormatter
1182             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
1183                 $this->_content);
1184             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
1185                 $this->_content);
1186
1187             $this->_currentVersion = $this->current->getVersion();
1188             $this->version = $this->_currentVersion;
1189             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
1190         }
1191
1192         if ($this->editaction == 'edit_convert')
1193             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
1194         if ($this->editaction == 'preview')
1195             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
1196
1197         // FIXME: NOT_CURRENT_MESSAGE?
1198         $tokens = array_merge($tokens, $this->getFormElements());
1199         // we need all GET params for loadfile overwrite
1200         if ($this->request->getArg('action') == 'loadfile') {
1201
1202             $this->tokens['HIDDEN_INPUTS'] =
1203                 HTML(HiddenInputs
1204                     (array('source' => $this->request->getArg('source'),
1205                         'merge' => 1)),
1206                     $this->tokens['HIDDEN_INPUTS']);
1207             // add two conflict resolution buttons before preview and save.
1208             $tokens['PREVIEW_B'] = HTML(
1209                 Button('submit:edit[keep_old]',
1210                     _("Keep old"), 'wikiaction'),
1211                 $tokens['SEP'],
1212                 Button('submit:edit[overwrite]',
1213                     _("Overwrite with new"), 'wikiaction'),
1214                 $tokens['SEP'],
1215                 $tokens['PREVIEW_B']);
1216         }
1217         return $this->output('editpage', _("Merge and Edit: %s"));
1218     }
1219
1220     public function output($template, $title_fs)
1221     {
1222         $selected = &$this->selected;
1223         $current = &$this->current;
1224
1225         if ($selected && $selected->getVersion() != $current->getVersion()) {
1226             $pagelink = WikiLink($selected);
1227         } else {
1228             $pagelink = WikiLink($this->page);
1229         }
1230
1231         $title = new FormattedText ($title_fs, $pagelink);
1232         $this->tokens['HEADER'] = $title;
1233         //hack! there's no TITLE in editpage, but in the previous top template
1234         if (empty($this->tokens['PAGE_LOCKED_MESSAGE']))
1235             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML::h3($title);
1236         else
1237             $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
1238                 $this->tokens['PAGE_LOCKED_MESSAGE']);
1239         $template = Template($template, $this->tokens);
1240
1241         //GeneratePage($template, $title, $rev);
1242         PrintXML($template);
1243         return true;
1244     }
1245
1246     protected function getConflictMessage($unresolved = false)
1247     {
1248         $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.",
1249                 "<<<<<<<",
1250                 "======="),
1251             HTML::p(_("Please check it through before saving."))));
1252         return $message;
1253     }
1254 }
1255
1256 // Local Variables:
1257 // mode: php
1258 // tab-width: 8
1259 // c-basic-offset: 4
1260 // c-hanging-comment-ender-p: nil
1261 // indent-tabs-mode: nil
1262 // End: