]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Minor, nitpicky, currently inconsequential changes.
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.56 2003-02-21 18:07:14 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                             'author_id' => $this->user->getAuthenticatedId(),
18                             'mtime' => time());
19         
20         $this->tokens = array();
21         
22         $version = $request->getArg('version');
23         if ($version !== false) {
24             $this->selected = $this->page->getRevision($version);
25             $this->version = $version;
26         }
27         else {
28             $this->selected = $this->current;
29             $this->version = $this->current->getVersion();
30         }
31
32         if ($this->_restoreState()) {
33             $this->_initialEdit = false;
34         }
35         else {
36             $this->_initializeState();
37             $this->_initialEdit = true;
38         }
39
40         header("Content-Type: text/html; charset=" . CHARSET);
41     }
42
43     function editPage () {
44         $saveFailed = false;
45         $tokens = &$this->tokens;
46
47         if (! $this->canEdit()) {
48             if ($this->isInitialEdit())
49                 return $this->viewSource();
50             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
51         }
52         elseif ($this->editaction == 'save') {
53             if ($this->savePage())
54                 return true;    // Page saved.
55             $saveFailed = true;
56         }
57
58         if ($saveFailed || $this->isConcurrentUpdate())
59         {
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);
78         }
79
80         if ($this->editaction == 'preview')
81             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
82
83         // FIXME: NOT_CURRENT_MESSAGE?
84
85         $tokens = array_merge($tokens, $this->getFormElements());
86
87         return $this->output('editpage', _("Edit: %s"));
88     }
89
90     function output ($template, $title_fs) {
91         $selected = &$this->selected;
92         $current = &$this->current;
93
94         if ($selected && $selected->getVersion() != $current->getVersion()) {
95             $rev = $selected;
96             $pagelink = WikiLink($selected);
97         }
98         else {
99             $rev = $current;
100             $pagelink = WikiLink($this->page);
101         }
102
103
104         $title = new FormattedText ($title_fs, $pagelink);
105         $template = Template($template, $this->tokens);
106
107         GeneratePage($template, $title, $rev);
108         return true;
109     }
110
111
112     function viewSource () {
113         assert($this->isInitialEdit());
114         assert($this->selected);
115
116         $this->tokens['PAGE_SOURCE'] = $this->_content;
117         return $this->output('viewsource', _("View Source: %s"));
118     }
119
120     function updateLock() {
121         if ((bool)$this->page->get('locked') == (bool)$this->locked)
122             return false;       // Not changed.
123
124         if (!$this->user->isAdmin()) {
125             // FIXME: some sort of message
126             return false;         // not allowed.
127         }
128
129         $page->set('locked', (bool)$this->locked);
130         $this->tokens['LOCK_CHANGED_MSG']
131             = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
132
133         return true;            // lock changed.
134     }
135
136     function savePage () {
137         $request = &$this->request;
138
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();
144                 $dbi->touch();
145             }
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');
153             return true;
154         }
155
156         $page = &$this->page;
157
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);
165         
166         // Save new revision
167         $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
168         if (!is_object($newrevision)) {
169             // Save failed.  (Concurrent updates).
170             return false;
171         }
172         
173         // New contents successfully saved...
174         $this->updateLock();
175
176         // Clean out archived versions of this page.
177         include_once('lib/ArchiveCleaner.php');
178         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
179         $cleaner->cleanPageRevisions($page);
180
181         $dbi = $request->getDbh();
182         $warnings = $dbi->GenericWarnings();
183         $dbi->touch();
184         
185         global $Theme;
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
190             // (below).
191             $this->_redirectToBrowsePage();
192         }
193
194         // Force browse of current page version.
195         $request->setArg('version', false);
196
197         $template = Template('savepage', $this->tokens);
198         $template->replace('CONTENT', $newrevision->getTransformedContent());
199         if (!empty($warnings))
200             $template->replace('WARNINGS', $warnings);
201
202         $pagelink = WikiLink($page);
203
204         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
205         return true;
206     }
207
208     function isConcurrentUpdate () {
209         assert($this->current->getVersion() >= $this->_currentVersion);
210         return $this->current->getVersion() != $this->_currentVersion;
211     }
212
213     function canEdit () {
214         return !$this->page->get('locked') || $this->user->isAdmin();
215     }
216
217     function isInitialEdit () {
218         return $this->_initialEdit;
219     }
220
221     function isUnchanged () {
222         $current = &$this->current;
223
224         if ($this->meta['markup'] !=  $current->get('markup'))
225             return false;
226
227         return $this->_content == $current->getPackedContent();
228     }
229
230     function getPreview () {
231         include_once('lib/PageType.php');
232         return new TransformedText($this->page, $this->_content, $this->meta);
233     }
234
235     function getLockedMessage () {
236         return
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.")));
241     }
242
243     function getConflictMessage ($unresolved = false) {
244         /*
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.
248          */
249
250         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
251
252         if ($unresolved)
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")));
256         else
257             $message = HTML::p(_("Please check it through before saving."));
258
259
260
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.",
263                        $re_edit_link)),
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.")));
266         */
267         return
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.")),
271                  $message);
272     }
273
274
275     function getTextArea () {
276         $request = &$this->request;
277
278         // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
279         // long lines
280         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
281
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'),
288                               $this->_content);
289     }
290
291     function getFormElements () {
292         $request = &$this->request;
293         $page = &$this->page;
294
295
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);
301
302         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
303
304
305         $el['EDIT_TEXTAREA'] = $this->getTextArea();
306
307         $el['SUMMARY_INPUT']
308             = HTML::input(array('type'  => 'text',
309                                 'class' => 'wikitext',
310                                 'name'  => 'edit[summary]',
311                                 'size'  => 50,
312                                 'maxlength' => 256,
313                                 'value' => $this->meta['summary']));
314         $el['MINOR_EDIT_CB']
315             = HTML::input(array('type' => 'checkbox',
316                                 'name'  => 'edit[minor_edit]',
317                                 'checked' => (bool) $this->meta['is_minor_edit']));
318         $el['OLD_MARKUP_CB']
319             = HTML::input(array('type' => 'checkbox',
320                                 'name' => 'edit[markup]',
321                                 'value' => 'old',
322                                 'checked' => $this->meta['markup'] < 2.0,
323                                 'id' => 'useOldMarkup',
324                                 'onclick' => 'showOldMarkupRules(this.checked)'));
325
326         $el['LOCKED_CB']
327             = HTML::input(array('type' => 'checkbox',
328                                 'name' => 'edit[locked]',
329                                 'disabled' => (bool) !$this->user->isadmin(),
330                                 'checked'  => (bool) $this->locked));
331
332         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
333                                   'wikiaction');
334
335         //if (!$this->isConcurrentUpdate() && $this->canEdit())
336         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
337
338         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
339
340         return $el;
341     }
342
343     function _redirectToBrowsePage() {
344         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
345     }
346     
347
348     function _restoreState () {
349         $request = &$this->request;
350
351         $posted = $request->getArg('edit');
352         $request->setArg('edit', false);
353
354         if (!$posted || !$request->isPost()
355             || $request->getArg('action') != 'edit')
356             return false;
357
358         if (!isset($posted['content']) || !is_string($posted['content']))
359             return false;
360         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
361                                         rtrim($posted['content']));
362
363         $this->_currentVersion = (int) $posted['current_version'];
364
365         if ($this->_currentVersion < 0)
366             return false;
367         if ($this->_currentVersion > $this->current->getVersion())
368             return false;       // FIXME: some kind of warning?
369
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']);
377
378         if (!empty($posted['preview']))
379             $this->editaction = 'preview';
380         elseif (!empty($posted['save']))
381             $this->editaction = 'save';
382         else
383             $this->editaction = 'edit';
384
385         return true;
386     }
387
388     function _initializeState () {
389         $request = &$this->request;
390         $current = &$this->current;
391         $selected = &$this->selected;
392         $user = &$this->user;
393
394         if (!$selected)
395             NoSuchRevision($request, $this->page, $this->version); // noreturn
396
397         $this->_currentVersion = $current->getVersion();
398         $this->_content = $selected->getPackedContent();
399
400         $this->meta['summary'] = '';
401         $this->locked = $this->page->get('locked');
402
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()
407                                          );
408
409         // Default for new pages is new-style markup.
410         if ($selected->hasDefaultContents())
411             $is_new_markup = true;
412         else
413             $is_new_markup = $selected->get('markup') >= 2.0;
414
415         $this->meta['markup'] = $is_new_markup ? 2.0: false;
416         $this->meta['pagetype'] = $selected->get('pagetype');
417         $this->editaction = 'edit';
418     }
419 }
420
421 class LoadFileConflictPageEditor
422 extends PageEditor
423 {
424     function editPage ($saveFailed = true) {
425         $tokens = &$this->tokens;
426
427         if (!$this->canEdit()) {
428             if ($this->isInitialEdit())
429                 return $this->viewSource();
430             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
431         }
432         elseif ($this->editaction == 'save') {
433             if ($this->savePage())
434                 return true;    // Page saved.
435             $saveFailed = true;
436         }
437
438         if ($saveFailed || $this->isConcurrentUpdate())
439         {
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);
451
452             $this->_content = $fmt->format($diff2);
453             // FIXME: integrate this into class BlockDiffFormatter
454             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
455                                           $this->_content);
456             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
457                                           $this->_content);
458
459             $this->_currentVersion = $this->current->getVersion();
460             $this->version = $this->_currentVersion;
461             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
462         }
463
464         if ($this->editaction == 'preview')
465             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
466
467         // FIXME: NOT_CURRENT_MESSAGE?
468
469         $tokens = array_merge($tokens, $this->getFormElements());
470
471         return $this->output('editpage', _("Merge and Edit: %s"));
472         // FIXME: this doesn't display
473     }
474
475     function output ($template, $title_fs) {
476         $selected = &$this->selected;
477         $current = &$this->current;
478
479         if ($selected && $selected->getVersion() != $current->getVersion()) {
480             $rev = $selected;
481             $pagelink = WikiLink($selected);
482         }
483         else {
484             $rev = $current;
485             $pagelink = WikiLink($this->page);
486         }
487
488         $title = new FormattedText ($title_fs, $pagelink);
489         $template = Template($template, $this->tokens);
490
491         //GeneratePage($template, $title, $rev);
492         PrintXML($template);
493         return true;
494     }
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.",
497                                     "<<<<<<<",
498                                     "======="),
499                                 HTML::p(_("Please check it through before saving."))));
500         return $message;
501     }
502 }
503
504 /**
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.
509
510  Revision 1.54  2003/02/16 19:47:16  dairiki
511  Update WikiDB timestamp when editing or deleting pages.
512
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.
516
517  Revision 1.52  2003/01/03 22:22:00  carstenklapp
518  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
519
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.
523
524  */
525
526 // Local Variables:
527 // mode: php
528 // tab-width: 8
529 // c-basic-offset: 4
530 // c-hanging-comment-ender-p: nil
531 // indent-tabs-mode: nil
532 // End:
533 ?>