2 rcs_id('$Id: editpage.php,v 1.66 2004-04-29 23:25:12 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;
63 header("Content-Type: text/html; charset=" . $GLOBALS['charset']);
66 function editPage () {
68 $tokens = &$this->tokens;
70 if (! $this->canEdit()) {
71 if ($this->isInitialEdit())
72 return $this->viewSource();
73 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
75 elseif ($this->editaction == 'save') {
76 if ($this->savePage())
77 return true; // Page saved.
81 if ($saveFailed || $this->isConcurrentUpdate())
83 // Get the text of the original page, and the two conflicting edits
84 // The diff3 class takes arrays as input. So retrieve content as
85 // an array, or convert it as necesary.
86 $orig = $this->page->getRevision($this->_currentVersion);
87 // FIXME: what if _currentVersion has be deleted?
88 $orig_content = $orig->getContent();
89 $this_content = explode("\n", $this->_content);
90 $other_content = $this->current->getContent();
91 include_once("lib/diff3.php");
92 $diff = new diff3($orig_content, $this_content, $other_content);
93 $output = $diff->merged_output(_("Your version"), _("Other version"));
94 // Set the content of the textarea to the merged diff
95 // output, and update the version
96 $this->_content = implode ("\n", $output);
97 $this->_currentVersion = $this->current->getVersion();
98 $this->version = $this->_currentVersion;
99 $unresolved = $diff->ConflictingBlocks;
100 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
103 if ($this->editaction == 'preview')
104 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
106 // FIXME: NOT_CURRENT_MESSAGE?
108 $tokens = array_merge($tokens, $this->getFormElements());
110 if (defined('JS_SEARCHREPLACE') and JS_SEARCHREPLACE) {
111 $tokens['JS_SEARCHREPLACE'] = 1;
112 $GLOBALS['Theme']->addMoreHeaders(Javascript("
113 var wart=0, d, f, x='', replacewin, pretxt=new Array(), pretxt_anzahl=0;
114 var fag='<font face=\"arial,helvetica,sans-serif\" size=\"-1\">', fr='<font color=\"#cc0000\">', spn='<span class=\"grey\">';
116 function define_f() {
117 f=document.getElementById('editpage');
118 f.editarea=document.getElementById('edit[content]');
119 if(f.rck.style) f.rck.style.color='#ececec';
123 replacewin=window.open('','','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,height=90,width=450');
124 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>');
125 replacewin.window.document.close();
128 function do_replace() {
129 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;
130 if(ein==''||ein==null) {
131 replacewin.window.document.forms[0].ein.focus();
134 var z_repl=txt.match(ein)? txt.match(ein).length : 0;
135 txt=txt.replace(ein,aus);
136 ein=ein.toString().substring(1,ein.toString().length-2);
137 result(z_repl, 'Substring \"'+ein+'\" found '+z_repl+' times. Replace with \"'+aus+'\"?', txt, 'String \"'+ein+'\" not found.');
138 replacewin.window.focus();
139 replacewin.window.document.forms[0].ein.focus();
141 function result(zahl,frage,txt,alert_txt) {
142 if(wart!=0&&wart.window) {
147 if(window.confirm(frage)==true) {
148 f.editarea.value=txt;
150 if(f.rck.style) f.rck.style.color='#000000';
151 f.rck.value='"._("Undo")."';
153 } else alert(alert_txt);
156 if(pretxt_anzahl==0) return;
157 else if(pretxt_anzahl>0) {
158 f.editarea.value=pretxt[pretxt_anzahl-1];
159 pretxt[pretxt_anzahl]=null;
161 if(pretxt_anzahl==0) {
162 alert('Operation undone.');
163 if(f.rck.style) f.rck.style.color='#ececec';
164 f.rck.value='("._("Undo").")';
165 if(f.rck.blur) f.rck.blur();
170 pretxt[pretxt_anzahl]=f.editarea.value;
172 if(f.rck.style) f.rck.style.color='#000000';
173 f.rck.value='"._("Undo")."';
176 $GLOBALS['Theme']->addMoreAttr('body'," onload='define_f()'");
179 return $this->output('editpage', _("Edit: %s"));
182 function output ($template, $title_fs) {
184 $selected = &$this->selected;
185 $current = &$this->current;
187 if ($selected && $selected->getVersion() != $current->getVersion()) {
189 $pagelink = WikiLink($selected);
193 $pagelink = WikiLink($this->page);
197 $title = new FormattedText ($title_fs, $pagelink);
198 if ($template == 'editpage' and USE_HTMLAREA) {
199 $Theme->addMoreHeaders(Edit_HtmlArea_Head());
200 //$tokens['PAGE_SOURCE'] = Edit_HtmlArea_ConvertBefore($this->_content);
202 $template = Template($template, $this->tokens);
203 GeneratePage($template, $title, $rev);
208 function viewSource () {
209 assert($this->isInitialEdit());
210 assert($this->selected);
212 $this->tokens['PAGE_SOURCE'] = $this->_content;
213 return $this->output('viewsource', _("View Source: %s"));
216 function updateLock() {
217 if ((bool)$this->page->get('locked') == (bool)$this->locked)
218 return false; // Not changed.
220 if (!$this->user->isAdmin()) {
221 // FIXME: some sort of message
222 return false; // not allowed.
225 $this->page->set('locked', (bool)$this->locked);
226 $this->tokens['LOCK_CHANGED_MSG']
227 = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
229 return true; // lock changed.
232 function savePage () {
233 $request = &$this->request;
235 if ($this->isUnchanged()) {
236 // Allow admin lock/unlock even if
237 // no text changes were made.
238 if ($this->updateLock()) {
239 $dbi = $request->getDbh();
242 // Save failed. No changes made.
243 $this->_redirectToBrowsePage();
244 // user will probably not see the rest of this...
245 include_once('lib/display.php');
246 // force browse of current version:
247 $request->setArg('version', false);
248 displayPage($request, 'nochanges');
252 $page = &$this->page;
254 // Include any meta-data from original page version which
255 // has not been explicitly updated.
256 // (Except don't propagate pgsrc_version --- moot for now,
257 // because at present it never gets into the db...)
258 $meta = $this->selected->getMetaData();
259 unset($meta['pgsrc_version']);
260 $meta = array_merge($meta, $this->meta);
263 $this->_content = $this->getContent();
264 $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
265 if (!isa($newrevision, 'wikidb_pagerevision')) {
266 // Save failed. (Concurrent updates).
270 // New contents successfully saved...
273 // Clean out archived versions of this page.
274 include_once('lib/ArchiveCleaner.php');
275 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
276 $cleaner->cleanPageRevisions($page);
278 /* generate notification emails done in WikiDB::save to catch all direct calls
281 $dbi = $request->getDbh();
282 $warnings = $dbi->GenericWarnings();
286 if (empty($warnings) && ! $Theme->getImageURL('signature')) {
287 // Do redirect to browse page if no signature has
288 // been defined. In this case, the user will most
289 // likely not see the rest of the HTML we generate
291 $this->_redirectToBrowsePage();
294 // Force browse of current page version.
295 $request->setArg('version', false);
296 //$request->setArg('action', false);
298 $template = Template('savepage', $this->tokens);
299 $template->replace('CONTENT', $newrevision->getTransformedContent());
300 if (!empty($warnings))
301 $template->replace('WARNINGS', $warnings);
303 $pagelink = WikiLink($page);
305 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
309 function isConcurrentUpdate () {
310 assert($this->current->getVersion() >= $this->_currentVersion);
311 return $this->current->getVersion() != $this->_currentVersion;
314 function canEdit () {
315 return !$this->page->get('locked') || $this->user->isAdmin();
318 function isInitialEdit () {
319 return $this->_initialEdit;
322 function isUnchanged () {
323 $current = &$this->current;
325 if ($this->meta['markup'] != $current->get('markup'))
328 return $this->_content == $current->getPackedContent();
331 function getPreview () {
332 include_once('lib/PageType.php');
333 $this->_content = $this->getContent();
334 return new TransformedText($this->page, $this->_content, $this->meta);
337 // possibly convert HTMLAREA content back to Wiki markup
338 function getContent () {
340 $xml_output = Edit_HtmlArea_ConvertAfter($this->_content);
341 $this->_content = join("",$xml_output->_content);
342 return $this->_content;
344 return $this->_content;
348 function getLockedMessage () {
350 HTML(HTML::h2(_("Page Locked")),
351 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
352 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
353 HTML::p(_("Sorry for the inconvenience.")));
356 function getConflictMessage ($unresolved = false) {
358 xgettext only knows about c/c++ line-continuation strings
359 it does not know about php's dot operator.
360 We want to translate this entire paragraph as one string, of course.
363 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
366 $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.",
367 "<<<<<<< ". _("Your version"),
368 ">>>>>>> ". _("Other version")));
370 $message = HTML::p(_("Please check it through before saving."));
374 /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
375 HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
377 HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
378 HTML::li(_("Save your updated changes.")));
381 HTML(HTML::h2(_("Conflicting Edits!")),
382 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
383 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.")),
388 function getTextArea () {
389 $request = &$this->request;
391 // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
393 $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
395 $html = $this->getPreview();
396 $this->_wikicontent = $this->_content;
397 $this->_content = $html->asXML();
399 $textarea = HTML::textarea(array('class' => 'wikiedit',
400 'name' => 'edit[content]',
401 'id' => 'edit[content]',
402 'rows' => $request->getPref('editHeight'),
403 'cols' => $request->getPref('editWidth'),
404 'readonly' => (bool) $readonly,
405 'wrap' => 'virtual'),
408 return Edit_HtmlArea_Textarea($textarea,$this->_wikicontent,'edit[content]');
413 function getFormElements () {
414 $request = &$this->request;
415 $page = &$this->page;
418 $h = array('action' => 'edit',
419 'pagename' => $page->getName(),
420 'version' => $this->version,
421 'edit[pagetype]' => $this->meta['pagetype'],
422 'edit[current_version]' => $this->_currentVersion);
424 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
427 $el['EDIT_TEXTAREA'] = $this->getTextArea();
430 = HTML::input(array('type' => 'text',
431 'class' => 'wikitext',
432 'name' => 'edit[summary]',
435 'value' => $this->meta['summary']));
437 = HTML::input(array('type' => 'checkbox',
438 'name' => 'edit[minor_edit]',
439 'checked' => (bool) $this->meta['is_minor_edit']));
441 = HTML::input(array('type' => 'checkbox',
442 'name' => 'edit[markup]',
444 'checked' => $this->meta['markup'] < 2.0,
445 'id' => 'useOldMarkup',
446 'onclick' => 'showOldMarkupRules(this.checked)'));
449 = HTML::input(array('type' => 'checkbox',
450 'name' => 'edit[locked]',
451 'disabled' => (bool) !$this->user->isadmin(),
452 'checked' => (bool) $this->locked));
454 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
457 //if (!$this->isConcurrentUpdate() && $this->canEdit())
458 $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
460 $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
465 function _redirectToBrowsePage() {
466 $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
470 function _restoreState () {
471 $request = &$this->request;
473 $posted = $request->getArg('edit');
474 $request->setArg('edit', false);
476 if (!$posted || !$request->isPost()
477 || $request->getArg('action') != 'edit')
480 if (!isset($posted['content']) || !is_string($posted['content']))
482 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
483 rtrim($posted['content']));
484 $this->_content = $this->getContent();
486 $this->_currentVersion = (int) $posted['current_version'];
488 if ($this->_currentVersion < 0)
490 if ($this->_currentVersion > $this->current->getVersion())
491 return false; // FIXME: some kind of warning?
493 $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
494 $meta['markup'] = $is_old_markup ? false : 2.0;
495 $meta['summary'] = trim(substr($posted['summary'], 0, 256));
496 $meta['is_minor_edit'] = !empty($posted['minor_edit']);
497 $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
498 $this->meta = array_merge($this->meta, $meta);
499 $this->locked = !empty($posted['locked']);
501 if (!empty($posted['preview']))
502 $this->editaction = 'preview';
503 elseif (!empty($posted['save']))
504 $this->editaction = 'save';
506 $this->editaction = 'edit';
511 function _initializeState () {
512 $request = &$this->request;
513 $current = &$this->current;
514 $selected = &$this->selected;
515 $user = &$this->user;
518 NoSuchRevision($request, $this->page, $this->version); // noreturn
520 $this->_currentVersion = $current->getVersion();
521 $this->_content = $selected->getPackedContent();
523 $this->meta['summary'] = '';
524 $this->locked = $this->page->get('locked');
526 // If author same as previous author, default minor_edit to on.
527 $age = $this->meta['mtime'] - $current->get('mtime');
528 $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
529 && $current->get('author') == $user->getId()
532 // Default for new pages is new-style markup.
533 if ($selected->hasDefaultContents())
534 $is_new_markup = true;
536 $is_new_markup = $selected->get('markup') >= 2.0;
538 $this->meta['markup'] = $is_new_markup ? 2.0: false;
539 $this->meta['pagetype'] = $selected->get('pagetype');
540 $this->editaction = 'edit';
544 class LoadFileConflictPageEditor
547 function editPage ($saveFailed = true) {
548 $tokens = &$this->tokens;
550 if (!$this->canEdit()) {
551 if ($this->isInitialEdit())
552 return $this->viewSource();
553 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
555 elseif ($this->editaction == 'save') {
556 if ($this->savePage())
557 return true; // Page saved.
561 if ($saveFailed || $this->isConcurrentUpdate())
563 // Get the text of the original page, and the two conflicting edits
564 // The diff class takes arrays as input. So retrieve content as
565 // an array, or convert it as necesary.
566 $orig = $this->page->getRevision($this->_currentVersion);
567 $this_content = explode("\n", $this->_content);
568 $other_content = $this->current->getContent();
569 include_once("lib/diff.php");
570 $diff2 = new Diff($other_content, $this_content);
571 $context_lines = max(4, count($other_content) + 1,
572 count($this_content) + 1);
573 $fmt = new BlockDiffFormatter($context_lines);
575 $this->_content = $fmt->format($diff2);
576 // FIXME: integrate this into class BlockDiffFormatter
577 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
579 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
582 $this->_currentVersion = $this->current->getVersion();
583 $this->version = $this->_currentVersion;
584 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
587 if ($this->editaction == 'preview')
588 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
590 // FIXME: NOT_CURRENT_MESSAGE?
592 $tokens = array_merge($tokens, $this->getFormElements());
594 return $this->output('editpage', _("Merge and Edit: %s"));
595 // FIXME: this doesn't display
598 function output ($template, $title_fs) {
599 $selected = &$this->selected;
600 $current = &$this->current;
602 if ($selected && $selected->getVersion() != $current->getVersion()) {
604 $pagelink = WikiLink($selected);
608 $pagelink = WikiLink($this->page);
611 $title = new FormattedText ($title_fs, $pagelink);
612 $template = Template($template, $this->tokens);
614 //GeneratePage($template, $title, $rev);
618 function getConflictMessage () {
619 $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.",
622 HTML::p(_("Please check it through before saving."))));
628 $Log: not supported by cvs2svn $
629 Revision 1.65 2004/04/18 01:11:52 rurban
630 more numeric pagename fixes.
631 fixed action=upload with merge conflict warnings.
632 charset changed from constant to global (dynamic utf-8 switching)
634 Revision 1.64 2004/04/06 19:48:56 rurban
635 temp workaround for action=edit AddComment form
637 Revision 1.63 2004/03/24 19:39:02 rurban
638 php5 workaround code (plus some interim debugging code in XmlElement)
639 php5 doesn't work yet with the current XmlElement class constructors,
640 WikiUserNew does work better than php4.
641 rewrote WikiUserNew user upgrading to ease php5 update
642 fixed pref handling in WikiUserNew
643 added Email Notification
644 added simple Email verification
645 removed emailVerify userpref subclass: just a email property
646 changed pref binary storage layout: numarray => hash of non default values
647 print optimize message only if really done.
648 forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
649 prefs should be stored in db or homepage, besides the current session.
651 Revision 1.62 2004/03/17 18:41:05 rurban
652 initial_content and template support for CreatePage
654 Revision 1.61 2004/03/12 20:59:17 rurban
655 important cookie fix by Konstantin Zadorozhny
656 new editpage feature: JS_SEARCHREPLACE
658 Revision 1.60 2004/02/15 21:34:37 rurban
659 PageList enhanced and improved.
660 fixed new WikiAdmin... plugins
661 editpage, Theme with exp. htmlarea framework
662 (htmlarea yet committed, this is really questionable)
663 WikiUser... code with better session handling for prefs
664 enhanced UserPreferences (again)
665 RecentChanges for show_deleted: how should pages be deleted then?
667 Revision 1.59 2003/12/07 20:35:26 carstenklapp
668 Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
669 error: Call to undefined function: gettransformedcontent() in
670 /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
673 Revision 1.58 2003/03/10 18:25:22 dairiki
674 Bug/typo fix. If you use the edit page to un/lock a page, it
675 failed with: Fatal error: Call to a member function on a
676 non-object in editpage.php on line 136
678 Revision 1.57 2003/02/26 03:40:22 dairiki
679 New action=create. Essentially the same as action=edit, except that if the
680 page already exists, it falls back to action=browse.
682 This is for use in the "question mark" links for unknown wiki words
683 to avoid problems and confusion when following links from stale pages.
684 (If the "unknown page" has been created in the interim, the user probably
685 wants to view the page before editing it.)
687 Revision 1.56 2003/02/21 18:07:14 dairiki
688 Minor, nitpicky, currently inconsequential changes.
690 Revision 1.55 2003/02/21 04:10:58 dairiki
691 Fixes for new cached markup.
692 Some minor code cleanups.
694 Revision 1.54 2003/02/16 19:47:16 dairiki
695 Update WikiDB timestamp when editing or deleting pages.
697 Revision 1.53 2003/02/15 23:20:27 dairiki
698 Redirect back to browse current version of page upon save,
699 even when no changes were made.
701 Revision 1.52 2003/01/03 22:22:00 carstenklapp
702 Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
704 Revision 1.51 2003/01/03 02:43:26 carstenklapp
705 New class LoadFileConflictPageEditor, for merging / comparing a loaded
706 pgsrc file with an existing page.
714 // c-hanging-comment-ender-p: nil
715 // indent-tabs-mode: nil