2 require_once 'lib/Template.php';
3 require_once 'lib/WikiUser.php';
11 * @var WikiDB_PageRevision $current
18 public $_currentVersion;
20 * @var UserPreferences $_prefs
24 private $_wikicontent;
27 * @param WikiRequest $request
29 function __construct(&$request)
31 $this->request = &$request;
33 $this->user = $request->getUser();
34 $this->page = $request->getPage();
36 $this->current = $this->page->getCurrentRevision(false);
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
44 $this->meta = array('author' => $this->user->getId(),
45 'author_id' => $this->user->getAuthenticatedId(),
48 $this->tokens = array();
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();
57 if (defined('ENABLE_CAPTCHA' and ENABLE_CAPTCHA)) {
58 require_once 'lib/Captcha.php';
59 $this->Captcha = new Captcha($this->meta);
62 $version = $request->getArg('version');
63 if ($version !== false) {
64 $this->selected = $this->page->getRevision($version);
65 $this->version = $version;
67 $this->version = $this->current->getVersion();
68 $this->selected = $this->page->getRevision($this->version);
71 if ($this->_restoreState()) {
72 $this->_initialEdit = false;
74 $this->_initializeState();
75 $this->_initialEdit = true;
77 // The edit request has specified some initial content from a template
78 if (($template = $request->getArg('template'))
79 and $request->_dbi->isWikiPage($template)
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');
90 header("Content-Type: text/html; charset=UTF-8");
93 public function editPage()
96 $tokens = &$this->tokens;
97 $tokens['PAGE_LOCKED_MESSAGE'] = '';
98 $tokens['LOCK_CHANGED_MSG'] = '';
99 $tokens['CONCURRENT_UPDATE_MESSAGE'] = '';
100 $r =& $this->request;
102 if (isset($r->args['pref']['editWidth'])
103 and ($r->getPref('editWidth') != $r->args['pref']['editWidth'])
105 $r->_prefs->set('editWidth', $r->args['pref']['editWidth']);
107 if (isset($r->args['pref']['editHeight'])
108 and ($r->getPref('editHeight') != $r->args['pref']['editHeight'])
110 $r->_prefs->set('editHeight', $r->args['pref']['editHeight']);
113 if ($this->isModerated())
114 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getModeratedMessage();
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()) {
127 $request->setArg('action', false);
128 $r->redirect(WikiURL($r->getArg('save_and_redirect_to')));
129 return true; // Page saved.
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.
141 } // coming from loadfile conflicts
142 elseif ($this->editaction == 'keep_old') {
143 // keep old page and do nothing
144 $this->_redirectToBrowsePage();
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();
156 } elseif ($this->editaction == 'upload') {
158 $plugin = new WikiPluginLoader("UpLoad");
160 // add link to content
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."))));
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();
198 // FIXME: NOT_CURRENT_MESSAGE?
199 $tokens = array_merge($tokens, $this->getFormElements());
201 return $this->output('editpage', _("Edit: %s"));
204 public function output($template, $title_fs)
207 $selected = &$this->selected;
208 $current = &$this->current;
210 if ($selected && $selected->getVersion() != $current->getVersion()) {
212 $pagelink = WikiLink($selected);
215 $pagelink = WikiLink($this->page);
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);
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);
232 public function viewSource()
234 assert($this->isInitialEdit());
235 assert($this->selected);
237 $this->tokens['PAGE_SOURCE'] = $this->_content;
238 $this->tokens['HIDDEN_INPUTS'] = HiddenInputs($this->request->getArgs());
239 return $this->output('viewsource', _("View Source: %s"));
242 private function updateLock()
245 if (!ENABLE_PAGE_PUBLIC && !ENABLE_EXTERNAL_PAGES) {
246 if ((bool)$this->page->get('locked') == (bool)$this->locked)
247 return false; // Not changed.
250 if (!$this->user->isAdmin()) {
251 // FIXME: some sort of message
252 return false; // not allowed.
254 if ((bool)$this->page->get('locked') != (bool)$this->locked) {
255 $this->page->set('locked', (bool)$this->locked);
256 $this->tokens['LOCK_CHANGED_MSG']
258 ? _("Page now locked.")
259 : _("Page now unlocked.") . " ");
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']
266 ? _("Page now public.")
267 : _("Page now not-public."));
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']
276 ? _("Page now external.")
277 : _("Page now not-external.")) . " ";
281 return $changed; // lock changed.
284 public function savePage()
286 $request = &$this->request;
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();
295 // Save failed. No changes made.
296 $this->_redirectToBrowsePage();
300 if (!$this->user->isAdmin() and $this->isSpam()) {
301 $this->_isSpam = true;
305 $page = &$this->page;
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);
313 $this->_content = $this->getContent();
314 $newrevision = $page->save($this->_content,
317 : $this->_currentVersion + 1,
320 if (!is_a($newrevision, 'WikiDB_PageRevision')) {
321 // Save failed. (Concurrent updates).
325 // New contents successfully saved...
328 // Clean out archived versions of this page.
329 require_once 'lib/ArchiveCleaner.php';
330 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
331 $cleaner->cleanPageRevisions($page);
333 /* generate notification emails done in WikiDB::save to catch
334 all direct calls (admin plugins) */
336 // look at the errorstack
337 $errors = $GLOBALS['ErrorManager']->_postponed_errors;
338 $warnings = $GLOBALS['ErrorManager']->getPostponedErrorsAsHTML();
339 $GLOBALS['ErrorManager']->_postponed_errors = $errors;
341 $dbi = $request->getDbh();
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
350 $request->setArg('action', false);
351 $this->_redirectToBrowsePage();
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);
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);
367 $pagelink = WikiLink($page);
369 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
373 protected function isConcurrentUpdate()
375 assert($this->current->getVersion() >= $this->_currentVersion);
376 return $this->current->getVersion() != $this->_currentVersion;
379 protected function canEdit()
381 return !$this->page->get('locked') || $this->user->isAdmin();
384 protected function isInitialEdit()
386 return $this->_initialEdit;
389 private function isUnchanged()
391 $current = &$this->current;
392 return $this->_content == $current->getPackedContent();
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.
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
405 private function isSpam()
407 $current = &$this->current;
408 $request = &$this->request;
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.
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."))));
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())
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))));
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);
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());
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])))));
479 /** Number of external links in the wikitext
481 private function numLinks(&$text)
483 return substr_count($text, "http://") + substr_count($text, "https://");
486 /** Header of the Anti Spam message
488 private function getSpamMessage()
491 HTML(HTML::h2(_("Spam Prevention")),
492 HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
494 _("Sorry for the inconvenience.")),
498 protected function getPreview()
500 require_once 'lib/PageType.php';
501 $this->_content = $this->getContent();
502 return new TransformedText($this->page, $this->_content, $this->meta);
505 protected function getConvertedPreview()
507 require_once 'lib/PageType.php';
508 $this->_content = $this->getContent();
509 return new TransformedText($this->page, $this->_content, $this->meta);
512 private function getDiff()
514 require_once 'lib/diff.php';
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")));
523 // New CSS formatted unified diffs
524 $fmt = new HtmlUnifiedDiffFormatter;
525 $html->pushContent($fmt->format($diff));
530 // possibly convert HTMLAREA content back to Wiki markup
531 private function getContent()
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);
541 $this->meta['pagetype'] = 'html';
543 return $this->_content;
545 return $this->_content;
549 protected function getLockedMessage()
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.")));
558 private function isModerated()
560 return $this->page->get('moderation');
563 private function getModeratedMessage()
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")))));
572 protected function getConflictMessage($unresolved = false)
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.
580 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
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")));
587 $message = HTML::p(_("Please check it through before saving."));
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.",
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.")));
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.")),
602 private function getTextArea()
606 $request = &$this->request;
608 $readonly = !$this->canEdit(); // || $this->isConcurrentUpdate();
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();
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),
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
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") . "'
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
652 $WikiTheme->addMoreAttr('body', "editfocus", "document.getElementById('edit-content]').editarea.focus()");
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
667 printXML(JavaScript('define_f()'));
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',
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;')));
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',
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="','">>','"._('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;")));
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));
771 if (defined('TOOLBAR_TEMPLATE_PULLDOWN') and TOOLBAR_TEMPLATE_PULLDOWN) {
772 $toolbar->pushContent($this->templatePulldown(TOOLBAR_TEMPLATE_PULLDOWN));
774 if (defined('TOOLBAR_IMAGE_PULLDOWN') and TOOLBAR_IMAGE_PULLDOWN) {
775 $toolbar->pushContent($this->imagePulldown());
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',
785 'alt' => _('Undo Search & Replace'),
786 'title' => _('Undo Search & Replace'),
787 'onclick' => "do_undo();")));
788 return HTML($toolbar, $textarea);
794 private function categoriesPulldown()
797 * @var WikiRequest $request
802 require_once 'lib/TextSearchQuery.php';
803 $dbi =& $request->_dbi;
804 // KEYWORDS formerly known as $KeywordLinkRegexp
805 $pages = $dbi->titleSearch(new TextSearchQuery(KEYWORDS, true));
807 $categories = array();
808 while ($p = $pages->next()) {
809 $page = $p->getName();
810 $categories[] = "['$page', '%0A----%0A%5B%5B" . $page . "%5D%5D']";
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;
829 private function pluginPulldown()
832 global $AllAllowedPlugins;
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();
841 if (!empty($plugins)) {
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') {
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());
857 $plugin_args = ' ' . str_replace($src, $replace, $desc);
858 $toinsert = "%0A<<" . $pluginName . $plugin_args . ">>"; // args?
859 $plugin_js .= ",['$pluginName','$toinsert']";
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('" .
871 . "',[" . $plugin_js . "],'"
872 . _("Insert") . "','"
873 . _("Close") . "','tb-plugins')"));
874 return $more_buttons;
879 private function pagesPulldown($query)
882 * @var WikiRequest $request
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) {
892 while ($p = $page_iter->next()) {
893 $page = $p->getName();
894 $pages[] = "['$page', '%5B%5B" . $page . "%5D%5D']";
896 return HTML::img(array('class' => "toolbar",
898 'src' => $WikiTheme->getImageURL("ed_pages.png"),
899 'title' => _("Insert PageLink"),
900 'alt' => _("Insert PageLink"),
901 'onclick' => "showPulldown('" .
903 . "',[" . join(",", $pages) . "],'"
904 . _("Insert") . "','"
905 . _("Close") . "','tb-pages')"));
910 private function templatePulldown($query)
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()) {
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']";
924 $pages_js = substr($pages_js, 1);
925 if (!empty($pages_js))
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('" .
934 . "',[" . $pages_js . "],'"
935 . _("Insert") . "','"
936 . _("Close") . "','tb-templates')"));
941 private function imagePulldown()
943 global $WikiTheme, $request;
945 $image_dir = getUploadFilePath();
946 $pd = new imageOrVideoSet($image_dir, '*');
947 $images = $pd->getFiles();
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());
956 if (!empty($images)) {
958 foreach ($images as $image) {
959 $image_js .= ",['$image','{{" . $image . "}}']";
961 $image_js = substr($image_js, 1);
962 $more_buttons = HTML::img(array('class' => "toolbar",
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;
977 protected function getFormElements()
980 $request = &$this->request;
981 $page = &$this->page;
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);
989 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
990 $el['EDIT_TEXTAREA'] = $this->getTextArea();
991 if (ENABLE_CAPTCHA) {
992 $el = array_merge($el, $this->Captcha->getFormElements());
995 = HTML::input(array('type' => 'text',
996 'class' => 'wikitext',
997 'id' => 'edit-summary',
998 'name' => 'edit[summary]',
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']));
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) {
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')));
1021 if (ENABLE_EXTERNAL_PAGES) {
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')));
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");
1034 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
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()));
1060 private function _redirectToBrowsePage()
1062 $this->request->redirect(WikiURL($this->page, array(), 'absolute_url'));
1065 private function _restoreState()
1067 $request = &$this->request;
1069 $posted = $request->getArg('edit');
1070 $request->setArg('edit', false);
1073 || !$request->isPost()
1074 || !in_array($request->getArg('action'), array('edit', 'loadfile'))
1078 if (!isset($posted['content']) || !is_string($posted['content']))
1080 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
1081 rtrim($posted['content']));
1082 $this->_content = $this->getContent();
1084 $this->_currentVersion = (int)$posted['current_version'];
1086 if ($this->_currentVersion < 0)
1088 if ($this->_currentVersion > $this->current->getVersion())
1089 return false; // FIXME: some kind of warning?
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;
1095 $meta['captcha_input'] = !empty($posted['captcha_input']) ?
1096 $posted['captcha_input'] : '';
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']);
1105 foreach (array('preview', 'save', 'edit_convert',
1106 'keep_old', 'overwrite', 'diff', 'upload') as $o) {
1107 if (!empty($posted[$o]))
1108 $this->editaction = $o;
1110 if (empty($this->editaction))
1111 $this->editaction = 'edit';
1116 private function _initializeState()
1118 $request = &$this->request;
1119 $current = &$this->current;
1120 $selected = &$this->selected;
1121 $user = &$this->user;
1124 NoSuchRevision($request, $this->page, $this->version); // noreturn
1126 $this->_currentVersion = $current->getVersion();
1127 $this->_content = $selected->getPackedContent();
1129 $this->locked = $this->page->get('locked');
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()
1137 $this->meta['pagetype'] = $selected->get('pagetype');
1138 if ($this->meta['pagetype'] == 'wikiblog')
1139 $this->meta['summary'] = $selected->get('summary'); // keep blog title
1141 $this->meta['summary'] = '';
1142 $this->editaction = 'edit';
1146 class LoadFileConflictPageEditor
1149 public function editPage($saveFailed = true)
1151 $tokens = &$this->tokens;
1153 if (!$this->canEdit()) {
1154 if ($this->isInitialEdit()) {
1155 return $this->viewSource();
1157 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
1158 } elseif ($this->editaction == 'save') {
1159 if ($this->savePage()) {
1160 return true; // Page saved.
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);
1178 $this->_content = $fmt->format($diff2);
1179 // FIXME: integrate this into class BlockDiffFormatter
1180 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
1182 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
1185 $this->_currentVersion = $this->current->getVersion();
1186 $this->version = $this->_currentVersion;
1187 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
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?
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') {
1200 $this->tokens['HIDDEN_INPUTS'] =
1202 (array('source' => $this->request->getArg('source'),
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'),
1210 Button('submit:edit[overwrite]',
1211 _("Overwrite with new"), 'wikiaction'),
1213 $tokens['PREVIEW_B']);
1215 return $this->output('editpage', _("Merge and Edit: %s"));
1218 public function output($template, $title_fs)
1220 $selected = &$this->selected;
1221 $current = &$this->current;
1223 if ($selected && $selected->getVersion() != $current->getVersion()) {
1224 $pagelink = WikiLink($selected);
1226 $pagelink = WikiLink($this->page);
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);
1235 $this->tokens['PAGE_LOCKED_MESSAGE'] = HTML(HTML::h3($title),
1236 $this->tokens['PAGE_LOCKED_MESSAGE']);
1237 $template = Template($template, $this->tokens);
1239 //GeneratePage($template, $title, $rev);
1240 PrintXML($template);
1244 protected function getConflictMessage($unresolved = false)
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.",
1249 HTML::p(_("Please check it through before saving."))));
1257 // c-basic-offset: 4
1258 // c-hanging-comment-ender-p: nil
1259 // indent-tabs-mode: nil