2 rcs_id('$Id: editpage.php,v 1.56 2003-02-21 18:07:14 dairiki Exp $');
4 require_once('lib/Template.php');
8 function PageEditor (&$request) {
9 $this->request = &$request;
11 $this->user = $request->getUser();
12 $this->page = $request->getPage();
14 $this->current = $this->page->getCurrentRevision();
16 $this->meta = array('author' => $this->user->getId(),
17 'author_id' => $this->user->getAuthenticatedId(),
20 $this->tokens = array();
22 $version = $request->getArg('version');
23 if ($version !== false) {
24 $this->selected = $this->page->getRevision($version);
25 $this->version = $version;
28 $this->selected = $this->current;
29 $this->version = $this->current->getVersion();
32 if ($this->_restoreState()) {
33 $this->_initialEdit = false;
36 $this->_initializeState();
37 $this->_initialEdit = true;
40 header("Content-Type: text/html; charset=" . CHARSET);
43 function editPage () {
45 $tokens = &$this->tokens;
47 if (! $this->canEdit()) {
48 if ($this->isInitialEdit())
49 return $this->viewSource();
50 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
52 elseif ($this->editaction == 'save') {
53 if ($this->savePage())
54 return true; // Page saved.
58 if ($saveFailed || $this->isConcurrentUpdate())
60 // Get the text of the original page, and the two conflicting edits
61 // The diff3 class takes arrays as input. So retrieve content as
62 // an array, or convert it as necesary.
63 $orig = $this->page->getRevision($this->_currentVersion);
64 // FIXME: what if _currentVersion has be deleted?
65 $orig_content = $orig->getContent();
66 $this_content = explode("\n", $this->_content);
67 $other_content = $this->current->getContent();
68 include_once("lib/diff3.php");
69 $diff = new diff3($orig_content, $this_content, $other_content);
70 $output = $diff->merged_output(_("Your version"), _("Other version"));
71 // Set the content of the textarea to the merged diff
72 // output, and update the version
73 $this->_content = implode ("\n", $output);
74 $this->_currentVersion = $this->current->getVersion();
75 $this->version = $this->_currentVersion;
76 $unresolved = $diff->ConflictingBlocks;
77 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
80 if ($this->editaction == 'preview')
81 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
83 // FIXME: NOT_CURRENT_MESSAGE?
85 $tokens = array_merge($tokens, $this->getFormElements());
87 return $this->output('editpage', _("Edit: %s"));
90 function output ($template, $title_fs) {
91 $selected = &$this->selected;
92 $current = &$this->current;
94 if ($selected && $selected->getVersion() != $current->getVersion()) {
96 $pagelink = WikiLink($selected);
100 $pagelink = WikiLink($this->page);
104 $title = new FormattedText ($title_fs, $pagelink);
105 $template = Template($template, $this->tokens);
107 GeneratePage($template, $title, $rev);
112 function viewSource () {
113 assert($this->isInitialEdit());
114 assert($this->selected);
116 $this->tokens['PAGE_SOURCE'] = $this->_content;
117 return $this->output('viewsource', _("View Source: %s"));
120 function updateLock() {
121 if ((bool)$this->page->get('locked') == (bool)$this->locked)
122 return false; // Not changed.
124 if (!$this->user->isAdmin()) {
125 // FIXME: some sort of message
126 return false; // not allowed.
129 $page->set('locked', (bool)$this->locked);
130 $this->tokens['LOCK_CHANGED_MSG']
131 = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
133 return true; // lock changed.
136 function savePage () {
137 $request = &$this->request;
139 if ($this->isUnchanged()) {
140 // Allow admin lock/unlock even if
141 // no text changes were made.
142 if ($this->updateLock()) {
143 $dbi = $request->getDbh();
146 // Save failed. No changes made.
147 $this->_redirectToBrowsePage();
148 // user will probably not see the rest of this...
149 include_once('lib/display.php');
150 // force browse of current version:
151 $request->setArg('version', false);
152 displayPage($request, 'nochanges');
156 $page = &$this->page;
158 // Include any meta-data from original page version which
159 // has not been explicitly updated.
160 // (Except don't propagate pgsrc_version --- moot for now,
161 // because at present it never gets into the db...)
162 $meta = $this->selected->getMetaData();
163 unset($meta['pgsrc_version']);
164 $meta = array_merge($meta, $this->meta);
167 $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
168 if (!is_object($newrevision)) {
169 // Save failed. (Concurrent updates).
173 // New contents successfully saved...
176 // Clean out archived versions of this page.
177 include_once('lib/ArchiveCleaner.php');
178 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
179 $cleaner->cleanPageRevisions($page);
181 $dbi = $request->getDbh();
182 $warnings = $dbi->GenericWarnings();
186 if (empty($warnings) && ! $Theme->getImageURL('signature')) {
187 // Do redirect to browse page if no signature has
188 // been defined. In this case, the user will most
189 // likely not see the rest of the HTML we generate
191 $this->_redirectToBrowsePage();
194 // Force browse of current page version.
195 $request->setArg('version', false);
197 $template = Template('savepage', $this->tokens);
198 $template->replace('CONTENT', $newrevision->getTransformedContent());
199 if (!empty($warnings))
200 $template->replace('WARNINGS', $warnings);
202 $pagelink = WikiLink($page);
204 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
208 function isConcurrentUpdate () {
209 assert($this->current->getVersion() >= $this->_currentVersion);
210 return $this->current->getVersion() != $this->_currentVersion;
213 function canEdit () {
214 return !$this->page->get('locked') || $this->user->isAdmin();
217 function isInitialEdit () {
218 return $this->_initialEdit;
221 function isUnchanged () {
222 $current = &$this->current;
224 if ($this->meta['markup'] != $current->get('markup'))
227 return $this->_content == $current->getPackedContent();
230 function getPreview () {
231 include_once('lib/PageType.php');
232 return new TransformedText($this->page, $this->_content, $this->meta);
235 function getLockedMessage () {
237 HTML(HTML::h2(_("Page Locked")),
238 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
239 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
240 HTML::p(_("Sorry for the inconvenience.")));
243 function getConflictMessage ($unresolved = false) {
245 xgettext only knows about c/c++ line-continuation strings
246 it does not know about php's dot operator.
247 We want to translate this entire paragraph as one string, of course.
250 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
253 $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.",
254 "<<<<<<< ". _("Your version"),
255 ">>>>>>> ". _("Other version")));
257 $message = HTML::p(_("Please check it through before saving."));
261 /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
262 HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
264 HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
265 HTML::li(_("Save your updated changes.")));
268 HTML(HTML::h2(_("Conflicting Edits!")),
269 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
270 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.")),
275 function getTextArea () {
276 $request = &$this->request;
278 // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
280 $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
282 return HTML::textarea(array('class' => 'wikiedit',
283 'name' => 'edit[content]',
284 'rows' => $request->getPref('editHeight'),
285 'cols' => $request->getPref('editWidth'),
286 'readonly' => (bool) $readonly,
287 'wrap' => 'virtual'),
291 function getFormElements () {
292 $request = &$this->request;
293 $page = &$this->page;
296 $h = array('action' => 'edit',
297 'pagename' => $page->getName(),
298 'version' => $this->version,
299 'edit[pagetype]' => $this->meta['pagetype'],
300 'edit[current_version]' => $this->_currentVersion);
302 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
305 $el['EDIT_TEXTAREA'] = $this->getTextArea();
308 = HTML::input(array('type' => 'text',
309 'class' => 'wikitext',
310 'name' => 'edit[summary]',
313 'value' => $this->meta['summary']));
315 = HTML::input(array('type' => 'checkbox',
316 'name' => 'edit[minor_edit]',
317 'checked' => (bool) $this->meta['is_minor_edit']));
319 = HTML::input(array('type' => 'checkbox',
320 'name' => 'edit[markup]',
322 'checked' => $this->meta['markup'] < 2.0,
323 'id' => 'useOldMarkup',
324 'onclick' => 'showOldMarkupRules(this.checked)'));
327 = HTML::input(array('type' => 'checkbox',
328 'name' => 'edit[locked]',
329 'disabled' => (bool) !$this->user->isadmin(),
330 'checked' => (bool) $this->locked));
332 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
335 //if (!$this->isConcurrentUpdate() && $this->canEdit())
336 $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
338 $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
343 function _redirectToBrowsePage() {
344 $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
348 function _restoreState () {
349 $request = &$this->request;
351 $posted = $request->getArg('edit');
352 $request->setArg('edit', false);
354 if (!$posted || !$request->isPost()
355 || $request->getArg('action') != 'edit')
358 if (!isset($posted['content']) || !is_string($posted['content']))
360 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
361 rtrim($posted['content']));
363 $this->_currentVersion = (int) $posted['current_version'];
365 if ($this->_currentVersion < 0)
367 if ($this->_currentVersion > $this->current->getVersion())
368 return false; // FIXME: some kind of warning?
370 $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
371 $meta['markup'] = $is_old_markup ? false : 2.0;
372 $meta['summary'] = trim(substr($posted['summary'], 0, 256));
373 $meta['is_minor_edit'] = !empty($posted['minor_edit']);
374 $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
375 $this->meta = array_merge($this->meta, $meta);
376 $this->locked = !empty($posted['locked']);
378 if (!empty($posted['preview']))
379 $this->editaction = 'preview';
380 elseif (!empty($posted['save']))
381 $this->editaction = 'save';
383 $this->editaction = 'edit';
388 function _initializeState () {
389 $request = &$this->request;
390 $current = &$this->current;
391 $selected = &$this->selected;
392 $user = &$this->user;
395 NoSuchRevision($request, $this->page, $this->version); // noreturn
397 $this->_currentVersion = $current->getVersion();
398 $this->_content = $selected->getPackedContent();
400 $this->meta['summary'] = '';
401 $this->locked = $this->page->get('locked');
403 // If author same as previous author, default minor_edit to on.
404 $age = $this->meta['mtime'] - $current->get('mtime');
405 $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
406 && $current->get('author') == $user->getId()
409 // Default for new pages is new-style markup.
410 if ($selected->hasDefaultContents())
411 $is_new_markup = true;
413 $is_new_markup = $selected->get('markup') >= 2.0;
415 $this->meta['markup'] = $is_new_markup ? 2.0: false;
416 $this->meta['pagetype'] = $selected->get('pagetype');
417 $this->editaction = 'edit';
421 class LoadFileConflictPageEditor
424 function editPage ($saveFailed = true) {
425 $tokens = &$this->tokens;
427 if (!$this->canEdit()) {
428 if ($this->isInitialEdit())
429 return $this->viewSource();
430 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
432 elseif ($this->editaction == 'save') {
433 if ($this->savePage())
434 return true; // Page saved.
438 if ($saveFailed || $this->isConcurrentUpdate())
440 // Get the text of the original page, and the two conflicting edits
441 // The diff class takes arrays as input. So retrieve content as
442 // an array, or convert it as necesary.
443 $orig = $this->page->getRevision($this->_currentVersion);
444 $this_content = explode("\n", $this->_content);
445 $other_content = $this->current->getContent();
446 include_once("lib/diff.php");
447 $diff2 = new Diff($other_content, $this_content);
448 $context_lines = max(4, count($other_content) + 1,
449 count($this_content) + 1);
450 $fmt = new BlockDiffFormatter($context_lines);
452 $this->_content = $fmt->format($diff2);
453 // FIXME: integrate this into class BlockDiffFormatter
454 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
456 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
459 $this->_currentVersion = $this->current->getVersion();
460 $this->version = $this->_currentVersion;
461 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
464 if ($this->editaction == 'preview')
465 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
467 // FIXME: NOT_CURRENT_MESSAGE?
469 $tokens = array_merge($tokens, $this->getFormElements());
471 return $this->output('editpage', _("Merge and Edit: %s"));
472 // FIXME: this doesn't display
475 function output ($template, $title_fs) {
476 $selected = &$this->selected;
477 $current = &$this->current;
479 if ($selected && $selected->getVersion() != $current->getVersion()) {
481 $pagelink = WikiLink($selected);
485 $pagelink = WikiLink($this->page);
488 $title = new FormattedText ($title_fs, $pagelink);
489 $template = Template($template, $this->tokens);
491 //GeneratePage($template, $title, $rev);
495 function getConflictMessage () {
496 $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.",
499 HTML::p(_("Please check it through before saving."))));
505 $Log: not supported by cvs2svn $
506 Revision 1.55 2003/02/21 04:10:58 dairiki
507 Fixes for new cached markup.
508 Some minor code cleanups.
510 Revision 1.54 2003/02/16 19:47:16 dairiki
511 Update WikiDB timestamp when editing or deleting pages.
513 Revision 1.53 2003/02/15 23:20:27 dairiki
514 Redirect back to browse current version of page upon save,
515 even when no changes were made.
517 Revision 1.52 2003/01/03 22:22:00 carstenklapp
518 Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
520 Revision 1.51 2003/01/03 02:43:26 carstenklapp
521 New class LoadFileConflictPageEditor, for merging / comparing a loaded
522 pgsrc file with an existing page.
530 // c-hanging-comment-ender-p: nil
531 // indent-tabs-mode: nil