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