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