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