]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/editpage.php
fix ConvertBefore
[SourceForge/phpwiki.git] / lib / editpage.php
1 <?php
2 rcs_id('$Id: editpage.php,v 1.104 2005-10-31 17:20:40 rurban 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(false);
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         if (ENABLE_WYSIWYG) {
30             $backend = WYSIWYG_BACKEND;
31             // TODO: error message
32             require_once("lib/WysiwygEdit/$backend.php");
33             $class = "WysiwygEdit_$backend";
34             $this->WysiwygEdit = new $class();
35         }
36         if (ENABLE_CAPTCHA) {
37             require_once('lib/Captcha.php');
38             $this->Captcha = new Captcha($this->meta);
39         }
40
41         $version = $request->getArg('version');
42         if ($version !== false) {
43             $this->selected = $this->page->getRevision($version);
44             $this->version = $version;
45         }
46         else {
47             $this->version = $this->current->getVersion();
48             $this->selected = $this->page->getRevision($this->version);
49         }
50
51         if ($this->_restoreState()) {
52             $this->_initialEdit = false;
53         }
54         else {
55             $this->_initializeState();
56             $this->_initialEdit = true;
57
58             // The edit request has specified some initial content from a template 
59             if (  ($template = $request->getArg('template'))
60                    and $request->_dbi->isWikiPage($template)) {
61                 $page = $request->_dbi->getPage($template);
62                 $current = $page->getCurrentRevision();
63                 $this->_content = $current->getPackedContent();
64             } elseif ($initial_content = $request->getArg('initial_content')) {
65                 $this->_content = $initial_content;
66                 $this->_redirect_to = $request->getArg('save_and_redirect_to');
67             }
68         }
69         if (!headers_sent())
70             header("Content-Type: text/html; charset=" . $GLOBALS['charset']);
71     }
72
73     function editPage () {
74         global $WikiTheme;
75         $saveFailed = false;
76         $tokens = &$this->tokens;
77         $tokens['PAGE_LOCKED_MESSAGE'] = '';
78         $tokens['CONCURRENT_UPDATE_MESSAGE'] = '';
79
80         if (isset($this->request->args['pref']['editWidth'])
81             and ($this->request->getPref('editWidth') != $this->request->args['pref']['editWidth'])) {
82             $this->request->_prefs->set('editWidth', $this->request->args['pref']['editWidth']);
83         }
84         if (isset($this->request->args['pref']['editHeight'])
85             and ($this->request->getPref('editHeight') != $this->request->args['pref']['editHeight'])) {
86             $this->request->_prefs->set('editHeight', $this->request->args['pref']['editHeight']);
87         }
88
89         if (! $this->canEdit()) {
90             if ($this->isInitialEdit())
91                 return $this->viewSource();
92             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
93         }
94         elseif ($this->request->getArg('save_and_redirect_to') != "") {
95             if (ENABLE_CAPTCHA && $this->Captcha->Failed()) {
96                 $this->tokens['PAGE_LOCKED_MESSAGE'] = 
97                     HTML::p(HTML::h1($this->Captcha->failed_msg));
98             }
99             elseif ( $this->savePage()) {
100                 // noreturn
101                 $this->request->redirect(WikiURL($this->request->getArg('save_and_redirect_to')));
102                 return true;    // Page saved.
103             }
104             $saveFailed = true;
105         }
106         elseif ($this->editaction == 'save') {
107             if (ENABLE_CAPTCHA && $this->Captcha->Failed()) {
108                 $this->tokens['PAGE_LOCKED_MESSAGE'] = 
109                     HTML::p(HTML::h1($this->Captcha->failed_msg));
110             }
111             elseif ($this->savePage()) {
112                 return true;    // Page saved.
113             }
114             else {
115                 $saveFailed = true;
116             }
117         }
118
119         if ($saveFailed and $this->isConcurrentUpdate())
120         {
121             // Get the text of the original page, and the two conflicting edits
122             // The diff3 class takes arrays as input.  So retrieve content as
123             // an array, or convert it as necesary.
124             $orig = $this->page->getRevision($this->_currentVersion);
125             // FIXME: what if _currentVersion has be deleted?
126             $orig_content = $orig->getContent();
127             $this_content = explode("\n", $this->_content);
128             $other_content = $this->current->getContent();
129             include_once("lib/diff3.php");
130             $diff = new diff3($orig_content, $this_content, $other_content);
131             $output = $diff->merged_output(_("Your version"), _("Other version"));
132             // Set the content of the textarea to the merged diff
133             // output, and update the version
134             $this->_content = implode ("\n", $output);
135             $this->_currentVersion = $this->current->getVersion();
136             $this->version = $this->_currentVersion;
137             $unresolved = $diff->ConflictingBlocks;
138             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage($unresolved);
139         } elseif ($saveFailed && !$this->_isSpam) {
140             $tokens['CONCURRENT_UPDATE_MESSAGE'] = 
141                 HTML(HTML::h2(_("Some internal editing error")),
142                      HTML::p(_("Your are probably trying to edit/create an invalid version of this page.")),
143                      HTML::p(HTML::em(_("&version=-1 might help."))));
144         }
145
146         if ($this->editaction == 'edit_convert')
147             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
148         if ($this->editaction == 'preview')
149             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
150
151         // FIXME: NOT_CURRENT_MESSAGE?
152         $tokens = array_merge($tokens, $this->getFormElements());
153
154         if (ENABLE_EDIT_TOOLBAR and !ENABLE_WYSIWYG) {
155             include_once("lib/EditToolbar.php");
156             $toolbar = new EditToolbar();
157             $tokens = array_merge($tokens, $toolbar->getTokens());
158         }
159
160         return $this->output('editpage', _("Edit: %s"));
161     }
162
163     function output ($template, $title_fs) {
164         global $WikiTheme;
165         $selected = &$this->selected;
166         $current = &$this->current;
167
168         if ($selected && $selected->getVersion() != $current->getVersion()) {
169             $rev = $selected;
170             $pagelink = WikiLink($selected);
171         }
172         else {
173             $rev = $current;
174             $pagelink = WikiLink($this->page);
175         }
176
177         $title = new FormattedText ($title_fs, $pagelink);
178         // not for dumphtml or viewsource
179         if (ENABLE_WYSIWYG and $template == 'editpage') {
180             $WikiTheme->addMoreHeaders($this->WysiwygEdit->Head());
181             //$tokens['PAGE_SOURCE'] = $this->WysiwygEdit->ConvertBefore($this->_content);
182         }
183         $template = Template($template, $this->tokens);
184         GeneratePage($template, $title, $rev);
185         return true;
186     }
187
188
189     function viewSource () {
190         assert($this->isInitialEdit());
191         assert($this->selected);
192
193         $this->tokens['PAGE_SOURCE'] = $this->_content;
194         $this->tokens['HIDDEN_INPUTS'] = HiddenInputs($this->request->getArgs());
195         return $this->output('viewsource', _("View Source: %s"));
196     }
197
198     function updateLock() {
199         if ((bool)$this->page->get('locked') == (bool)$this->locked)
200             return false;       // Not changed.
201
202         if (!$this->user->isAdmin()) {
203             // FIXME: some sort of message
204             return false;         // not allowed.
205         }
206
207         $this->page->set('locked', (bool)$this->locked);
208         $this->tokens['LOCK_CHANGED_MSG']
209             = $this->locked ? _("Page now locked.") : _("Page now unlocked.");
210
211         return true;            // lock changed.
212     }
213
214     function savePage () {
215         $request = &$this->request;
216
217         if ($this->isUnchanged()) {
218             // Allow admin lock/unlock even if
219             // no text changes were made.
220             if ($this->updateLock()) {
221                 $dbi = $request->getDbh();
222                 $dbi->touch();
223             }
224             // Save failed. No changes made.
225             $this->_redirectToBrowsePage();
226             // user will probably not see the rest of this...
227             include_once('lib/display.php');
228             // force browse of current version:
229             $request->setArg('version', false);
230             displayPage($request, 'nochanges');
231             return true;
232         }
233
234         if ($this->isSpam()) {
235             $this->_isSpam = true;
236             return false;
237             /*
238             // Save failed. No changes made.
239             $this->_redirectToBrowsePage();
240             // user will probably not see the rest of this...
241             include_once('lib/display.php');
242             // force browse of current version:
243             $request->setArg('version', false);
244             displayPage($request, 'nochanges');
245             return true;
246             */
247         }
248
249         $page = &$this->page;
250
251         // Include any meta-data from original page version which
252         // has not been explicitly updated.
253         // (Except don't propagate pgsrc_version --- moot for now,
254         //  because at present it never gets into the db...)
255         $meta = $this->selected->getMetaData();
256         unset($meta['pgsrc_version']);
257         $meta = array_merge($meta, $this->meta);
258         
259         // Save new revision
260         $this->_content = $this->getContent();
261         $newrevision = $page->save($this->_content, 
262                                    $this->version == -1 
263                                      ? -1 
264                                      : $this->_currentVersion + 1, 
265                                    // force new?
266                                    $meta);
267         if (!isa($newrevision, 'WikiDB_PageRevision')) {
268             // Save failed.  (Concurrent updates).
269             return false;
270         }
271         
272         // New contents successfully saved...
273         $this->updateLock();
274
275         // Clean out archived versions of this page.
276         include_once('lib/ArchiveCleaner.php');
277         $cleaner = new ArchiveCleaner($GLOBALS['ExpireParams']);
278         $cleaner->cleanPageRevisions($page);
279
280         /* generate notification emails done in WikiDB::save to catch all direct calls 
281           (admin plugins) */
282
283         // look at the errorstack
284         $errors   = $GLOBALS['ErrorManager']->_postponed_errors;
285         $warnings = $GLOBALS['ErrorManager']->getPostponedErrorsAsHTML(); 
286         $GLOBALS['ErrorManager']->_postponed_errors = $errors;
287
288         $dbi = $request->getDbh();
289         $dbi->touch();
290         
291         global $WikiTheme;
292         if (empty($warnings->_content) && ! $WikiTheme->getImageURL('signature')) {
293             // Do redirect to browse page if no signature has
294             // been defined.  In this case, the user will most
295             // likely not see the rest of the HTML we generate
296             // (below).
297             $this->_redirectToBrowsePage();
298         }
299
300         // Force browse of current page version.
301         $request->setArg('version', false);
302         //$request->setArg('action', false);
303
304         $template = Template('savepage', $this->tokens);
305         $template->replace('CONTENT', $newrevision->getTransformedContent());
306         if (!empty($warnings->_content))
307             $template->replace('WARNINGS', $warnings);
308
309         $pagelink = WikiLink($page);
310
311         GeneratePage($template, fmt("Saved: %s", $pagelink), $newrevision);
312         return true;
313     }
314
315     function isConcurrentUpdate () {
316         assert($this->current->getVersion() >= $this->_currentVersion);
317         return $this->current->getVersion() != $this->_currentVersion;
318     }
319
320     function canEdit () {
321         return !$this->page->get('locked') || $this->user->isAdmin();
322     }
323
324     function isInitialEdit () {
325         return $this->_initialEdit;
326     }
327
328     function isUnchanged () {
329         $current = &$this->current;
330
331         if ($this->meta['markup'] !=  $current->get('markup'))
332             return false;
333
334         return $this->_content == $current->getPackedContent();
335     }
336
337     /** 
338      * Handle AntiSpam here. How? http://wikiblacklist.blogspot.com/
339      * Need to check dynamically some blacklist wikipage settings 
340      * (plugin WikiAccessRestrictions) and some static blacklist.
341      * DONE: 
342      *   Always: More then 20 new external links
343      *   ENABLE_SPAMASSASSIN:  content patterns by babycart (only php >= 4.3 for now)
344      *   ENABLE_SPAMBLOCKLIST: content domain blacklist
345      */
346     function isSpam () {
347         $current = &$this->current;
348         $request = &$this->request;
349
350         $oldtext = $current->getPackedContent();
351         $newtext =& $this->_content;
352
353         // FIXME: in longer texts the NUM_SPAM_LINKS number should be increased.
354         //        better use a certain text : link ratio.
355
356         // 1. Not more then 20 new external links
357         if ($this->numLinks($newtext) - $this->numLinks($oldtext) >= NUM_SPAM_LINKS) {
358             // TODO: mail the admin?
359             $this->tokens['PAGE_LOCKED_MESSAGE'] = 
360                 HTML($this->getSpamMessage(),
361                      HTML::p(HTML::strong(_("Too many external links."))));
362             return true;
363         }
364         // 2. external babycart (SpamAssassin) check
365         // This will probably prevent from discussing sex or viagra related topics. So beware.
366         if (ENABLE_SPAMASSASSIN) {
367             $user = $request->getUser();
368             include_once("lib/spam_babycart.php");
369             if ($babycart = check_babycart($newtext, $request->get("REMOTE_ADDR"), 
370                                            $user->getId())) {
371                 // TODO: mail the admin
372                 if (is_array($babycart))
373                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
374                         HTML($this->getSpamMessage(),
375                              HTML::p(HTML::em(_("SpamAssassin reports: ", 
376                                                 join("\n", $babycart)))));
377                 return true;
378             }
379         }
380         // 3. extract (new) links and check surbl for blocked domains
381         if (ENABLE_SPAMBLOCKLIST and $this->numLinks($newtext)) {
382             include_once("lib/SpamBlocklist.php");
383             include_once("lib/InlineParser.php");
384             $parsed = TransformLinks($newtext);
385             foreach ($parsed->_content as $link) {
386                 if (isa($link, 'Cached_ExternalLink')) {
387                   $uri = $link->_getURL($this->page->getName());
388                   if ($res = IsBlackListed($uri)) {
389                     // TODO: mail the admin
390                     $this->tokens['PAGE_LOCKED_MESSAGE'] = 
391                         HTML($this->getSpamMessage(),
392                              HTML::p(HTML::strong(_("External links contain blocked domains:")),
393                              HTML::ul(HTML::li(sprintf(_("%s is listed at %s"), 
394                                                        $res[2], $res[0])))));
395                     return true;
396                   }
397                 }
398             }
399         }
400
401         return false;
402     }
403
404     /** Number of external links in the wikitext
405      */
406     function numLinks(&$text) {
407         return substr_count($text, "http://") + substr_count($text, "https://");
408     }
409
410     /** Header of the Anti Spam message 
411      */
412     function getSpamMessage () {
413         return
414             HTML(HTML::h2(_("Spam Prevention")),
415                  HTML::p(_("This page edit seems to contain spam and was therefore not saved."),
416                          HTML::br(),
417                          _("Sorry for the inconvenience.")),
418                  HTML::p(""));
419     }
420
421     function getPreview () {
422         include_once('lib/PageType.php');
423         $this->_content = $this->getContent();
424         return new TransformedText($this->page, $this->_content, $this->meta);
425     }
426
427     function getConvertedPreview () {
428         include_once('lib/PageType.php');
429         $this->_content = $this->getContent();
430         $this->meta['markup'] = 2.0;
431         $this->_content = ConvertOldMarkup($this->_content);
432         return new TransformedText($this->page, $this->_content, $this->meta);
433     }
434
435     // possibly convert HTMLAREA content back to Wiki markup
436     function getContent () {
437         if (ENABLE_WYSIWYG) {
438             // don't store everything as html
439             if (!WYSIWYG_DEFAULT_PAGETYPE_HTML) {
440                 $xml_output = $this->WysiwygEdit->ConvertAfter($this->_content);
441                 $this->_content = join("", $xml_output->_content);
442             } else {
443                 $this->meta['pagetype'] = 'html';
444             }
445             return $this->_content;
446         } else {
447             return $this->_content;
448         }
449     }
450
451     function getLockedMessage () {
452         return
453             HTML(HTML::h2(_("Page Locked")),
454                  HTML::p(_("This page has been locked by the administrator so your changes can not be saved.")),
455                  HTML::p(_("(Copy your changes to the clipboard. You can try editing a different page or save your text in a text editor.)")),
456                  HTML::p(_("Sorry for the inconvenience.")));
457     }
458
459     function getConflictMessage ($unresolved = false) {
460         /*
461          xgettext only knows about c/c++ line-continuation strings
462          it does not know about php's dot operator.
463          We want to translate this entire paragraph as one string, of course.
464          */
465
466         //$re_edit_link = Button('edit', _("Edit the new version"), $this->page);
467
468         if ($unresolved)
469             $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.",
470                                 "<<<<<<< ". _("Your version"),
471                                 ">>>>>>> ". _("Other version")));
472         else
473             $message = HTML::p(_("Please check it through before saving."));
474
475
476
477         /*$steps = HTML::ol(HTML::li(_("Copy your changes to the clipboard or to another temporary place (e.g. text editor).")),
478           HTML::li(fmt("%s of the page. You should now see the most current version of the page. Your changes are no longer there.",
479                        $re_edit_link)),
480           HTML::li(_("Make changes to the file again. Paste your additions from the clipboard (or text editor).")),
481           HTML::li(_("Save your updated changes.")));
482         */
483         return
484             HTML(HTML::h2(_("Conflicting Edits!")),
485                  HTML::p(_("In the time since you started editing this page, another user has saved a new version of it.")),
486                  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.")),
487                  $message);
488     }
489
490
491     function getTextArea () {
492         $request = &$this->request;
493
494         $readonly = ! $this->canEdit(); // || $this->isConcurrentUpdate();
495
496         // WYSIWYG will need two pagetypes: raw wikitest and converted html
497         if (ENABLE_WYSIWYG) {
498             $this->_wikicontent = $this->_content;
499             $this->_content = $this->WysiwygEdit->ConvertBefore($this->_content);
500             //                $this->getPreview();
501             //$this->_htmlcontent = $this->_content->asXML();
502         }
503
504         $textarea = HTML::textarea(array('class'=> 'wikiedit',
505                                          'name' => 'edit[content]',
506                                          'id'   => 'edit:content',
507                                          'rows' => $request->getPref('editHeight'),
508                                          'cols' => $request->getPref('editWidth'),
509                                          'readonly' => (bool) $readonly),
510                                    $this->_content);
511         /** <textarea wrap="virtual"> is not valid XHTML but Netscape 4 requires it
512          * to wrap long lines.
513          */
514         if (isBrowserNS4())
515             $textarea->setAttr('wrap', 'virtual');
516         if (ENABLE_WYSIWYG) {
517             return $this->WysiwygEdit->Textarea($textarea, $this->_wikicontent, 
518                                                 $textarea->getAttr('name'));
519         } else
520             return $textarea;
521     }
522
523     function getFormElements () {
524         global $WikiTheme;
525         $request = &$this->request;
526         $page = &$this->page;
527
528         $h = array('action'   => 'edit',
529                    'pagename' => $page->getName(),
530                    'version'  => $this->version,
531                    'edit[pagetype]' => $this->meta['pagetype'],
532                    'edit[current_version]' => $this->_currentVersion);
533
534         $el['HIDDEN_INPUTS'] = HiddenInputs($h);
535         $el['EDIT_TEXTAREA'] = $this->getTextArea();
536         if ( ENABLE_CAPTCHA ) {
537             $el = array_merge($el, $this->Captcha->getFormElements());
538         }
539         $el['SUMMARY_INPUT']
540             = HTML::input(array('type'  => 'text',
541                                 'class' => 'wikitext',
542                                 'id' => 'edit:summary',
543                                 'name'  => 'edit[summary]',
544                                 'size'  => 50,
545                                 'maxlength' => 256,
546                                 'value' => $this->meta['summary']));
547         $el['MINOR_EDIT_CB']
548             = HTML::input(array('type' => 'checkbox',
549                                 'name'  => 'edit[minor_edit]',
550                                 'id' => 'edit:minor_edit',
551                                 'checked' => (bool) $this->meta['is_minor_edit']));
552         $el['OLD_MARKUP_CB']
553             = HTML::input(array('type' => 'checkbox',
554                                 'name' => 'edit[markup]',
555                                 'value' => 'old',
556                                 'checked' => $this->meta['markup'] < 2.0,
557                                 'id' => 'useOldMarkup',
558                                 'onclick' => 'showOldMarkupRules(this.checked)'));
559         $el['OLD_MARKUP_CONVERT'] = ($this->meta['markup'] < 2.0) 
560             ? Button('submit:edit[edit_convert]', _("Convert"), 'wikiaction') : '';
561         $el['LOCKED_CB']
562             = HTML::input(array('type' => 'checkbox',
563                                 'name' => 'edit[locked]',
564                                 'id'   => 'edit:locked',
565                                 'disabled' => (bool) !$this->user->isadmin(),
566                                 'checked'  => (bool) $this->locked));
567
568         $el['PREVIEW_B'] = Button('submit:edit[preview]', _("Preview"),
569                                   'wikiaction');
570
571         //if (!$this->isConcurrentUpdate() && $this->canEdit())
572         $el['SAVE_B'] = Button('submit:edit[save]', _("Save"), 'wikiaction');
573
574         $el['IS_CURRENT'] = $this->version == $this->current->getVersion();
575
576         $el['WIDTH_PREF'] 
577             = HTML::input(array('type'     => 'text',
578                                 'size'     => 3,
579                                 'maxlength'=> 4,
580                                 'class'    => "numeric",
581                                 'name'     => 'pref[editWidth]',
582                                 'id'       => 'pref:editWidth',
583                                 'value'    => $request->getPref('editWidth'),
584                                 'onchange' => 'this.form.submit();'));
585         $el['HEIGHT_PREF'] 
586             = HTML::input(array('type'     => 'text',
587                                 'size'     => 3,
588                                 'maxlength'=> 4,
589                                 'class'    => "numeric",
590                                 'name'     => 'pref[editHeight]',
591                                 'id'       => 'pref:editHeight',
592                                 'value'    => $request->getPref('editHeight'),
593                                 'onchange' => 'this.form.submit();'));
594         $el['SEP'] = $WikiTheme->getButtonSeparator();
595         $el['AUTHOR_MESSAGE'] = fmt("Author will be logged as %s.", 
596                                     HTML::em($this->user->getId()));
597         
598         return $el;
599     }
600
601     function _redirectToBrowsePage() {
602         $this->request->redirect(WikiURL($this->page, false, 'absolute_url'));
603     }
604
605     function _restoreState () {
606         $request = &$this->request;
607
608         $posted = $request->getArg('edit');
609         $request->setArg('edit', false);
610
611         if (!$posted || !$request->isPost()
612             || $request->getArg('action') != 'edit')
613             return false;
614
615         if (!isset($posted['content']) || !is_string($posted['content']))
616             return false;
617         $this->_content = preg_replace('/[ \t\r]+\n/', "\n",
618                                         rtrim($posted['content']));
619         $this->_content = $this->getContent();
620
621         $this->_currentVersion = (int) $posted['current_version'];
622
623         if ($this->_currentVersion < 0)
624             return false;
625         if ($this->_currentVersion > $this->current->getVersion())
626             return false;       // FIXME: some kind of warning?
627
628         $is_old_markup = !empty($posted['markup']) && $posted['markup'] == 'old';
629         $meta['markup'] = $is_old_markup ? false : 2.0;
630         $meta['summary'] = trim(substr($posted['summary'], 0, 256));
631         $meta['is_minor_edit'] = !empty($posted['minor_edit']);
632         $meta['pagetype'] = !empty($posted['pagetype']) ? $posted['pagetype'] : false;
633         if ( ENABLE_CAPTCHA )
634             $meta['captcha_input'] = !empty($posted['captcha_input']) ?
635                 $posted['captcha_input'] : '';
636
637         $this->meta = array_merge($this->meta, $meta);
638         $this->locked = !empty($posted['locked']);
639
640         if (!empty($posted['preview']))
641             $this->editaction = 'preview';
642         elseif (!empty($posted['save']))
643             $this->editaction = 'save';
644         elseif (!empty($posted['edit_convert']))
645             $this->editaction = 'edit_convert';
646         else
647             $this->editaction = 'edit';
648
649         return true;
650     }
651
652     function _initializeState () {
653         $request = &$this->request;
654         $current = &$this->current;
655         $selected = &$this->selected;
656         $user = &$this->user;
657
658         if (!$selected)
659             NoSuchRevision($request, $this->page, $this->version); // noreturn
660
661         $this->_currentVersion = $current->getVersion();
662         $this->_content = $selected->getPackedContent();
663
664         $this->locked = $this->page->get('locked');
665
666         // If author same as previous author, default minor_edit to on.
667         $age = $this->meta['mtime'] - $current->get('mtime');
668         $this->meta['is_minor_edit'] = ( $age < MINOR_EDIT_TIMEOUT
669                                          && $current->get('author') == $user->getId()
670                                          );
671
672         // Default for new pages is new-style markup.
673         if ($selected->hasDefaultContents())
674             $is_new_markup = true;
675         else
676             $is_new_markup = $selected->get('markup') >= 2.0;
677
678         $this->meta['markup'] = $is_new_markup ? 2.0: false;
679         $this->meta['pagetype'] = $selected->get('pagetype');
680         if ($this->meta['pagetype'] == 'wikiblog')
681             $this->meta['summary'] = $selected->get('summary'); // keep blog title
682         else
683             $this->meta['summary'] = '';
684         $this->editaction = 'edit';
685     }
686 }
687
688 class LoadFileConflictPageEditor
689 extends PageEditor
690 {
691     function editPage ($saveFailed = true) {
692         $tokens = &$this->tokens;
693
694         if (!$this->canEdit()) {
695             if ($this->isInitialEdit())
696                 return $this->viewSource();
697             $tokens['PAGE_LOCKED_MESSAGE'] = $this->getLockedMessage();
698         }
699         elseif ($this->editaction == 'save') {
700             if ($this->savePage())
701                 return true;    // Page saved.
702             $saveFailed = true;
703         }
704
705         if ($saveFailed || $this->isConcurrentUpdate())
706         {
707             // Get the text of the original page, and the two conflicting edits
708             // The diff class takes arrays as input.  So retrieve content as
709             // an array, or convert it as necesary.
710             $orig = $this->page->getRevision($this->_currentVersion);
711             $this_content = explode("\n", $this->_content);
712             $other_content = $this->current->getContent();
713             include_once("lib/diff.php");
714             $diff2 = new Diff($other_content, $this_content);
715             $context_lines = max(4, count($other_content) + 1,
716                                  count($this_content) + 1);
717             $fmt = new BlockDiffFormatter($context_lines);
718
719             $this->_content = $fmt->format($diff2);
720             // FIXME: integrate this into class BlockDiffFormatter
721             $this->_content = str_replace(">>>>>>>\n<<<<<<<\n", "=======\n",
722                                           $this->_content);
723             $this->_content = str_replace("<<<<<<<\n>>>>>>>\n", "=======\n",
724                                           $this->_content);
725
726             $this->_currentVersion = $this->current->getVersion();
727             $this->version = $this->_currentVersion;
728             $tokens['CONCURRENT_UPDATE_MESSAGE'] = $this->getConflictMessage();
729         }
730
731         if ($this->editaction == 'edit_convert')
732             $tokens['PREVIEW_CONTENT'] = $this->getConvertedPreview();
733         if ($this->editaction == 'preview')
734             $tokens['PREVIEW_CONTENT'] = $this->getPreview(); // FIXME: convert to _MESSAGE?
735
736         // FIXME: NOT_CURRENT_MESSAGE?
737         $tokens = array_merge($tokens, $this->getFormElements());
738
739         return $this->output('editpage', _("Merge and Edit: %s"));
740         // FIXME: this doesn't display
741     }
742
743     function output ($template, $title_fs) {
744         $selected = &$this->selected;
745         $current = &$this->current;
746
747         if ($selected && $selected->getVersion() != $current->getVersion()) {
748             $rev = $selected;
749             $pagelink = WikiLink($selected);
750         }
751         else {
752             $rev = $current;
753             $pagelink = WikiLink($this->page);
754         }
755
756         //$title = new FormattedText ($title_fs, $pagelink);
757         $template = Template($template, $this->tokens);
758
759         //GeneratePage($template, $title, $rev);
760         PrintXML($template);
761         return true;
762     }
763
764     function getConflictMessage () {
765         $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.",
766                                     "<<<<<<<",
767                                     "======="),
768                                 HTML::p(_("Please check it through before saving."))));
769         return $message;
770     }
771 }
772
773 /**
774  $Log: not supported by cvs2svn $
775  Revision 1.103  2005/10/31 17:09:13  rurban
776  use better constant WYSIWYG_DEFAULT_PAGETYPE_HTML
777
778  Revision 1.102  2005/10/31 16:47:14  rurban
779  enable wysiwyg html converters
780
781  Revision 1.101  2005/10/30 16:12:28  rurban
782  simplify viewsource tokens
783
784  Revision 1.100  2005/10/30 14:20:42  rurban
785  move Captcha specific vars and methods into a Captcha object
786  randomize Captcha chars positions and angles (smoothly)
787
788  Revision 1.99  2005/10/29 08:21:58  rurban
789  ENABLE_SPAMBLOCKLIST:
790    Check for links to blocked external tld domains in new edits, against
791    multi.surbl.org and bl.spamcop.net.
792
793  Revision 1.98  2005/10/10 19:37:04  rurban
794  change USE_HTMLAREA to ENABLE WYSIWYG, add NUM_SPAM_LINKS=20
795
796  Revision 1.97  2005/09/26 06:32:22  rurban
797  [] is forbidden in id tags. Renamed to use :
798
799  Revision 1.96  2005/05/06 17:54:22  rurban
800  silence Preview warnings for PAGE_LOCKED_MESSAGE, CONCURRENT_UPDATE_MESSAGE (thanks to schorni)
801
802  Revision 1.95  2005/04/25 20:17:14  rurban
803  captcha feature by Benjamin Drieu. Patch #1110699
804
805  Revision 1.94  2005/02/28 20:23:31  rurban
806  fix error_stack
807
808  Revision 1.93  2005/02/27 19:31:52  rurban
809  hack: display errorstack without sideeffects (save and restore)
810
811  Revision 1.92  2005/01/29 20:37:21  rurban
812  no edit toolbar at all if ENABLE_EDITTOOLBAR = false
813
814  Revision 1.91  2005/01/25 07:05:49  rurban
815  extract toolbar code, support new tags to get rid of php inside templates
816
817  Revision 1.90  2005/01/22 12:46:15  rurban
818  fix oldmakrup button label
819  update pref[edit*] settings
820
821  Revision 1.89  2005/01/21 14:07:49  rurban
822  reformatting
823
824  Revision 1.88  2004/12/17 16:39:03  rurban
825  minor reformatting
826
827  Revision 1.87  2004/12/16 18:28:05  rurban
828  keep wikiblog summary = page title
829
830  Revision 1.86  2004/12/11 14:50:15  rurban
831  new edit_convert button, to get rid of old markup eventually
832
833  Revision 1.85  2004/12/06 19:49:56  rurban
834  enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
835  renamed delete_page to purge_page.
836  enable action=edit&version=-1 to force creation of a new version.
837  added BABYCART_PATH config
838  fixed magiqc in adodb.inc.php
839  and some more docs
840
841  Revision 1.84  2004/12/04 12:58:26  rurban
842  enable babycart Blog::SpamAssassin module on ENABLE_SPAMASSASSIN=true
843  (currently only for php >= 4.3.0)
844
845  Revision 1.83  2004/12/04 11:55:39  rurban
846  First simple AntiSpam prevention:
847    No more than 20 new http:// links allowed
848
849  Revision 1.82  2004/11/30 22:21:56  rurban
850  changed gif to optimized (pngout) png
851
852  Revision 1.81  2004/11/29 17:57:27  rurban
853  translated pulldown buttons
854
855  Revision 1.80  2004/11/25 17:20:51  rurban
856  and again a couple of more native db args: backlinks
857
858  Revision 1.79  2004/11/21 11:59:20  rurban
859  remove final \n to be ob_cache independent
860
861  Revision 1.78  2004/11/16 17:57:45  rurban
862  fix search&replace button
863  use new addTagButton machinery
864  new showPulldown for categories, TODO: in a seperate request
865
866  Revision 1.77  2004/11/15 15:52:35  rurban
867  improve js stability
868
869  Revision 1.76  2004/11/15 15:37:34  rurban
870  fix JS_SEARCHREPLACE
871    don't use document.write for replace, otherwise self.opener is not defined.
872
873  Revision 1.75  2004/09/16 08:00:52  rurban
874  just some comments
875
876  Revision 1.74  2004/07/03 07:36:28  rurban
877  do not get unneccessary content
878
879  Revision 1.73  2004/06/16 21:23:44  rurban
880  fixed non-object fatal #215
881
882  Revision 1.72  2004/06/14 11:31:37  rurban
883  renamed global $Theme to $WikiTheme (gforge nameclash)
884  inherit PageList default options from PageList
885    default sortby=pagename
886  use options in PageList_Selectable (limit, sortby, ...)
887  added action revert, with button at action=diff
888  added option regex to WikiAdminSearchReplace
889
890  Revision 1.71  2004/06/03 18:06:29  rurban
891  fix file locking issues (only needed on write)
892  fixed immediate LANG and THEME in-session updates if not stored in prefs
893  advanced editpage toolbars (search & replace broken)
894
895  Revision 1.70  2004/06/02 20:47:47  rurban
896  dont use the wikiaction class
897
898  Revision 1.69  2004/06/02 10:17:56  rurban
899  integrated search/replace into toolbar
900  added save+preview buttons
901
902  Revision 1.68  2004/06/01 15:28:00  rurban
903  AdminUser only ADMIN_USER not member of Administrators
904  some RateIt improvements by dfrankow
905  edit_toolbar buttons
906
907  Revision _1.6  2004/05/26 15:48:00  syilek
908  fixed problem with creating page with slashes from one true page
909
910  Revision _1.5  2004/05/25 16:51:53  syilek
911  added ability to create a page from the category page and not have to edit it
912
913  Revision 1.67  2004/05/27 17:49:06  rurban
914  renamed DB_Session to DbSession (in CVS also)
915  added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
916  remove leading slash in error message
917  added force_unlock parameter to File_Passwd (no return on stale locks)
918  fixed adodb session AffectedRows
919  added FileFinder helpers to unify local filenames and DATA_PATH names
920  editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
921
922  Revision 1.66  2004/04/29 23:25:12  rurban
923  re-ordered locale init (as in 1.3.9)
924  fixed loadfile with subpages, and merge/restore anyway
925    (sf.net bug #844188)
926
927  Revision 1.65  2004/04/18 01:11:52  rurban
928  more numeric pagename fixes.
929  fixed action=upload with merge conflict warnings.
930  charset changed from constant to global (dynamic utf-8 switching)
931
932  Revision 1.64  2004/04/06 19:48:56  rurban
933  temp workaround for action=edit AddComment form
934
935  Revision 1.63  2004/03/24 19:39:02  rurban
936  php5 workaround code (plus some interim debugging code in XmlElement)
937    php5 doesn't work yet with the current XmlElement class constructors,
938    WikiUserNew does work better than php4.
939  rewrote WikiUserNew user upgrading to ease php5 update
940  fixed pref handling in WikiUserNew
941  added Email Notification
942  added simple Email verification
943  removed emailVerify userpref subclass: just a email property
944  changed pref binary storage layout: numarray => hash of non default values
945  print optimize message only if really done.
946  forced new cookie policy: delete pref cookies, use only WIKI_ID as plain string.
947    prefs should be stored in db or homepage, besides the current session.
948
949  Revision 1.62  2004/03/17 18:41:05  rurban
950  initial_content and template support for CreatePage
951
952  Revision 1.61  2004/03/12 20:59:17  rurban
953  important cookie fix by Konstantin Zadorozhny
954  new editpage feature: JS_SEARCHREPLACE
955
956  Revision 1.60  2004/02/15 21:34:37  rurban
957  PageList enhanced and improved.
958  fixed new WikiAdmin... plugins
959  editpage, Theme with exp. htmlarea framework
960    (htmlarea yet committed, this is really questionable)
961  WikiUser... code with better session handling for prefs
962  enhanced UserPreferences (again)
963  RecentChanges for show_deleted: how should pages be deleted then?
964
965  Revision 1.59  2003/12/07 20:35:26  carstenklapp
966  Bugfix: Concurrent updates broken since after 1.3.4 release: Fatal
967  error: Call to undefined function: gettransformedcontent() in
968  /home/groups/p/ph/phpwiki/htdocs/phpwiki2/lib/editpage.php on line
969  205.
970
971  Revision 1.58  2003/03/10 18:25:22  dairiki
972  Bug/typo fix.  If you use the edit page to un/lock a page, it
973  failed with: Fatal error: Call to a member function on a
974  non-object in editpage.php on line 136
975
976  Revision 1.57  2003/02/26 03:40:22  dairiki
977  New action=create.  Essentially the same as action=edit, except that if the
978  page already exists, it falls back to action=browse.
979
980  This is for use in the "question mark" links for unknown wiki words
981  to avoid problems and confusion when following links from stale pages.
982  (If the "unknown page" has been created in the interim, the user probably
983  wants to view the page before editing it.)
984
985  Revision 1.56  2003/02/21 18:07:14  dairiki
986  Minor, nitpicky, currently inconsequential changes.
987
988  Revision 1.55  2003/02/21 04:10:58  dairiki
989  Fixes for new cached markup.
990  Some minor code cleanups.
991
992  Revision 1.54  2003/02/16 19:47:16  dairiki
993  Update WikiDB timestamp when editing or deleting pages.
994
995  Revision 1.53  2003/02/15 23:20:27  dairiki
996  Redirect back to browse current version of page upon save,
997  even when no changes were made.
998
999  Revision 1.52  2003/01/03 22:22:00  carstenklapp
1000  Minor adjustments to diff block markers ("<<<<<<<"). Source reformatting.
1001
1002  Revision 1.51  2003/01/03 02:43:26  carstenklapp
1003  New class LoadFileConflictPageEditor, for merging / comparing a loaded
1004  pgsrc file with an existing page.
1005
1006  */
1007
1008 // Local Variables:
1009 // mode: php
1010 // tab-width: 8
1011 // c-basic-offset: 4
1012 // c-hanging-comment-ender-p: nil
1013 // indent-tabs-mode: nil
1014 // End:
1015 ?>