]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
New action=create. Essentially the same as action=edit, except that if the
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.57 2003-02-26 03:40:22 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         // 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
20         }
21         
22         
23         $this->meta = array('author' => $this->user->getId(),
24                             'author_id' => $this->user->getAuthenticatedId(),
25                             'mtime' => time());
26         
27         $this->tokens = array();
28         
29         $version = $request->getArg('version');
30         if ($version !== false) {
31             $this->selected = $this->page->getRevision($version);
32             $this->version = $version;
33         }
34         else {
35             $this->selected = $this->current;
36             $this->version = $this->current->getVersion();
37         }
38
39         if ($this->_restoreState()) {
40             $this->_initialEdit = false;
41         }
42         else {
43             $this->_initializeState();
44             $this->_initialEdit = true;
45         }
46
47         header("Content-Type: text/html; charset=" . CHARSET);
48     }
49
50     function editPage () {
51         $saveFailed = false;
52         $tokens = &$this->tokens;
53
54         if (! $this->canEdit()) {
55             if ($this->isInitialEdit())
56                 return $this->viewSource();
57             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
58         }
59         elseif ($this->editaction == 'save') {
60             if ($this->savePage())
61                 return true;    // Page saved.
62             $saveFailed = true;
63         }
64
65         if ($saveFailed || $this->isConcurrentUpdate())
66         {
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);
85         }
86
87         if ($this->editaction == 'preview')
88             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
89
90         // FIXME: NOT_CURRENT_MESSAGE?
91
92         $tokens = array_merge($tokens, $this->getFormElements());
93
94         return $this->output('editpage', _("Edit: %s"));
95     }
96
97     function output ($template, $title_fs) {
98         $selected = &$this->selected;
99         $current = &$this->current;
100
101         if ($selected && $selected->getVersion() != $current->getVersion()) {
102             $rev = $selected;
103             $pagelink = WikiLink($selected);
104         }
105         else {
106             $rev = $current;
107             $pagelink = WikiLink($this->page);
108         }
109
110
111         $title = new FormattedText ($title_fs, $pagelink);
112         $template = Template($template, $this->tokens);
113
114         GeneratePage($template, $title, $rev);
115         return true;
116     }
117
118
119     function viewSource () {
120         assert($this->isInitialEdit());
121         assert($this->selected);
122
123         $this->tokens['PAGE_SOURCE'] = $this->_content;
124         return $this->output('viewsource', _("View Source: %s"));
125     }
126
127     function updateLock() {
128         if ((bool)$this->page->get('locked') == (bool)$this->locked)
129             return false;       // Not changed.
130
131         if (!$this->user->isAdmin()) {
132             // FIXME: some sort of message
133             return false;         // not allowed.
134         }
135
136         $page->set('locked', (bool)$this->locked);
137         $this->tokens['LOCK_CHANGED_MSG']
138             = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
139
140         return true;            // lock changed.
141     }
142
143     function savePage () {
144         $request = &$this->request;
145
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();
151                 $dbi->touch();
152             }
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');
160             return true;
161         }
162
163         $page = &$this->page;
164
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);
172         
173         // Save new revision
174         $newrevision = $page->save($this->_content, $this->_currentVersion + 1, $meta);
175         if (!is_object($newrevision)) {
176             // Save failed.  (Concurrent updates).
177             return false;
178         }
179         
180         // New contents successfully saved...
181         $this->updateLock();
182
183         // Clean out archived versions of this page.
184         include_once('lib/ArchiveCleaner.php');
185         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
186         $cleaner->cleanPageRevisions($page);
187
188         $dbi = $request->getDbh();
189         $warnings = $dbi->GenericWarnings();
190         $dbi->touch();
191         
192         global $Theme;
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
197             // (below).
198             $this->_redirectToBrowsePage();
199         }
200
201         // Force browse of current page version.
202         $request->setArg('version', false);
203
204         $template = Template('savepage', $this->tokens);
205         $template->replace('CONTENT', $newrevision->getTransformedContent());
206         if (!empty($warnings))
207             $template->replace('WARNINGS', $warnings);
208
209         $pagelink = WikiLink($page);
210
211         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
212         return true;
213     }
214
215     function isConcurrentUpdate () {
216         assert($this->current->getVersion() >= $this->_currentVersion);
217         return $this->current->getVersion() != $this->_currentVersion;
218     }
219
220     function canEdit () {
221         return !$this->page->get('locked') || $this->user->isAdmin();
222     }
223
224     function isInitialEdit () {
225         return $this->_initialEdit;
226     }
227
228     function isUnchanged () {
229         $current = &$this->current;
230
231         if ($this->meta['markup'] !=  $current->get('markup'))
232             return false;
233
234         return $this->_content == $current->getPackedContent();
235     }
236
237     function getPreview () {
238         include_once('lib/PageType.php');
239         return new TransformedText($this->page, $this->_content, $this->meta);
240     }
241
242     function getLockedMessage () {
243         return
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.")));
248     }
249
250     function getConflictMessage ($unresolved = false) {
251         /*
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.
255          */
256
257         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
258
259         if ($unresolved)
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")));
263         else
264             $message = HTML::p(_("Please check it through before saving."));
265
266
267
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.",
270                        $re_edit_link)),
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.")));
273         */
274         return
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.")),
278                  $message);
279     }
280
281
282     function getTextArea () {
283         $request = &$this->request;
284
285         // wrap=virtual is not HTML4, but without it NS4 doesn't wrap
286         // long lines
287         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
288
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'),
295                               $this->_content);
296     }
297
298     function getFormElements () {
299         $request = &$this->request;
300         $page = &$this->page;
301
302
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);
308
309         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
310
311
312         $el['EDIT_TEXTAREA'] = $this->getTextArea();
313
314         $el['SUMMARY_INPUT']
315             = HTML::input(array('type'  => 'text',
316                                 'class' => 'wikitext',
317                                 'name'  => 'edit[summary]',
318                                 'size'  => 50,
319                                 'maxlength' => 256,
320                                 'value' => $this->meta['summary']));
321         $el['MINOR_EDIT_CB']
322             = HTML::input(array('type' => 'checkbox',
323                                 'name'  => 'edit[minor_edit]',
324                                 'checked' => (bool) $this->meta['is_minor_edit']));
325         $el['OLD_MARKUP_CB']
326             = HTML::input(array('type' => 'checkbox',
327                                 'name' => 'edit[markup]',
328                                 'value' => 'old',
329                                 'checked' => $this->meta['markup'] < 2.0,
330                                 'id' => 'useOldMarkup',
331                                 'onclick' => 'showOldMarkupRules(this.checked)'));
332
333         $el['LOCKED_CB']
334             = HTML::input(array('type' => 'checkbox',
335                                 'name' => 'edit[locked]',
336                                 'disabled' => (bool) !$this->user->isadmin(),
337                                 'checked'  => (bool) $this->locked));
338
339         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
340                                   'wikiaction');
341
342         //if (!$this->isConcurrentUpdate() && $this->canEdit())
343         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
344
345         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
346
347         return $el;
348     }
349
350     function _redirectToBrowsePage() {
351         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
352     }
353     
354
355     function _restoreState () {
356         $request = &$this->request;
357
358         $posted = $request->getArg('edit');
359         $request->setArg('edit', false);
360
361         if (!$posted || !$request->isPost()
362             || $request->getArg('action') != 'edit')
363             return false;
364
365         if (!isset($posted['content']) || !is_string($posted['content']))
366             return false;
367         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
368                                         rtrim($posted['content']));
369
370         $this->_currentVersion = (int) $posted['current_version'];
371
372         if ($this->_currentVersion < 0)
373             return false;
374         if ($this->_currentVersion > $this->current->getVersion())
375             return false;       // FIXME: some kind of warning?
376
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']);
384
385         if (!empty($posted['preview']))
386             $this->editaction = 'preview';
387         elseif (!empty($posted['save']))
388             $this->editaction = 'save';
389         else
390             $this->editaction = 'edit';
391
392         return true;
393     }
394
395     function _initializeState () {
396         $request = &$this->request;
397         $current = &$this->current;
398         $selected = &$this->selected;
399         $user = &$this->user;
400
401         if (!$selected)
402             NoSuchRevision($request, $this->page, $this->version); // noreturn
403
404         $this->_currentVersion = $current->getVersion();
405         $this->_content = $selected->getPackedContent();
406
407         $this->meta['summary'] = '';
408         $this->locked = $this->page->get('locked');
409
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()
414                                          );
415
416         // Default for new pages is new-style markup.
417         if ($selected->hasDefaultContents())
418             $is_new_markup = true;
419         else
420             $is_new_markup = $selected->get('markup') >= 2.0;
421
422         $this->meta['markup'] = $is_new_markup ? 2.0: false;
423         $this->meta['pagetype'] = $selected->get('pagetype');
424         $this->editaction = 'edit';
425     }
426 }
427
428 class LoadFileConflictPageEditor
429 extends PageEditor
430 {
431     function editPage ($saveFailed = true) {
432         $tokens = &$this->tokens;
433
434         if (!$this->canEdit()) {
435             if ($this->isInitialEdit())
436                 return $this->viewSource();
437             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
438         }
439         elseif ($this->editaction == 'save') {
440             if ($this->savePage())
441                 return true;    // Page saved.
442             $saveFailed = true;
443         }
444
445         if ($saveFailed || $this->isConcurrentUpdate())
446         {
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);
458
459             $this->_content = $fmt->format($diff2);
460             // FIXME: integrate this into class BlockDiffFormatter
461             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
462                                           $this->_content);
463             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
464                                           $this->_content);
465
466             $this->_currentVersion = $this->current->getVersion();
467             $this->version = $this->_currentVersion;
468             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
469         }
470
471         if ($this->editaction == 'preview')
472             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
473
474         // FIXME: NOT_CURRENT_MESSAGE?
475
476         $tokens = array_merge($tokens, $this->getFormElements());
477
478         return $this->output('editpage', _("Merge and Edit: %s"));
479         // FIXME: this doesn't display
480     }
481
482     function output ($template, $title_fs) {
483         $selected = &$this->selected;
484         $current = &$this->current;
485
486         if ($selected && $selected->getVersion() != $current->getVersion()) {
487             $rev = $selected;
488             $pagelink = WikiLink($selected);
489         }
490         else {
491             $rev = $current;
492             $pagelink = WikiLink($this->page);
493         }
494
495         $title = new FormattedText ($title_fs, $pagelink);
496         $template = Template($template, $this->tokens);
497
498         //GeneratePage($template, $title, $rev);
499         PrintXML($template);
500         return true;
501     }
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.",
504                                     "<<<<<<<",
505                                     "======="),
506                                 HTML::p(_("Please check it through before saving."))));
507         return $message;
508     }
509 }
510
511 /**
512  $Log: not supported by cvs2svn $
513  Revision 1.56  2003/02/21 18:07:14  dairiki
514  Minor, nitpicky, currently inconsequential changes.
515
516  Revision 1.55  2003/02/21 04:10:58  dairiki
517  Fixes for new cached markup.
518  Some minor code cleanups.
519
520  Revision 1.54  2003/02/16 19:47:16  dairiki
521  Update WikiDB timestamp when editing or deleting pages.
522
523  Revision 1.53  2003/02/15 23:20:27  dairiki
524  Redirect back to browse current version of page upon save,
525  even when no changes were made.
526
527  Revision 1.52  2003/01/03 22:22:00  carstenklapp
528  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
529
530  Revision 1.51  2003/01/03 02:43:26  carstenklapp
531  New class LoadFileConflictPageEditor, for merging / comparing a loaded
532  pgsrc file with an existing page.
533
534  */
535
536 // Local Variables:
537 // mode: php
538 // tab-width: 8
539 // c-basic-offset: 4
540 // c-hanging-comment-ender-p: nil
541 // indent-tabs-mode: nil
542 // End:
543 ?>