2 rcs_id('$Id: editpage.php,v 1.61 2004-03-12 20:59:17 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;
53 header("Content-Type: text/html; charset=" . CHARSET);
56 function editPage () {
58 $tokens = &$this->tokens;
60 if (! $this->canEdit()) {
61 if ($this->isInitialEdit())
62 return $this->viewSource();
63 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
65 elseif ($this->editaction == 'save') {
66 if ($this->savePage())
67 return true; // Page saved.
71 if ($saveFailed || $this->isConcurrentUpdate())
73 // Get the text of the original page, and the two conflicting edits
74 // The diff3 class takes arrays as input. So retrieve content as
75 // an array, or convert it as necesary.
76 $orig = $this->page->getRevision($this->_currentVersion);
77 // FIXME: what if _currentVersion has be deleted?
78 $orig_content = $orig->getContent();
79 $this_content = explode("\n", $this->_content);
80 $other_content = $this->current->getContent();
81 include_once("lib/diff3.php");
82 $diff = new diff3($orig_content, $this_content, $other_content);
83 $output = $diff->merged_output(_("Your version"), _("Other version"));
84 // Set the content of the textarea to the merged diff
85 // output, and update the version
86 $this->_content = implode ("\n", $output);
87 $this->_currentVersion = $this->current->getVersion();
88 $this->version = $this->_currentVersion;
89 $unresolved = $diff->ConflictingBlocks;
90 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
93 if ($this->editaction == 'preview')
94 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
96 // FIXME: NOT_CURRENT_MESSAGE?
98 $tokens = array_merge($tokens, $this->getFormElements());
100 if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
101 $tokens['JS_SEARCHREPLACE'] = 1;
102 $GLOBALS['Theme']->addMoreHeaders(Javascript("
103 var wart=0, d, f, x='', replacewin, pretxt=new Array(), pretxt_anzahl=0;
104 var fag='<font face=\"arial,helvetica,sans-serif\" size=\"-1\">', fr='<font color=\"#cc0000\">', spn='<span class=\"grey\">';
106 function define_f() {
107 f=document.getElementById('editpage');
108 f.editarea=document.getElementById('edit[content]');
109 if(f.rck.style) f.rck.style.color='#ececec';
113 replacewin=window.open('','','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,height=90,width=450');
114 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>');
115 replacewin.window.document.close();
118 function do_replace() {
119 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;
120 if(ein==''||ein==null) {
121 replacewin.window.document.forms[0].ein.focus();
124 var z_repl=txt.match(ein)? txt.match(ein).length : 0;
125 txt=txt.replace(ein,aus);
126 ein=ein.toString().substring(1,ein.toString().length-2);
127 result(z_repl, 'Substring \"'+ein+'\" found '+z_repl+' times. Replace with \"'+aus+'\"?', txt, 'String \"'+ein+'\" not found.');
128 replacewin.window.focus();
129 replacewin.window.document.forms[0].ein.focus();
131 function result(zahl,frage,txt,alert_txt) {
132 if(wart!=0&&wart.window) {
137 if(window.confirm(frage)==true) {
138 f.editarea.value=txt;
140 if(f.rck.style) f.rck.style.color='#000000';
141 f.rck.value='"._("Undo")."';
143 } else alert(alert_txt);
146 if(pretxt_anzahl==0) return;
147 else if(pretxt_anzahl>0) {
148 f.editarea.value=pretxt[pretxt_anzahl-1];
149 pretxt[pretxt_anzahl]=null;
151 if(pretxt_anzahl==0) {
152 alert('Operation undone.');
153 if(f.rck.style) f.rck.style.color='#ececec';
154 f.rck.value='("._("Undo").")';
155 if(f.rck.blur) f.rck.blur();
160 pretxt[pretxt_anzahl]=f.editarea.value;
162 if(f.rck.style) f.rck.style.color='#000000';
163 f.rck.value='"._("Undo")."';
166 $GLOBALS['Theme']->addMoreAttr('body'," onload='define_f()'");
169 return $this->output('editpage', _("Edit: %s"));
172 function output ($template, $title_fs) {
174 $selected = &$this->selected;
175 $current = &$this->current;
177 if ($selected && $selected->getVersion() != $current->getVersion()) {
179 $pagelink = WikiLink($selected);
183 $pagelink = WikiLink($this->page);
187 $title = new FormattedText ($title_fs, $pagelink);
188 if ($template == 'editpage' and USE_HTMLAREA) {
189 $Theme->addMoreHeaders(Edit_HtmlArea_Head());
190 //$tokens['PAGE_SOURCE'] = Edit_HtmlArea_ConvertBefore($this->_content);
192 $template = Template($template, $this->tokens);
193 GeneratePage($template, $title, $rev);
198 function viewSource () {
199 assert($this->isInitialEdit());
200 assert($this->selected);
202 $this->tokens['PAGE_SOURCE'] = $this->_content;
203 return $this->output('viewsource', _("View Source: %s"));
206 function updateLock() {
207 if ((bool)$this->page->get('locked') == (bool)$this->locked)
208 return false; // Not changed.
210 if (!$this->user->isAdmin()) {
211 // FIXME: some sort of message
212 return false; // not allowed.
215 $this->page->set('locked', (bool)$this->locked);
216 $this->tokens['LOCK_CHANGED_MSG']
217 = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
219 return true; // lock changed.
222 function savePage () {
223 $request = &$this->request;
225 if ($this->isUnchanged()) {
226 // Allow admin lock/unlock even if
227 // no text changes were made.
228 if ($this->updateLock()) {
229 $dbi = $request->getDbh();
232 // Save failed. No changes made.
233 $this->_redirectToBrowsePage();
234 // user will probably not see the rest of this...
235 include_once('lib/display.php');
236 // force browse of current version:
237 $request->setArg('version', false);
238 displayPage($request, 'nochanges');
242 $page = &$this->page;
244 // Include any meta-data from original page version which
245 // has not been explicitly updated.
246 // (Except don't propagate pgsrc_version --- moot for now,
247 // because at present it never gets into the db...)
248 $meta = $this->selected->getMetaData();
249 unset($meta['pgsrc_version']);
250 $meta = array_merge($meta, $this->meta);
253 $this->_content = $this->getContent();
254 $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
255 if (!isa($newrevision, 'wikidb_pagerevision')) {
256 // Save failed. (Concurrent updates).
260 // New contents successfully saved...
263 // Clean out archived versions of this page.
264 include_once('lib/ArchiveCleaner.php');
265 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
266 $cleaner->cleanPageRevisions($page);
268 $dbi = $request->getDbh();
269 $warnings = $dbi->GenericWarnings();
273 if (empty($warnings) && ! $Theme->getImageURL('signature')) {
274 // Do redirect to browse page if no signature has
275 // been defined. In this case, the user will most
276 // likely not see the rest of the HTML we generate
278 $this->_redirectToBrowsePage();
281 // Force browse of current page version.
282 $request->setArg('version', false);
284 $template = Template('savepage', $this->tokens);
285 $template->replace('CONTENT', $newrevision->getTransformedContent());
286 if (!empty($warnings))
287 $template->replace('WARNINGS', $warnings);
289 $pagelink = WikiLink($page);
291 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
295 function isConcurrentUpdate () {
296 assert($this->current->getVersion() >= $this->_currentVersion);
297 return $this->current->getVersion() != $this->_currentVersion;
300 function canEdit () {
301 return !$this->page->get('locked') || $this->user->isAdmin();
304 function isInitialEdit () {
305 return $this->_initialEdit;
308 function isUnchanged () {
309 $current = &$this->current;
311 if ($this->meta['markup'] != $current->get('markup'))
314 return $this->_content == $current->getPackedContent();
317 function getPreview () {
318 include_once('lib/PageType.php');
319 $this->_content = $this->getContent();
320 return new TransformedText($this->page, $this->_content, $this->meta);
323 // possibly convert HTMLAREA content back to Wiki markup
324 function getContent () {
326 $xml_output = Edit_HtmlArea_ConvertAfter($this->_content);
327 $this->_content = join("",$xml_output->_content);
328 return $this->_content;
330 return $this->_content;
334 function getLockedMessage () {
336 HTML(HTML::h2(_("Page Locked")),
337 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
338 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
339 HTML::p(_("Sorry for the inconvenience.")));
342 function getConflictMessage ($unresolved = false) {
344 xgettext only knows about c/c++ line-continuation strings
345 it does not know about php's dot operator.
346 We want to translate this entire paragraph as one string, of course.
349 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
352 $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.",
353 "<<<<<<< ". _("Your version"),
354 ">>>>>>> ". _("Other version")));
356 $message = HTML::p(_("Please check it through before saving."));
360 /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
361 HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
363 HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
364 HTML::li(_("Save your updated changes.")));
367 HTML(HTML::h2(_("Conflicting Edits!")),
368 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
369 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.")),
374 function getTextArea () {
375 $request = &$this->request;
377 // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
379 $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
381 $html = $this->getPreview();
382 $this->_wikicontent = $this->_content;
383 $this->_content = $html->asXML();
385 $textarea = HTML::textarea(array('class' => 'wikiedit',
386 'name' => 'edit[content]',
387 'id' => 'edit[content]',
388 'rows' => $request->getPref('editHeight'),
389 'cols' => $request->getPref('editWidth'),
390 'readonly' => (bool) $readonly,
391 'wrap' => 'virtual'),
394 return Edit_HtmlArea_Textarea($textarea,$this->_wikicontent,'edit[content]');
399 function getFormElements () {
400 $request = &$this->request;
401 $page = &$this->page;
404 $h = array('action' => 'edit',
405 'pagename' => $page->getName(),
406 'version' => $this->version,
407 'edit[pagetype]' => $this->meta['pagetype'],
408 'edit[current_version]' => $this->_currentVersion);
410 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
413 $el['EDIT_TEXTAREA'] = $this->getTextArea();
416 = HTML::input(array('type' => 'text',
417 'class' => 'wikitext',
418 'name' => 'edit[summary]',
421 'value' => $this->meta['summary']));
423 = HTML::input(array('type' => 'checkbox',
424 'name' => 'edit[minor_edit]',
425 'checked' => (bool) $this->meta['is_minor_edit']));
427 = HTML::input(array('type' => 'checkbox',
428 'name' => 'edit[markup]',
430 'checked' => $this->meta['markup'] < 2.0,
431 'id' => 'useOldMarkup',
432 'onclick' => 'showOldMarkupRules(this.checked)'));
435 = HTML::input(array('type' => 'checkbox',
436 'name' => 'edit[locked]',
437 'disabled' => (bool) !$this->user->isadmin(),
438 'checked' => (bool) $this->locked));
440 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
443 //if (!$this->isConcurrentUpdate() && $this->canEdit())
444 $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
446 $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
451 function _redirectToBrowsePage() {
452 $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
456 function _restoreState () {
457 $request = &$this->request;
459 $posted = $request->getArg('edit');
460 $request->setArg('edit', false);
462 if (!$posted || !$request->isPost()
463 || $request->getArg('action') != 'edit')
466 if (!isset($posted['content']) || !is_string($posted['content']))
468 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
469 rtrim($posted['content']));
470 $this->_content = $this->getContent();
472 $this->_currentVersion = (int) $posted['current_version'];
474 if ($this->_currentVersion < 0)
476 if ($this->_currentVersion > $this->current->getVersion())
477 return false; // FIXME: some kind of warning?
479 $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
480 $meta['markup'] = $is_old_markup ? false : 2.0;
481 $meta['summary'] = trim(substr($posted['summary'], 0, 256));
482 $meta['is_minor_edit'] = !empty($posted['minor_edit']);
483 $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
484 $this->meta = array_merge($this->meta, $meta);
485 $this->locked = !empty($posted['locked']);
487 if (!empty($posted['preview']))
488 $this->editaction = 'preview';
489 elseif (!empty($posted['save']))
490 $this->editaction = 'save';
492 $this->editaction = 'edit';
497 function _initializeState () {
498 $request = &$this->request;
499 $current = &$this->current;
500 $selected = &$this->selected;
501 $user = &$this->user;
504 NoSuchRevision($request, $this->page, $this->version); // noreturn
506 $this->_currentVersion = $current->getVersion();
507 $this->_content = $selected->getPackedContent();
509 $this->meta['summary'] = '';
510 $this->locked = $this->page->get('locked');
512 // If author same as previous author, default minor_edit to on.
513 $age = $this->meta['mtime'] - $current->get('mtime');
514 $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
515 && $current->get('author') == $user->getId()
518 // Default for new pages is new-style markup.
519 if ($selected->hasDefaultContents())
520 $is_new_markup = true;
522 $is_new_markup = $selected->get('markup') >= 2.0;
524 $this->meta['markup'] = $is_new_markup ? 2.0: false;
525 $this->meta['pagetype'] = $selected->get('pagetype');
526 $this->editaction = 'edit';
530 class LoadFileConflictPageEditor
533 function editPage ($saveFailed = true) {
534 $tokens = &$this->tokens;
536 if (!$this->canEdit()) {
537 if ($this->isInitialEdit())
538 return $this->viewSource();
539 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
541 elseif ($this->editaction == 'save') {
542 if ($this->savePage())
543 return true; // Page saved.
547 if ($saveFailed || $this->isConcurrentUpdate())
549 // Get the text of the original page, and the two conflicting edits
550 // The diff class takes arrays as input. So retrieve content as
551 // an array, or convert it as necesary.
552 $orig = $this->page->getRevision($this->_currentVersion);
553 $this_content = explode("\n", $this->_content);
554 $other_content = $this->current->getContent();
555 include_once("lib/diff.php");
556 $diff2 = new Diff($other_content, $this_content);
557 $context_lines = max(4, count($other_content) + 1,
558 count($this_content) + 1);
559 $fmt = new BlockDiffFormatter($context_lines);
561 $this->_content = $fmt->format($diff2);
562 // FIXME: integrate this into class BlockDiffFormatter
563 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
565 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
568 $this->_currentVersion = $this->current->getVersion();
569 $this->version = $this->_currentVersion;
570 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
573 if ($this->editaction == 'preview')
574 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
576 // FIXME: NOT_CURRENT_MESSAGE?
578 $tokens = array_merge($tokens, $this->getFormElements());
580 return $this->output('editpage', _("Merge and Edit: %s"));
581 // FIXME: this doesn't display
584 function output ($template, $title_fs) {
585 $selected = &$this->selected;
586 $current = &$this->current;
588 if ($selected && $selected->getVersion() != $current->getVersion()) {
590 $pagelink = WikiLink($selected);
594 $pagelink = WikiLink($this->page);
597 $title = new FormattedText ($title_fs, $pagelink);
598 $template = Template($template, $this->tokens);
600 //GeneratePage($template, $title, $rev);
604 function getConflictMessage () {
605 $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.",
608 HTML::p(_("Please check it through before saving."))));
614 $Log: not supported by cvs2svn $
615 Revision 1.60 2004/02/15 21:34:37 rurban
616 PageList enhanced and improved.
617 fixed new WikiAdmin... plugins
618 editpage, Theme with exp. htmlarea framework
619 (htmlarea yet committed, this is really questionable)
620 WikiUser... code with better session handling for prefs
621 enhanced UserPreferences (again)
622 RecentChanges for show_deleted: how should pages be deleted then?
624 Revision 1.59 2003/12/07 20:35:26 carstenklapp
625 Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
626 error: Call to undefined function: gettransformedcontent() in
627 /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
630 Revision 1.58 2003/03/10 18:25:22 dairiki
631 Bug/typo fix. If you use the edit page to un/lock a page, it
632 failed with: Fatal error: Call to a member function on a
633 non-object in editpage.php on line 136
635 Revision 1.57 2003/02/26 03:40:22 dairiki
636 New action=create. Essentially the same as action=edit, except that if the
637 page already exists, it falls back to action=browse.
639 This is for use in the "question mark" links for unknown wiki words
640 to avoid problems and confusion when following links from stale pages.
641 (If the "unknown page" has been created in the interim, the user probably
642 wants to view the page before editing it.)
644 Revision 1.56 2003/02/21 18:07:14 dairiki
645 Minor, nitpicky, currently inconsequential changes.
647 Revision 1.55 2003/02/21 04:10:58 dairiki
648 Fixes for new cached markup.
649 Some minor code cleanups.
651 Revision 1.54 2003/02/16 19:47:16 dairiki
652 Update WikiDB timestamp when editing or deleting pages.
654 Revision 1.53 2003/02/15 23:20:27 dairiki
655 Redirect back to browse current version of page upon save,
656 even when no changes were made.
658 Revision 1.52 2003/01/03 22:22:00 carstenklapp
659 Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
661 Revision 1.51 2003/01/03 02:43:26 carstenklapp
662 New class LoadFileConflictPageEditor, for merging / comparing a loaded
663 pgsrc file with an existing page.
671 // c-hanging-comment-ender-p: nil
672 // indent-tabs-mode: nil