2 rcs_id('$Id: editpage.php,v 1.65 2004-04-18 01:11:52 rurban Exp $');
4 require_once('lib/Template.php');
6 // Not yet enabled, since we cannot convert HTML to Wiki Markup yet.
7 // We might use a HTML PageType, which is contra wiki, but some people might prefer HTML markup.
8 // Todo: change from constant to user preference variable. (or checkbox setting)
9 if (!defined('USE_HTMLAREA')) define('USE_HTMLAREA',false);
10 if (USE_HTMLAREA) require_once('lib/htmlarea.php');
14 function PageEditor (&$request) {
15 $this->request = &$request;
17 $this->user = $request->getUser();
18 $this->page = $request->getPage();
20 $this->current = $this->page->getCurrentRevision();
22 // HACKish short circuit to browse on action=create
23 if ($request->getArg('action') == 'create') {
24 if (! $this->current->hasDefaultContents())
25 $request->redirect(WikiURL($this->page->getName())); // noreturn
29 $this->meta = array('author' => $this->user->getId(),
30 'author_id' => $this->user->getAuthenticatedId(),
33 $this->tokens = array();
35 $version = $request->getArg('version');
36 if ($version !== false) {
37 $this->selected = $this->page->getRevision($version);
38 $this->version = $version;
41 $this->selected = $this->current;
42 $this->version = $this->current->getVersion();
45 if ($this->_restoreState()) {
46 $this->_initialEdit = false;
49 $this->_initializeState();
50 $this->_initialEdit = true;
52 // The edit request has specified some initial content from a template
53 if ( ($template = $request->getArg('template')) and
54 $request->_dbi->isWikiPage($template)) {
55 $page = $request->_dbi->getPage($template);
56 $current = $page->getCurrentRevision();
57 $this->_content = $current->getPackedContent();
58 } elseif ($initial_content = $request->getArg('initial_content')) {
59 $this->_content = $initial_content;
62 header("Content-Type: text/html; charset=" . $GLOBALS['charset']);
65 function editPage () {
67 $tokens = &$this->tokens;
69 if (! $this->canEdit()) {
70 if ($this->isInitialEdit())
71 return $this->viewSource();
72 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
74 elseif ($this->editaction == 'save') {
75 if ($this->savePage())
76 return true; // Page saved.
80 if ($saveFailed || $this->isConcurrentUpdate())
82 // Get the text of the original page, and the two conflicting edits
83 // The diff3 class takes arrays as input. So retrieve content as
84 // an array, or convert it as necesary.
85 $orig = $this->page->getRevision($this->_currentVersion);
86 // FIXME: what if _currentVersion has be deleted?
87 $orig_content = $orig->getContent();
88 $this_content = explode("\n", $this->_content);
89 $other_content = $this->current->getContent();
90 include_once("lib/diff3.php");
91 $diff = new diff3($orig_content, $this_content, $other_content);
92 $output = $diff->merged_output(_("Your version"), _("Other version"));
93 // Set the content of the textarea to the merged diff
94 // output, and update the version
95 $this->_content = implode ("\n", $output);
96 $this->_currentVersion = $this->current->getVersion();
97 $this->version = $this->_currentVersion;
98 $unresolved = $diff->ConflictingBlocks;
99 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
102 if ($this->editaction == 'preview')
103 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
105 // FIXME: NOT_CURRENT_MESSAGE?
107 $tokens = array_merge($tokens, $this->getFormElements());
109 if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
110 $tokens['JS_SEARCHREPLACE'] = 1;
111 $GLOBALS['Theme']->addMoreHeaders(Javascript("
112 var wart=0, d, f, x='', replacewin, pretxt=new Array(), pretxt_anzahl=0;
113 var fag='<font face=\"arial,helvetica,sans-serif\" size=\"-1\">', fr='<font color=\"#cc0000\">', spn='<span class=\"grey\">';
115 function define_f() {
116 f=document.getElementById('editpage');
117 f.editarea=document.getElementById('edit[content]');
118 if(f.rck.style) f.rck.style.color='#ececec';
122 replacewin=window.open('','','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,height=90,width=450');
123 replacewin.window.document.write('<html><head><title>"._("Search & Replace")."</title><style type=\"text/css\"><'+'!'+'-- input.btt {font-family:Tahoma,Verdana,Geneva,sans-serif;font-size:10pt} --'+'></style></head><body bgcolor=\"#dddddd\" onload=\"if(document.forms[0].ein.focus) document.forms[0].ein.focus()\"><form><center><table><tr><td align=\"right\">'+fag+'"._("Search").":</font></td><td align=\"left\"><input type=\"text\" name=\"ein\" size=\"50\" maxlength=\"500\"></td></tr><tr><td align=\"right\">'+fag+' "._("Replace with").":</font></td><td align=\"left\"><input type=\"text\" name=\"aus\" size=\"50\" maxlength=\"500\"></td></tr><tr><td colspan=\"2\" align=\"center\"><input class=\"btt\" type=\"button\" value=\" "._("OK")." \" onclick=\"self.opener.do_replace()\"> <input class=\"btt\" type=\"button\" value=\""._("Close")."\" onclick=\"self.close()\"></td></tr></table></center></form></body></html>');
124 replacewin.window.document.close();
127 function do_replace() {
128 var txt=pretxt[pretxt_anzahl]=f.editarea.value, ein=new RegExp(replacewin.document.forms[0].ein.value,'g'), aus=replacewin.document.forms[0].aus.value;
129 if(ein==''||ein==null) {
130 replacewin.window.document.forms[0].ein.focus();
133 var z_repl=txt.match(ein)? txt.match(ein).length : 0;
134 txt=txt.replace(ein,aus);
135 ein=ein.toString().substring(1,ein.toString().length-2);
136 result(z_repl, 'Substring \"'+ein+'\" found '+z_repl+' times. Replace with \"'+aus+'\"?', txt, 'String \"'+ein+'\" not found.');
137 replacewin.window.focus();
138 replacewin.window.document.forms[0].ein.focus();
140 function result(zahl,frage,txt,alert_txt) {
141 if(wart!=0&&wart.window) {
146 if(window.confirm(frage)==true) {
147 f.editarea.value=txt;
149 if(f.rck.style) f.rck.style.color='#000000';
150 f.rck.value='"._("Undo")."';
152 } else alert(alert_txt);
155 if(pretxt_anzahl==0) return;
156 else if(pretxt_anzahl>0) {
157 f.editarea.value=pretxt[pretxt_anzahl-1];
158 pretxt[pretxt_anzahl]=null;
160 if(pretxt_anzahl==0) {
161 alert('Operation undone.');
162 if(f.rck.style) f.rck.style.color='#ececec';
163 f.rck.value='("._("Undo").")';
164 if(f.rck.blur) f.rck.blur();
169 pretxt[pretxt_anzahl]=f.editarea.value;
171 if(f.rck.style) f.rck.style.color='#000000';
172 f.rck.value='"._("Undo")."';
175 $GLOBALS['Theme']->addMoreAttr('body'," onload='define_f()'");
178 return $this->output('editpage', _("Edit: %s"));
181 function output ($template, $title_fs) {
183 $selected = &$this->selected;
184 $current = &$this->current;
186 if ($selected && $selected->getVersion() != $current->getVersion()) {
188 $pagelink = WikiLink($selected);
192 $pagelink = WikiLink($this->page);
196 $title = new FormattedText ($title_fs, $pagelink);
197 if ($template == 'editpage' and USE_HTMLAREA) {
198 $Theme->addMoreHeaders(Edit_HtmlArea_Head());
199 //$tokens['PAGE_SOURCE'] = Edit_HtmlArea_ConvertBefore($this->_content);
201 $template = Template($template, $this->tokens);
202 GeneratePage($template, $title, $rev);
207 function viewSource () {
208 assert($this->isInitialEdit());
209 assert($this->selected);
211 $this->tokens['PAGE_SOURCE'] = $this->_content;
212 return $this->output('viewsource', _("View Source: %s"));
215 function updateLock() {
216 if ((bool)$this->page->get('locked') == (bool)$this->locked)
217 return false; // Not changed.
219 if (!$this->user->isAdmin()) {
220 // FIXME: some sort of message
221 return false; // not allowed.
224 $this->page->set('locked', (bool)$this->locked);
225 $this->tokens['LOCK_CHANGED_MSG']
226 = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
228 return true; // lock changed.
231 function savePage () {
232 $request = &$this->request;
234 if ($this->isUnchanged()) {
235 // Allow admin lock/unlock even if
236 // no text changes were made.
237 if ($this->updateLock()) {
238 $dbi = $request->getDbh();
241 // Save failed. No changes made.
242 $this->_redirectToBrowsePage();
243 // user will probably not see the rest of this...
244 include_once('lib/display.php');
245 // force browse of current version:
246 $request->setArg('version', false);
247 displayPage($request, 'nochanges');
251 $page = &$this->page;
253 // Include any meta-data from original page version which
254 // has not been explicitly updated.
255 // (Except don't propagate pgsrc_version --- moot for now,
256 // because at present it never gets into the db...)
257 $meta = $this->selected->getMetaData();
258 unset($meta['pgsrc_version']);
259 $meta = array_merge($meta, $this->meta);
262 $this->_content = $this->getContent();
263 $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
264 if (!isa($newrevision, 'wikidb_pagerevision')) {
265 // Save failed. (Concurrent updates).
269 // New contents successfully saved...
272 // Clean out archived versions of this page.
273 include_once('lib/ArchiveCleaner.php');
274 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
275 $cleaner->cleanPageRevisions($page);
277 /* generate notification emails done in WikiDB::save to catch all direct calls
280 $dbi = $request->getDbh();
281 $warnings = $dbi->GenericWarnings();
285 if (empty($warnings) && ! $Theme->getImageURL('signature')) {
286 // Do redirect to browse page if no signature has
287 // been defined. In this case, the user will most
288 // likely not see the rest of the HTML we generate
290 $this->_redirectToBrowsePage();
293 // Force browse of current page version.
294 $request->setArg('version', false);
295 //$request->setArg('action', false);
297 $template = Template('savepage', $this->tokens);
298 $template->replace('CONTENT', $newrevision->getTransformedContent());
299 if (!empty($warnings))
300 $template->replace('WARNINGS', $warnings);
302 $pagelink = WikiLink($page);
304 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
308 function isConcurrentUpdate () {
309 assert($this->current->getVersion() >= $this->_currentVersion);
310 return $this->current->getVersion() != $this->_currentVersion;
313 function canEdit () {
314 return !$this->page->get('locked') || $this->user->isAdmin();
317 function isInitialEdit () {
318 return $this->_initialEdit;
321 function isUnchanged () {
322 $current = &$this->current;
324 if ($this->meta['markup'] != $current->get('markup'))
327 return $this->_content == $current->getPackedContent();
330 function getPreview () {
331 include_once('lib/PageType.php');
332 $this->_content = $this->getContent();
333 return new TransformedText($this->page, $this->_content, $this->meta);
336 // possibly convert HTMLAREA content back to Wiki markup
337 function getContent () {
339 $xml_output = Edit_HtmlArea_ConvertAfter($this->_content);
340 $this->_content = join("",$xml_output->_content);
341 return $this->_content;
343 return $this->_content;
347 function getLockedMessage () {
349 HTML(HTML::h2(_("Page Locked")),
350 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
351 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
352 HTML::p(_("Sorry for the inconvenience.")));
355 function getConflictMessage ($unresolved = false) {
357 xgettext only knows about c/c++ line-continuation strings
358 it does not know about php's dot operator.
359 We want to translate this entire paragraph as one string, of course.
362 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
365 $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.",
366 "<<<<<<< ". _("Your version"),
367 ">>>>>>> ". _("Other version")));
369 $message = HTML::p(_("Please check it through before saving."));
373 /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
374 HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
376 HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
377 HTML::li(_("Save your updated changes.")));
380 HTML(HTML::h2(_("Conflicting Edits!")),
381 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
382 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.")),
387 function getTextArea () {
388 $request = &$this->request;
390 // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
392 $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
394 $html = $this->getPreview();
395 $this->_wikicontent = $this->_content;
396 $this->_content = $html->asXML();
398 $textarea = HTML::textarea(array('class' => 'wikiedit',
399 'name' => 'edit[content]',
400 'id' => 'edit[content]',
401 'rows' => $request->getPref('editHeight'),
402 'cols' => $request->getPref('editWidth'),
403 'readonly' => (bool) $readonly,
404 'wrap' => 'virtual'),
407 return Edit_HtmlArea_Textarea($textarea,$this->_wikicontent,'edit[content]');
412 function getFormElements () {
413 $request = &$this->request;
414 $page = &$this->page;
417 $h = array('action' => 'edit',
418 'pagename' => $page->getName(),
419 'version' => $this->version,
420 'edit[pagetype]' => $this->meta['pagetype'],
421 'edit[current_version]' => $this->_currentVersion);
423 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
426 $el['EDIT_TEXTAREA'] = $this->getTextArea();
429 = HTML::input(array('type' => 'text',
430 'class' => 'wikitext',
431 'name' => 'edit[summary]',
434 'value' => $this->meta['summary']));
436 = HTML::input(array('type' => 'checkbox',
437 'name' => 'edit[minor_edit]',
438 'checked' => (bool) $this->meta['is_minor_edit']));
440 = HTML::input(array('type' => 'checkbox',
441 'name' => 'edit[markup]',
443 'checked' => $this->meta['markup'] < 2.0,
444 'id' => 'useOldMarkup',
445 'onclick' => 'showOldMarkupRules(this.checked)'));
448 = HTML::input(array('type' => 'checkbox',
449 'name' => 'edit[locked]',
450 'disabled' => (bool) !$this->user->isadmin(),
451 'checked' => (bool) $this->locked));
453 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
456 //if (!$this->isConcurrentUpdate() && $this->canEdit())
457 $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
459 $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
464 function _redirectToBrowsePage() {
465 $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
469 function _restoreState () {
470 $request = &$this->request;
472 $posted = $request->getArg('edit');
473 $request->setArg('edit', false);
475 if (!$posted || !$request->isPost()
476 || $request->getArg('action') != 'edit')
479 if (!isset($posted['content']) || !is_string($posted['content']))
481 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
482 rtrim($posted['content']));
483 $this->_content = $this->getContent();
485 $this->_currentVersion = (int) $posted['current_version'];
487 if ($this->_currentVersion < 0)
489 if ($this->_currentVersion > $this->current->getVersion())
490 return false; // FIXME: some kind of warning?
492 $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
493 $meta['markup'] = $is_old_markup ? false : 2.0;
494 $meta['summary'] = trim(substr($posted['summary'], 0, 256));
495 $meta['is_minor_edit'] = !empty($posted['minor_edit']);
496 $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
497 $this->meta = array_merge($this->meta, $meta);
498 $this->locked = !empty($posted['locked']);
500 if (!empty($posted['preview']))
501 $this->editaction = 'preview';
502 elseif (!empty($posted['save']))
503 $this->editaction = 'save';
505 $this->editaction = 'edit';
510 function _initializeState () {
511 $request = &$this->request;
512 $current = &$this->current;
513 $selected = &$this->selected;
514 $user = &$this->user;
517 NoSuchRevision($request, $this->page, $this->version); // noreturn
519 $this->_currentVersion = $current->getVersion();
520 $this->_content = $selected->getPackedContent();
522 $this->meta['summary'] = '';
523 $this->locked = $this->page->get('locked');
525 // If author same as previous author, default minor_edit to on.
526 $age = $this->meta['mtime'] - $current->get('mtime');
527 $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
528 && $current->get('author') == $user->getId()
531 // Default for new pages is new-style markup.
532 if ($selected->hasDefaultContents())
533 $is_new_markup = true;
535 $is_new_markup = $selected->get('markup') >= 2.0;
537 $this->meta['markup'] = $is_new_markup ? 2.0: false;
538 $this->meta['pagetype'] = $selected->get('pagetype');
539 $this->editaction = 'edit';
543 class LoadFileConflictPageEditor
546 function editPage ($saveFailed = true) {
547 $tokens = &$this->tokens;
549 if (!$this->canEdit()) {
550 if ($this->isInitialEdit())
551 return $this->viewSource();
552 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
554 elseif ($this->editaction == 'save') {
555 if ($this->savePage())
556 return true; // Page saved.
560 if ($saveFailed || $this->isConcurrentUpdate())
562 // Get the text of the original page, and the two conflicting edits
563 // The diff class takes arrays as input. So retrieve content as
564 // an array, or convert it as necesary.
565 $orig = $this->page->getRevision($this->_currentVersion);
566 $this_content = explode("\n", $this->_content);
567 $other_content = $this->current->getContent();
568 include_once("lib/diff.php");
569 $diff2 = new Diff($other_content, $this_content);
570 $context_lines = max(4, count($other_content) + 1,
571 count($this_content) + 1);
572 $fmt = new BlockDiffFormatter($context_lines);
574 $this->_content = $fmt->format($diff2);
575 // FIXME: integrate this into class BlockDiffFormatter
576 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
578 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
581 $this->_currentVersion = $this->current->getVersion();
582 $this->version = $this->_currentVersion;
583 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
586 if ($this->editaction == 'preview')
587 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
589 // FIXME: NOT_CURRENT_MESSAGE?
591 $tokens = array_merge($tokens, $this->getFormElements());
593 return $this->output('editpage', _("Merge and Edit: %s"));
594 // FIXME: this doesn't display
597 function output ($template, $title_fs) {
598 $selected = &$this->selected;
599 $current = &$this->current;
601 if ($selected && $selected->getVersion() != $current->getVersion()) {
603 $pagelink = WikiLink($selected);
607 $pagelink = WikiLink($this->page);
610 $title = new FormattedText ($title_fs, $pagelink);
611 $template = Template($template, $this->tokens);
613 //GeneratePage($template, $title, $rev);
617 function getConflictMessage () {
618 $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.",
621 HTML::p(_("Please check it through before saving."))));
627 $Log: not supported by cvs2svn $
628 Revision 1.64 2004/04/06 19:48:56 rurban
629 temp workaround for action=edit AddComment form
631 Revision 1.63 2004/03/24 19:39:02 rurban
632 php5 workaround code (plus some interim debugging code in XmlElement)
633 php5 doesn't work yet with the current XmlElement class constructors,
634 WikiUserNew does work better than php4.
635 rewrote WikiUserNew user upgrading to ease php5 update
636 fixed pref handling in WikiUserNew
637 added Email Notification
638 added simple Email verification
639 removed emailVerify userpref subclass: just a email property
640 changed pref binary storage layout: numarray => hash of non default values
641 print optimize message only if really done.
642 forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
643 prefs should be stored in db or homepage, besides the current session.
645 Revision 1.62 2004/03/17 18:41:05 rurban
646 initial_content and template support for CreatePage
648 Revision 1.61 2004/03/12 20:59:17 rurban
649 important cookie fix by Konstantin Zadorozhny
650 new editpage feature: JS_SEARCHREPLACE
652 Revision 1.60 2004/02/15 21:34:37 rurban
653 PageList enhanced and improved.
654 fixed new WikiAdmin... plugins
655 editpage, Theme with exp. htmlarea framework
656 (htmlarea yet committed, this is really questionable)
657 WikiUser... code with better session handling for prefs
658 enhanced UserPreferences (again)
659 RecentChanges for show_deleted: how should pages be deleted then?
661 Revision 1.59 2003/12/07 20:35:26 carstenklapp
662 Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
663 error: Call to undefined function: gettransformedcontent() in
664 /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
667 Revision 1.58 2003/03/10 18:25:22 dairiki
668 Bug/typo fix. If you use the edit page to un/lock a page, it
669 failed with: Fatal error: Call to a member function on a
670 non-object in editpage.php on line 136
672 Revision 1.57 2003/02/26 03:40:22 dairiki
673 New action=create. Essentially the same as action=edit, except that if the
674 page already exists, it falls back to action=browse.
676 This is for use in the "question mark" links for unknown wiki words
677 to avoid problems and confusion when following links from stale pages.
678 (If the "unknown page" has been created in the interim, the user probably
679 wants to view the page before editing it.)
681 Revision 1.56 2003/02/21 18:07:14 dairiki
682 Minor, nitpicky, currently inconsequential changes.
684 Revision 1.55 2003/02/21 04:10:58 dairiki
685 Fixes for new cached markup.
686 Some minor code cleanups.
688 Revision 1.54 2003/02/16 19:47:16 dairiki
689 Update WikiDB timestamp when editing or deleting pages.
691 Revision 1.53 2003/02/15 23:20:27 dairiki
692 Redirect back to browse current version of page upon save,
693 even when no changes were made.
695 Revision 1.52 2003/01/03 22:22:00 carstenklapp
696 Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
698 Revision 1.51 2003/01/03 02:43:26 carstenklapp
699 New class LoadFileConflictPageEditor, for merging / comparing a loaded
700 pgsrc file with an existing page.
708 // c-hanging-comment-ender-p: nil
709 // indent-tabs-mode: nil