]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Redirect back to browse current version of page upon save,
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.53 2003-02-15 23:20:27 dairiki Exp $');
3
4 require_once('lib/Template.php');
5
6 class PageEditor
7 {
8     function PageEditor (&$request) {
9         $this->request = &$request;
10
11         $this->user = $request->getUser();
12         $this->page = $request->getPage();
13
14         $this->current = $this->page->getCurrentRevision();
15
16         $this->meta = array('author' => $this->user->getId(),
17                             'locked' => $this->page->get('locked'),
18                             'author_id' => $this->user->getAuthenticatedId());
19
20         $version = $request->getArg('version');
21         if ($version !== false) {
22             $this->selected = $this->page->getRevision($version);
23             $this->version = $version;
24         }
25         else {
26             $this->selected = $this->current;
27             $this->version = $this->current->getVersion();
28         }
29
30         if ($this->_restoreState()) {
31             $this->_initialEdit = false;
32         }
33         else {
34             $this->_initializeState();
35             $this->_initialEdit = true;
36         }
37
38         header("Content-Type: text/html; charset=" . CHARSET);
39     }
40
41     function editPage () {
42         $saveFailed = false;
43         $tokens = array();
44
45         if ($this->canEdit()) {
46             if ($this->isInitialEdit())
47                 return $this->viewSource();
48             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
49         }
50         elseif ($this->editaction == 'save') {
51             if ($this->savePage())
52                 return true;    // Page saved.
53             $saveFailed = true;
54         }
55
56         if ($saveFailed || $this->isConcurrentUpdate())
57         {
58             // Get the text of the original page, and the two conflicting edits
59             // The diff3 class takes arrays as input.  So retrieve content as
60             // an array, or convert it as necesary.
61             $orig = $this->page->getRevision($this->_currentVersion);
62             $orig_content = $orig->getContent();
63             $this_content = explode("\n", $this->_content);
64             $other_content = $this->current->getContent();
65             include_once("lib/diff3.php");
66             $diff = new diff3($orig_content, $this_content, $other_content);
67             $output = $diff->merged_output(_("Your version"), _("Other version"));
68             // Set the content of the textarea to the merged diff
69             // output, and update the version
70             $this->_content = implode ("\n", $output);
71             $this->_currentVersion = $this->current->getVersion();
72             $this->version = $this->_currentVersion;
73             $unresolved = $diff->ConflictingBlocks;
74             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
75         }
76
77         if ($this->editaction == 'preview')
78             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
79
80         // FIXME: NOT_CURRENT_MESSAGE?
81
82         $tokens = array_merge($tokens, $this->getFormElements());
83
84         return $this->output('editpage', _("Edit: %s"), $tokens);
85     }
86
87     function output ($template, $title_fs, $tokens) {
88         $selected = &$this->selected;
89         $current = &$this->current;
90
91         if ($selected && $selected->getVersion() != $current->getVersion()) {
92             $rev = $selected;
93             $pagelink = WikiLink($selected);
94         }
95         else {
96             $rev = $current;
97             $pagelink = WikiLink($this->page);
98         }
99
100
101         $title = new FormattedText ($title_fs, $pagelink);
102         $template = Template($template, $tokens);
103
104         GeneratePage($template, $title, $rev);
105         return true;
106     }
107
108
109     function viewSource ($tokens = false) {
110         assert($this->isInitialEdit());
111         assert($this->selected);
112
113         $tokens['PAGE_SOURCE'] = $this->_content;
114
115         return $this->output('viewsource', _("View Source: %s"), $tokens);
116     }
117
118     function setPageLockChanged($isadmin, $lock, &$page) {
119         if ($isadmin) {
120             if (! $page->get('locked') == $lock) {
121                 $request = &$this->request;
122                 $request->setArg('lockchanged', true); //is it safe to add new args to $request like this?
123             }
124             $page->set('locked', $lock);
125         }
126     }
127
128     function savePage () {
129         $request = &$this->request;
130
131         if ($this->isUnchanged()) {
132             // Allow admin lock/unlock even if
133             // no text changes were made.
134             if ($isadmin = $this->user->isadmin()) {
135                 $page = &$this->page;
136                 $lock = $this->meta['locked'];
137                 $this->setPageLockChanged($isadmin, $lock, $page);
138             }
139             // Save failed. No changes made.
140             $this->_redirectToBrowsePage();
141             // user will probably not see the rest of this...
142             include_once('lib/display.php');
143             // force browse of current version:
144             $request->setArg('version', false);
145             displayPage($request, 'nochanges');
146             return true;
147         }
148
149         $page = &$this->page;
150         $lock = $this->meta['locked'];
151         $this->meta['locked'] = ''; // hackish
152
153         // Save new revision
154         $newrevision = $page->createRevision($this->_currentVersion + 1,
155                                              $this->_content,
156                                              $this->meta,
157                                              ExtractWikiPageLinks($this->_content));
158         if (!is_object($newrevision)) {
159             // Save failed.  (Concurrent updates).
160             return false;
161         }
162         // New contents successfully saved...
163         if ($isadmin = $this->user->isadmin())
164             $this->setPageLockChanged($isadmin, $lock, $page);
165
166         // Clean out archived versions of this page.
167         include_once('lib/ArchiveCleaner.php');
168         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
169         $cleaner->cleanPageRevisions($page);
170
171         $dbi = $request->getDbh();
172         $warnings = $dbi->GenericWarnings();
173
174         global $Theme;
175         if (empty($warnings) && ! $Theme->getImageURL('signature')) {
176             // Do redirect to browse page if no signature has
177             // been defined.  In this case, the user will most
178             // likely not see the rest of the HTML we generate
179             // (below).
180             $this->_redirectToBrowsePage();
181         }
182
183         // Force browse of current page version.
184         $request->setArg('version', false);
185
186         require_once('lib/PageType.php');
187         $transformedText = PageType($newrevision);
188         $template = Template('savepage',
189                              array('CONTENT' => $transformedText));
190         //include_once('lib/BlockParser.php');
191         //$template = Template('savepage',
192         //                     array('CONTENT' => TransformText($newrevision)));
193         if (!empty($warnings))
194             $template->replace('WARNINGS', $warnings);
195
196         $pagelink = WikiLink($page);
197
198         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
199         return true;
200     }
201
202     function isConcurrentUpdate () {
203         assert($this->current->getVersion() >= $this->_currentVersion);
204         return $this->current->getVersion() != $this->_currentVersion;
205     }
206
207     function canEdit () {
208         return $this->page->get('locked') && !$this->user->isAdmin();
209     }
210
211     function isInitialEdit () {
212         return $this->_initialEdit;
213     }
214
215     function isUnchanged () {
216         $current = &$this->current;
217
218         if ($this->meta['markup'] !=  $current->get('markup'))
219             return false;
220
221         return $this->_content == $current->getPackedContent();
222     }
223
224     function getPreview () {
225         require_once('lib/PageType.php');
226         return PageType($this->_content, $this->page->getName(), $this->meta['markup']);
227
228         //include_once('lib/BlockParser.php');
229         //return TransformText($this->_content, $this->meta['markup']);
230     }
231
232     function getLockedMessage () {
233         return
234             HTML(HTML::h2(_("Page Locked")),
235                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
236                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
237                  HTML::p(_("Sorry for the inconvenience.")));
238     }
239
240     function getConflictMessage ($unresolved = false) {
241         /*
242          xgettext only knows about c/c++ line-continuation strings
243          it does not know about php's dot operator.
244          We want to translate this entire paragraph as one string, of course.
245          */
246
247         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
248
249         if ($unresolved)
250             $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.",
251                                 "<<<<<<< ". _("Your version"),
252                                 ">>>>>>> ". _("Other version")));
253         else
254             $message = HTML::p(_("Please check it through before saving."));
255
256
257
258         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
259           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
260                        $re_edit_link)),
261           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
262           HTML::li(_("Save your updated changes.")));
263         */
264         return
265             HTML(HTML::h2(_("Conflicting Edits!")),
266                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
267                  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.")),
268                  $message);
269     }
270
271
272     function getTextArea () {
273         $request = &$this->request;
274
275         // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
276         // long lines
277         $readonly = $this->canEdit(); // || $this->isConcurrentUpdate();
278
279         return HTML::textarea(array('class' => 'wikiedit',
280                                     'name' => 'edit[content]',
281                                     'rows' => $request->getPref('editHeight'),
282                                     'cols' => $request->getPref('editWidth'),
283                                     'readonly' => (bool) $readonly,
284                                     'wrap' => 'virtual'),
285                               $this->_content);
286     }
287
288     function getFormElements () {
289         $request = &$this->request;
290         $page = &$this->page;
291
292
293         $h = array('action'   => 'edit',
294                    'pagename' => $page->getName(),
295                    'version'  => $this->version,
296                    'edit[current_version]' => $this->_currentVersion);
297
298         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
299
300
301         $el['EDIT_TEXTAREA'] = $this->getTextArea();
302
303         $el['SUMMARY_INPUT']
304             = HTML::input(array('type'  => 'text',
305                                 'class' => 'wikitext',
306                                 'name'  => 'edit[summary]',
307                                 'size'  => 50,
308                                 'maxlength' => 256,
309                                 'value' => $this->meta['summary']));
310         $el['MINOR_EDIT_CB']
311             = HTML::input(array('type' => 'checkbox',
312                                 'name'  => 'edit[minor_edit]',
313                                 'checked' => (bool) $this->meta['is_minor_edit']));
314         $el['OLD_MARKUP_CB']
315             = HTML::input(array('type' => 'checkbox',
316                                 'name' => 'edit[markup]',
317                                 'value' => 'old',
318                                 'checked' => $this->meta['markup'] < 2.0,
319                                 'id' => 'useOldMarkup',
320                                 'onclick' => 'showOldMarkupRules(this.checked)'));
321
322         $el['LOCKED_CB']
323             = HTML::input(array('type' => 'checkbox',
324                                 'name' => 'edit[locked]',
325                                 'disabled' => (bool) !$this->user->isadmin(),
326                                 'checked'  => (bool) $this->meta['locked']));
327
328         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
329                                   'wikiaction');
330
331         //if (!$this->isConcurrentUpdate() && !$this->canEdit())
332         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
333
334         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
335
336         return $el;
337     }
338
339     function _redirectToBrowsePage() {
340         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
341     }
342     
343
344     function _restoreState () {
345         $request = &$this->request;
346
347         $posted = $request->getArg('edit');
348         $request->setArg('edit', false);
349
350         if (!$posted || !$request->isPost()
351             || $request->getArg('action') != 'edit')
352             return false;
353
354         if (!isset($posted['content']) || !is_string($posted['content']))
355             return false;
356         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
357                                         rtrim($posted['content']));
358
359         $this->_currentVersion = (int) $posted['current_version'];
360
361         if ($this->_currentVersion < 0)
362             return false;
363         if ($this->_currentVersion > $this->current->getVersion())
364             return false;       // FIXME: some kind of warning?
365
366         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
367         $meta['markup'] = $is_old_markup ? false : 2.0;
368         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
369         $meta['locked'] = !empty($posted['locked']);
370         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
371
372         $this->meta = array_merge($this->meta, $meta);
373
374         if (!empty($posted['preview']))
375             $this->editaction = 'preview';
376         elseif (!empty($posted['save']))
377             $this->editaction = 'save';
378         else
379             $this->editaction = 'edit';
380
381         return true;
382     }
383
384     function _initializeState () {
385         $request = &$this->request;
386         $current = &$this->current;
387         $selected = &$this->selected;
388         $user = &$this->user;
389
390         if (!$selected)
391             NoSuchRevision($request, $this->page, $this->version); // noreturn
392
393         $this->_currentVersion = $current->getVersion();
394         $this->_content = $selected->getPackedContent();
395
396         $this->meta['summary'] = '';
397         $this->meta['locked'] = $this->page->get('locked');
398
399         // If author same as previous author, default minor_edit to on.
400         $age = time() - $current->get('mtime');
401         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
402                                          && $current->get('author') == $user->getId()
403                                          );
404
405         // Default for new pages is new-style markup.
406         if ($selected->hasDefaultContents())
407             $is_new_markup = true;
408         else
409             $is_new_markup = $selected->get('markup') >= 2.0;
410
411         $this->meta['markup'] = $is_new_markup ? 2.0: false;
412         $this->editaction = 'edit';
413     }
414 }
415
416 class LoadFileConflictPageEditor
417 extends PageEditor
418 {
419     function editPage ($saveFailed = true) {
420         $tokens = array();
421
422         if ($this->canEdit()) {
423             if ($this->isInitialEdit())
424                 return $this->viewSource();
425             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
426         }
427         elseif ($this->editaction == 'save') {
428             if ($this->savePage())
429                 return true;    // Page saved.
430             $saveFailed = true;
431         }
432
433         if ($saveFailed || $this->isConcurrentUpdate())
434         {
435             // Get the text of the original page, and the two conflicting edits
436             // The diff class takes arrays as input.  So retrieve content as
437             // an array, or convert it as necesary.
438             $orig = $this->page->getRevision($this->_currentVersion);
439             $this_content = explode("\n", $this->_content);
440             $other_content = $this->current->getContent();
441             include_once("lib/diff.php");
442             $diff2 = new Diff($other_content, $this_content);
443             $context_lines = max(4, count($other_content) + 1,
444                                  count($this_content) + 1);
445             $fmt = new BlockDiffFormatter($context_lines);
446
447             $this->_content = $fmt->format($diff2);
448             // FIXME: integrate this into class BlockDiffFormatter
449             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
450                                           $this->_content);
451             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
452                                           $this->_content);
453
454             $this->_currentVersion = $this->current->getVersion();
455             $this->version = $this->_currentVersion;
456             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
457         }
458
459         if ($this->editaction == 'preview')
460             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
461
462         // FIXME: NOT_CURRENT_MESSAGE?
463
464         $tokens = array_merge($tokens, $this->getFormElements());
465
466         return $this->output('editpage', _("Merge and Edit: %s"), $tokens);
467         // FIXME: this doesn't display
468     }
469
470     function output ($template, $title_fs, $tokens) {
471         $selected = &$this->selected;
472         $current = &$this->current;
473
474         if ($selected && $selected->getVersion() != $current->getVersion()) {
475             $rev = $selected;
476             $pagelink = WikiLink($selected);
477         }
478         else {
479             $rev = $current;
480             $pagelink = WikiLink($this->page);
481         }
482
483         $title = new FormattedText ($title_fs, $pagelink);
484         $template = Template($template, $tokens);
485
486         //GeneratePage($template, $title, $rev);
487         PrintXML($template);
488         return true;
489     }
490     function getConflictMessage () {
491         $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.",
492                                     "<<<<<<<",
493                                     "======="),
494                                 HTML::p(_("Please check it through before saving."))));
495         return $message;
496     }
497 }
498
499 /**
500  $Log: not supported by cvs2svn $
501  Revision 1.52  2003/01/03 22:22:00  carstenklapp
502  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
503
504  Revision 1.51  2003/01/03 02:43:26  carstenklapp
505  New class LoadFileConflictPageEditor, for merging / comparing a loaded
506  pgsrc file with an existing page.
507
508  */
509
510 // Local Variables:
511 // mode: php
512 // tab-width: 8
513 // c-basic-offset: 4
514 // c-hanging-comment-ender-p: nil
515 // indent-tabs-mode: nil
516 // End:
517 ?>