2 rcs_id('$Id: editpage.php,v 1.58 2003-03-10 18:25:22 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 // HACKish short circuit to browse on action=create
17 if ($request->getArg('action') == 'create') {
18 if (! $this->current->hasDefaultContents())
19 $request->redirect(WikiURL($this->page->getName())); // noreturn
23 $this->meta = array('author' => $this->user->getId(),
24 'author_id' => $this->user->getAuthenticatedId(),
27 $this->tokens = array();
29 $version = $request->getArg('version');
30 if ($version !== false) {
31 $this->selected = $this->page->getRevision($version);
32 $this->version = $version;
35 $this->selected = $this->current;
36 $this->version = $this->current->getVersion();
39 if ($this->_restoreState()) {
40 $this->_initialEdit = false;
43 $this->_initializeState();
44 $this->_initialEdit = true;
47 header("Content-Type: text/html; charset=" . CHARSET);
50 function editPage () {
52 $tokens = &$this->tokens;
54 if (! $this->canEdit()) {
55 if ($this->isInitialEdit())
56 return $this->viewSource();
57 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
59 elseif ($this->editaction == 'save') {
60 if ($this->savePage())
61 return true; // Page saved.
65 if ($saveFailed || $this->isConcurrentUpdate())
67 // Get the text of the original page, and the two conflicting edits
68 // The diff3 class takes arrays as input. So retrieve content as
69 // an array, or convert it as necesary.
70 $orig = $this->page->getRevision($this->_currentVersion);
71 // FIXME: what if _currentVersion has be deleted?
72 $orig_content = $orig->getContent();
73 $this_content = explode("\n", $this->_content);
74 $other_content = $this->current->getContent();
75 include_once("lib/diff3.php");
76 $diff = new diff3($orig_content, $this_content, $other_content);
77 $output = $diff->merged_output(_("Your version"), _("Other version"));
78 // Set the content of the textarea to the merged diff
79 // output, and update the version
80 $this->_content = implode ("\n", $output);
81 $this->_currentVersion = $this->current->getVersion();
82 $this->version = $this->_currentVersion;
83 $unresolved = $diff->ConflictingBlocks;
84 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
87 if ($this->editaction == 'preview')
88 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
90 // FIXME: NOT_CURRENT_MESSAGE?
92 $tokens = array_merge($tokens, $this->getFormElements());
94 return $this->output('editpage', _("Edit: %s"));
97 function output ($template, $title_fs) {
98 $selected = &$this->selected;
99 $current = &$this->current;
101 if ($selected && $selected->getVersion() != $current->getVersion()) {
103 $pagelink = WikiLink($selected);
107 $pagelink = WikiLink($this->page);
111 $title = new FormattedText ($title_fs, $pagelink);
112 $template = Template($template, $this->tokens);
114 GeneratePage($template, $title, $rev);
119 function viewSource () {
120 assert($this->isInitialEdit());
121 assert($this->selected);
123 $this->tokens['PAGE_SOURCE'] = $this->_content;
124 return $this->output('viewsource', _("View Source: %s"));
127 function updateLock() {
128 if ((bool)$this->page->get('locked') == (bool)$this->locked)
129 return false; // Not changed.
131 if (!$this->user->isAdmin()) {
132 // FIXME: some sort of message
133 return false; // not allowed.
136 $this->page->set('locked', (bool)$this->locked);
137 $this->tokens['LOCK_CHANGED_MSG']
138 = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
140 return true; // lock changed.
143 function savePage () {
144 $request = &$this->request;
146 if ($this->isUnchanged()) {
147 // Allow admin lock/unlock even if
148 // no text changes were made.
149 if ($this->updateLock()) {
150 $dbi = $request->getDbh();
153 // Save failed. No changes made.
154 $this->_redirectToBrowsePage();
155 // user will probably not see the rest of this...
156 include_once('lib/display.php');
157 // force browse of current version:
158 $request->setArg('version', false);
159 displayPage($request, 'nochanges');
163 $page = &$this->page;
165 // Include any meta-data from original page version which
166 // has not been explicitly updated.
167 // (Except don't propagate pgsrc_version --- moot for now,
168 // because at present it never gets into the db...)
169 $meta = $this->selected->getMetaData();
170 unset($meta['pgsrc_version']);
171 $meta = array_merge($meta, $this->meta);
174 $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
175 if (!is_object($newrevision)) {
176 // Save failed. (Concurrent updates).
180 // New contents successfully saved...
183 // Clean out archived versions of this page.
184 include_once('lib/ArchiveCleaner.php');
185 $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
186 $cleaner->cleanPageRevisions($page);
188 $dbi = $request->getDbh();
189 $warnings = $dbi->GenericWarnings();
193 if (empty($warnings) && ! $Theme->getImageURL('signature')) {
194 // Do redirect to browse page if no signature has
195 // been defined. In this case, the user will most
196 // likely not see the rest of the HTML we generate
198 $this->_redirectToBrowsePage();
201 // Force browse of current page version.
202 $request->setArg('version', false);
204 $template = Template('savepage', $this->tokens);
205 $template->replace('CONTENT', $newrevision->getTransformedContent());
206 if (!empty($warnings))
207 $template->replace('WARNINGS', $warnings);
209 $pagelink = WikiLink($page);
211 GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
215 function isConcurrentUpdate () {
216 assert($this->current->getVersion() >= $this->_currentVersion);
217 return $this->current->getVersion() != $this->_currentVersion;
220 function canEdit () {
221 return !$this->page->get('locked') || $this->user->isAdmin();
224 function isInitialEdit () {
225 return $this->_initialEdit;
228 function isUnchanged () {
229 $current = &$this->current;
231 if ($this->meta['markup'] != $current->get('markup'))
234 return $this->_content == $current->getPackedContent();
237 function getPreview () {
238 include_once('lib/PageType.php');
239 return new TransformedText($this->page, $this->_content, $this->meta);
242 function getLockedMessage () {
244 HTML(HTML::h2(_("Page Locked")),
245 HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
246 HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
247 HTML::p(_("Sorry for the inconvenience.")));
250 function getConflictMessage ($unresolved = false) {
252 xgettext only knows about c/c++ line-continuation strings
253 it does not know about php's dot operator.
254 We want to translate this entire paragraph as one string, of course.
257 //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
260 $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.",
261 "<<<<<<< ". _("Your version"),
262 ">>>>>>> ". _("Other version")));
264 $message = HTML::p(_("Please check it through before saving."));
268 /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
269 HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
271 HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
272 HTML::li(_("Save your updated changes.")));
275 HTML(HTML::h2(_("Conflicting Edits!")),
276 HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
277 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.")),
282 function getTextArea () {
283 $request = &$this->request;
285 // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
287 $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
289 return HTML::textarea(array('class' => 'wikiedit',
290 'name' => 'edit[content]',
291 'rows' => $request->getPref('editHeight'),
292 'cols' => $request->getPref('editWidth'),
293 'readonly' => (bool) $readonly,
294 'wrap' => 'virtual'),
298 function getFormElements () {
299 $request = &$this->request;
300 $page = &$this->page;
303 $h = array('action' => 'edit',
304 'pagename' => $page->getName(),
305 'version' => $this->version,
306 'edit[pagetype]' => $this->meta['pagetype'],
307 'edit[current_version]' => $this->_currentVersion);
309 $el['HIDDEN_INPUTS'] = HiddenInputs($h);
312 $el['EDIT_TEXTAREA'] = $this->getTextArea();
315 = HTML::input(array('type' => 'text',
316 'class' => 'wikitext',
317 'name' => 'edit[summary]',
320 'value' => $this->meta['summary']));
322 = HTML::input(array('type' => 'checkbox',
323 'name' => 'edit[minor_edit]',
324 'checked' => (bool) $this->meta['is_minor_edit']));
326 = HTML::input(array('type' => 'checkbox',
327 'name' => 'edit[markup]',
329 'checked' => $this->meta['markup'] < 2.0,
330 'id' => 'useOldMarkup',
331 'onclick' => 'showOldMarkupRules(this.checked)'));
334 = HTML::input(array('type' => 'checkbox',
335 'name' => 'edit[locked]',
336 'disabled' => (bool) !$this->user->isadmin(),
337 'checked' => (bool) $this->locked));
339 $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
342 //if (!$this->isConcurrentUpdate() && $this->canEdit())
343 $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
345 $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
350 function _redirectToBrowsePage() {
351 $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
355 function _restoreState () {
356 $request = &$this->request;
358 $posted = $request->getArg('edit');
359 $request->setArg('edit', false);
361 if (!$posted || !$request->isPost()
362 || $request->getArg('action') != 'edit')
365 if (!isset($posted['content']) || !is_string($posted['content']))
367 $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
368 rtrim($posted['content']));
370 $this->_currentVersion = (int) $posted['current_version'];
372 if ($this->_currentVersion < 0)
374 if ($this->_currentVersion > $this->current->getVersion())
375 return false; // FIXME: some kind of warning?
377 $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
378 $meta['markup'] = $is_old_markup ? false : 2.0;
379 $meta['summary'] = trim(substr($posted['summary'], 0, 256));
380 $meta['is_minor_edit'] = !empty($posted['minor_edit']);
381 $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
382 $this->meta = array_merge($this->meta, $meta);
383 $this->locked = !empty($posted['locked']);
385 if (!empty($posted['preview']))
386 $this->editaction = 'preview';
387 elseif (!empty($posted['save']))
388 $this->editaction = 'save';
390 $this->editaction = 'edit';
395 function _initializeState () {
396 $request = &$this->request;
397 $current = &$this->current;
398 $selected = &$this->selected;
399 $user = &$this->user;
402 NoSuchRevision($request, $this->page, $this->version); // noreturn
404 $this->_currentVersion = $current->getVersion();
405 $this->_content = $selected->getPackedContent();
407 $this->meta['summary'] = '';
408 $this->locked = $this->page->get('locked');
410 // If author same as previous author, default minor_edit to on.
411 $age = $this->meta['mtime'] - $current->get('mtime');
412 $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
413 && $current->get('author') == $user->getId()
416 // Default for new pages is new-style markup.
417 if ($selected->hasDefaultContents())
418 $is_new_markup = true;
420 $is_new_markup = $selected->get('markup') >= 2.0;
422 $this->meta['markup'] = $is_new_markup ? 2.0: false;
423 $this->meta['pagetype'] = $selected->get('pagetype');
424 $this->editaction = 'edit';
428 class LoadFileConflictPageEditor
431 function editPage ($saveFailed = true) {
432 $tokens = &$this->tokens;
434 if (!$this->canEdit()) {
435 if ($this->isInitialEdit())
436 return $this->viewSource();
437 $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
439 elseif ($this->editaction == 'save') {
440 if ($this->savePage())
441 return true; // Page saved.
445 if ($saveFailed || $this->isConcurrentUpdate())
447 // Get the text of the original page, and the two conflicting edits
448 // The diff class takes arrays as input. So retrieve content as
449 // an array, or convert it as necesary.
450 $orig = $this->page->getRevision($this->_currentVersion);
451 $this_content = explode("\n", $this->_content);
452 $other_content = $this->current->getContent();
453 include_once("lib/diff.php");
454 $diff2 = new Diff($other_content, $this_content);
455 $context_lines = max(4, count($other_content) + 1,
456 count($this_content) + 1);
457 $fmt = new BlockDiffFormatter($context_lines);
459 $this->_content = $fmt->format($diff2);
460 // FIXME: integrate this into class BlockDiffFormatter
461 $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
463 $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
466 $this->_currentVersion = $this->current->getVersion();
467 $this->version = $this->_currentVersion;
468 $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
471 if ($this->editaction == 'preview')
472 $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
474 // FIXME: NOT_CURRENT_MESSAGE?
476 $tokens = array_merge($tokens, $this->getFormElements());
478 return $this->output('editpage', _("Merge and Edit: %s"));
479 // FIXME: this doesn't display
482 function output ($template, $title_fs) {
483 $selected = &$this->selected;
484 $current = &$this->current;
486 if ($selected && $selected->getVersion() != $current->getVersion()) {
488 $pagelink = WikiLink($selected);
492 $pagelink = WikiLink($this->page);
495 $title = new FormattedText ($title_fs, $pagelink);
496 $template = Template($template, $this->tokens);
498 //GeneratePage($template, $title, $rev);
502 function getConflictMessage () {
503 $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.",
506 HTML::p(_("Please check it through before saving."))));
512 $Log: not supported by cvs2svn $
513 Revision 1.57 2003/02/26 03:40:22 dairiki
514 New action=create. Essentially the same as action=edit, except that if the
515 page already exists, it falls back to action=browse.
517 This is for use in the "question mark" links for unknown wiki words
518 to avoid problems and confusion when following links from stale pages.
519 (If the "unknown page" has been created in the interim, the user probably
520 wants to view the page before editing it.)
522 Revision 1.56 2003/02/21 18:07:14 dairiki
523 Minor, nitpicky, currently inconsequential changes.
525 Revision 1.55 2003/02/21 04:10:58 dairiki
526 Fixes for new cached markup.
527 Some minor code cleanups.
529 Revision 1.54 2003/02/16 19:47:16 dairiki
530 Update WikiDB timestamp when editing or deleting pages.
532 Revision 1.53 2003/02/15 23:20:27 dairiki
533 Redirect back to browse current version of page upon save,
534 even when no changes were made.
536 Revision 1.52 2003/01/03 22:22:00 carstenklapp
537 Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
539 Revision 1.51 2003/01/03 02:43:26 carstenklapp
540 New class LoadFileConflictPageEditor, for merging / comparing a loaded
541 pgsrc file with an existing page.
549 // c-hanging-comment-ender-p: nil
550 // indent-tabs-mode: nil