]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
Update WikiDB timestamp when editing or deleting pages.
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.54 2003-02-16 19:47:16 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                 $dbi = $request->getDbh();
139                 $dbi->touch();
140             }
141             // Save failed. No changes made.
142             $this->_redirectToBrowsePage();
143             // user will probably not see the rest of this...
144             include_once('lib/display.php');
145             // force browse of current version:
146             $request->setArg('version', false);
147             displayPage($request, 'nochanges');
148             return true;
149         }
150
151         $page = &$this->page;
152         $lock = $this->meta['locked'];
153         $this->meta['locked'] = ''; // hackish
154
155         // Save new revision
156         $newrevision = $page->createRevision($this->_currentVersion + 1,
157                                              $this->_content,
158                                              $this->meta,
159                                              ExtractWikiPageLinks($this->_content));
160         if (!is_object($newrevision)) {
161             // Save failed.  (Concurrent updates).
162             return false;
163         }
164         // New contents successfully saved...
165         if ($isadmin = $this->user->isadmin())
166             $this->setPageLockChanged($isadmin, $lock, $page);
167
168         // Clean out archived versions of this page.
169         include_once('lib/ArchiveCleaner.php');
170         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
171         $cleaner->cleanPageRevisions($page);
172
173         $dbi = $request->getDbh();
174         $warnings = $dbi->GenericWarnings();
175         $dbi->touch();
176         
177         global $Theme;
178         if (empty($warnings) && ! $Theme->getImageURL('signature')) {
179             // Do redirect to browse page if no signature has
180             // been defined.  In this case, the user will most
181             // likely not see the rest of the HTML we generate
182             // (below).
183             $this->_redirectToBrowsePage();
184         }
185
186         // Force browse of current page version.
187         $request->setArg('version', false);
188
189         require_once('lib/PageType.php');
190         $transformedText = PageType($newrevision);
191         $template = Template('savepage',
192                              array('CONTENT' => $transformedText));
193         //include_once('lib/BlockParser.php');
194         //$template = Template('savepage',
195         //                     array('CONTENT' => TransformText($newrevision)));
196         if (!empty($warnings))
197             $template->replace('WARNINGS', $warnings);
198
199         $pagelink = WikiLink($page);
200
201         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
202         return true;
203     }
204
205     function isConcurrentUpdate () {
206         assert($this->current->getVersion() >= $this->_currentVersion);
207         return $this->current->getVersion() != $this->_currentVersion;
208     }
209
210     function canEdit () {
211         return $this->page->get('locked') && !$this->user->isAdmin();
212     }
213
214     function isInitialEdit () {
215         return $this->_initialEdit;
216     }
217
218     function isUnchanged () {
219         $current = &$this->current;
220
221         if ($this->meta['markup'] !=  $current->get('markup'))
222             return false;
223
224         return $this->_content == $current->getPackedContent();
225     }
226
227     function getPreview () {
228         require_once('lib/PageType.php');
229         return PageType($this->_content, $this->page->getName(), $this->meta['markup']);
230
231         //include_once('lib/BlockParser.php');
232         //return TransformText($this->_content, $this->meta['markup']);
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[current_version]' => $this->_currentVersion);
300
301         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
302
303
304         $el['EDIT_TEXTAREA'] = $this->getTextArea();
305
306         $el['SUMMARY_INPUT']
307             = HTML::input(array('type'  => 'text',
308                                 'class' => 'wikitext',
309                                 'name'  => 'edit[summary]',
310                                 'size'  => 50,
311                                 'maxlength' => 256,
312                                 'value' => $this->meta['summary']));
313         $el['MINOR_EDIT_CB']
314             = HTML::input(array('type' => 'checkbox',
315                                 'name'  => 'edit[minor_edit]',
316                                 'checked' => (bool) $this->meta['is_minor_edit']));
317         $el['OLD_MARKUP_CB']
318             = HTML::input(array('type' => 'checkbox',
319                                 'name' => 'edit[markup]',
320                                 'value' => 'old',
321                                 'checked' => $this->meta['markup'] < 2.0,
322                                 'id' => 'useOldMarkup',
323                                 'onclick' => 'showOldMarkupRules(this.checked)'));
324
325         $el['LOCKED_CB']
326             = HTML::input(array('type' => 'checkbox',
327                                 'name' => 'edit[locked]',
328                                 'disabled' => (bool) !$this->user->isadmin(),
329                                 'checked'  => (bool) $this->meta['locked']));
330
331         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
332                                   'wikiaction');
333
334         //if (!$this->isConcurrentUpdate() && !$this->canEdit())
335         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
336
337         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
338
339         return $el;
340     }
341
342     function _redirectToBrowsePage() {
343         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
344     }
345     
346
347     function _restoreState () {
348         $request = &$this->request;
349
350         $posted = $request->getArg('edit');
351         $request->setArg('edit', false);
352
353         if (!$posted || !$request->isPost()
354             || $request->getArg('action') != 'edit')
355             return false;
356
357         if (!isset($posted['content']) || !is_string($posted['content']))
358             return false;
359         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
360                                         rtrim($posted['content']));
361
362         $this->_currentVersion = (int) $posted['current_version'];
363
364         if ($this->_currentVersion < 0)
365             return false;
366         if ($this->_currentVersion > $this->current->getVersion())
367             return false;       // FIXME: some kind of warning?
368
369         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
370         $meta['markup'] = $is_old_markup ? false : 2.0;
371         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
372         $meta['locked'] = !empty($posted['locked']);
373         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
374
375         $this->meta = array_merge($this->meta, $meta);
376
377         if (!empty($posted['preview']))
378             $this->editaction = 'preview';
379         elseif (!empty($posted['save']))
380             $this->editaction = 'save';
381         else
382             $this->editaction = 'edit';
383
384         return true;
385     }
386
387     function _initializeState () {
388         $request = &$this->request;
389         $current = &$this->current;
390         $selected = &$this->selected;
391         $user = &$this->user;
392
393         if (!$selected)
394             NoSuchRevision($request, $this->page, $this->version); // noreturn
395
396         $this->_currentVersion = $current->getVersion();
397         $this->_content = $selected->getPackedContent();
398
399         $this->meta['summary'] = '';
400         $this->meta['locked'] = $this->page->get('locked');
401
402         // If author same as previous author, default minor_edit to on.
403         $age = time() - $current->get('mtime');
404         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
405                                          && $current->get('author') == $user->getId()
406                                          );
407
408         // Default for new pages is new-style markup.
409         if ($selected->hasDefaultContents())
410             $is_new_markup = true;
411         else
412             $is_new_markup = $selected->get('markup') >= 2.0;
413
414         $this->meta['markup'] = $is_new_markup ? 2.0: false;
415         $this->editaction = 'edit';
416     }
417 }
418
419 class LoadFileConflictPageEditor
420 extends PageEditor
421 {
422     function editPage ($saveFailed = true) {
423         $tokens = array();
424
425         if ($this->canEdit()) {
426             if ($this->isInitialEdit())
427                 return $this->viewSource();
428             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
429         }
430         elseif ($this->editaction == 'save') {
431             if ($this->savePage())
432                 return true;    // Page saved.
433             $saveFailed = true;
434         }
435
436         if ($saveFailed || $this->isConcurrentUpdate())
437         {
438             // Get the text of the original page, and the two conflicting edits
439             // The diff class takes arrays as input.  So retrieve content as
440             // an array, or convert it as necesary.
441             $orig = $this->page->getRevision($this->_currentVersion);
442             $this_content = explode("\n", $this->_content);
443             $other_content = $this->current->getContent();
444             include_once("lib/diff.php");
445             $diff2 = new Diff($other_content, $this_content);
446             $context_lines = max(4, count($other_content) + 1,
447                                  count($this_content) + 1);
448             $fmt = new BlockDiffFormatter($context_lines);
449
450             $this->_content = $fmt->format($diff2);
451             // FIXME: integrate this into class BlockDiffFormatter
452             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
453                                           $this->_content);
454             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
455                                           $this->_content);
456
457             $this->_currentVersion = $this->current->getVersion();
458             $this->version = $this->_currentVersion;
459             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
460         }
461
462         if ($this->editaction == 'preview')
463             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
464
465         // FIXME: NOT_CURRENT_MESSAGE?
466
467         $tokens = array_merge($tokens, $this->getFormElements());
468
469         return $this->output('editpage', _("Merge and Edit: %s"), $tokens);
470         // FIXME: this doesn't display
471     }
472
473     function output ($template, $title_fs, $tokens) {
474         $selected = &$this->selected;
475         $current = &$this->current;
476
477         if ($selected && $selected->getVersion() != $current->getVersion()) {
478             $rev = $selected;
479             $pagelink = WikiLink($selected);
480         }
481         else {
482             $rev = $current;
483             $pagelink = WikiLink($this->page);
484         }
485
486         $title = new FormattedText ($title_fs, $pagelink);
487         $template = Template($template, $tokens);
488
489         //GeneratePage($template, $title, $rev);
490         PrintXML($template);
491         return true;
492     }
493     function getConflictMessage () {
494         $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.",
495                                     "<<<<<<<",
496                                     "======="),
497                                 HTML::p(_("Please check it through before saving."))));
498         return $message;
499     }
500 }
501
502 /**
503  $Log: not supported by cvs2svn $
504  Revision 1.53  2003/02/15 23:20:27  dairiki
505  Redirect back to browse current version of page upon save,
506  even when no changes were made.
507
508  Revision 1.52  2003/01/03 22:22:00  carstenklapp
509  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
510
511  Revision 1.51  2003/01/03 02:43:26  carstenklapp
512  New class LoadFileConflictPageEditor, for merging / comparing a loaded
513  pgsrc file with an existing page.
514
515  */
516
517 // Local Variables:
518 // mode: php
519 // tab-width: 8
520 // c-basic-offset: 4
521 // c-hanging-comment-ender-p: nil
522 // indent-tabs-mode: nil
523 // End:
524 ?>