xref: /plugin/discussion/action.php (revision f573f8eeab065e06889cce83ec32f22d1a044311)
1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Esther Brunner <wikidesign@gmail.com>
5 */
6
7// must be run within Dokuwiki
8if (!defined('DOKU_INC')) die();
9
10if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
11if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13
14require_once(DOKU_PLUGIN.'action.php');
15
16class action_plugin_discussion extends DokuWiki_Action_Plugin{
17
18    var $avatar = null;
19    var $style = null;
20    var $use_avatar = null;
21
22    function register(&$contr) {
23        $contr->register_hook(
24                'ACTION_ACT_PREPROCESS',
25                'BEFORE',
26                $this,
27                'handle_act_preprocess',
28                array()
29                );
30        $contr->register_hook(
31                'TPL_ACT_RENDER',
32                'AFTER',
33                $this,
34                'comments',
35                array()
36                );
37        $contr->register_hook(
38                'INDEXER_PAGE_ADD',
39                'AFTER',
40                $this,
41                'idx_add_discussion',
42                array()
43                );
44        $contr->register_hook(
45                'INDEXER_VERSION_GET',
46                'BEFORE',
47                $this,
48                'idx_version',
49                array()
50                );
51        $contr->register_hook(
52                'TPL_METAHEADER_OUTPUT',
53                'BEFORE',
54                $this,
55                'handle_tpl_metaheader_output',
56                array()
57                );
58        $contr->register_hook(
59                'TOOLBAR_DEFINE',
60                'AFTER',
61                $this,
62                'handle_toolbar_define',
63                array()
64                );
65        $contr->register_hook(
66                'AJAX_CALL_UNKNOWN',
67                'BEFORE',
68                $this,
69                'handle_ajax_call',
70                array()
71                );
72        $contr->register_hook(
73                'TPL_TOC_RENDER',
74                'BEFORE',
75                $this,
76                'handle_toc_render',
77                array()
78                );
79    }
80
81    /**
82     * Preview Comments
83     *
84     * @author Michael Klier <chi@chimeric.de>
85     */
86    function handle_ajax_call(&$event, $params) {
87        if($event->data != 'discussion_preview') return;
88        $event->preventDefault();
89        $event->stopPropagation();
90        print p_locale_xhtml('preview');
91        print '<div class="comment_preview">';
92        if(!$_SERVER['REMOTE_USER'] && !$this->getConf('allowguests')) {
93            print p_locale_xhtml('denied');
94        } else {
95            print $this->_render($_REQUEST['comment']);
96        }
97        print '</div>';
98    }
99
100    /**
101     * Adds a TOC item if a discussion exists
102     *
103     * @author Michael Klier <chi@chimeric.de>
104     */
105    function handle_toc_render(&$event, $params) {
106        global $ID;
107        global $ACT;
108        if($this->_hasDiscussion($title) && $event->data && $ACT != 'admin') {
109            $tocitem = array( 'hid' => 'discussion__section',
110                              'title' => $this->getLang('discussion'),
111                              'type' => 'ul',
112                              'level' => 1 );
113
114            array_push($event->data, $tocitem);
115        }
116    }
117
118    /**
119     * Modify Tollbar for use with discussion plugin
120     *
121     * @author Michael Klier <chi@chimeric.de>
122     */
123    function handle_toolbar_define(&$event, $param) {
124        global $ACT;
125        if($ACT != 'show') return;
126
127        if($this->_hasDiscussion($title) && $this->getConf('wikisyntaxok')) {
128            $toolbar = array();
129            foreach($event->data as $btn) {
130                if($btn['type'] == 'mediapopup') continue;
131                if($btn['type'] == 'signature') continue;
132                if($btn['type'] == 'linkwiz') continue;
133                if(preg_match("/=+?/", $btn['open'])) continue;
134                array_push($toolbar, $btn);
135            }
136            $event->data = $toolbar;
137        }
138    }
139
140    /**
141     * Dirty workaround to add a toolbar to the discussion plugin
142     *
143     * @author Michael Klier <chi@chimeric.de>
144     */
145    function handle_tpl_metaheader_output(&$event, $param) {
146        global $ACT;
147        global $ID;
148        if($ACT != 'show') return;
149
150        if($this->_hasDiscussion($title) && $this->getConf('wikisyntaxok')) {
151            // FIXME ugly workaround, replace this once DW the toolbar code is more flexible
152            @require_once(DOKU_INC.'inc/toolbar.php');
153            ob_start();
154            print 'NS = "' . getNS($ID) . '";'; // we have to define NS, otherwise we get get JS errors
155            toolbar_JSdefines('toolbar');
156            $script = ob_get_clean();
157            array_push($event->data['script'], array('type' => 'text/javascript', 'charset' => "utf-8", '_data' => $script));
158        }
159    }
160
161    /**
162     * Handles comment actions, dispatches data processing routines
163     */
164    function handle_act_preprocess(&$event, $param) {
165        global $ID;
166        global $INFO;
167        global $conf;
168        global $lang;
169
170        // handle newthread ACTs
171        if ($event->data == 'newthread') {
172            // we can handle it -> prevent others
173            $event->preventDefault();
174            $event->data = $this->_newThread();
175        }
176
177        // enable captchas
178        if (in_array($_REQUEST['comment'], array('add', 'save'))) {
179            if (@file_exists(DOKU_PLUGIN.'captcha/action.php')) {
180                $this->_captchaCheck();
181            }
182            if (@file_exists(DOKU_PLUGIN.'recaptcha/action.php')) {
183                $this->_recaptchaCheck();
184            }
185        }
186
187        // if we are not in show mode or someone wants to unsubscribe, that was all for now
188        if ($event->data != 'show' && $event->data != 'discussion_unsubscribe' && $event->data != 'discussion_confirmsubscribe') return;
189
190        if ($event->data == 'discussion_unsubscribe' or $event->data == 'discussion_confirmsubscribe') {
191            // ok we can handle it prevent others
192            $event->preventDefault();
193
194            if (!isset($_REQUEST['hash'])) {
195                return false;
196            } else {
197                $file = metaFN($ID, '.comments');
198                $data = unserialize(io_readFile($file));
199                $themail = '';
200                foreach($data['subscribers'] as $mail => $info)  {
201                    // convert old style subscribers just in case
202                    if(!is_array($info)) {
203                        $hash = $data['subscribers'][$mail];
204                        $data['subscribers'][$mail]['hash']   = $hash;
205                        $data['subscribers'][$mail]['active'] = true;
206                        $data['subscribers'][$mail]['confirmsent'] = true;
207                    }
208
209                    if ($data['subscribers'][$mail]['hash'] == $_REQUEST['hash']) {
210                        $themail = $mail;
211                    }
212                }
213
214                if($themail != '') {
215                    if($event->data == 'discussion_unsubscribe') {
216                        unset($data['subscribers'][$themail]);
217                        msg(sprintf($lang['unsubscribe_success'], $themail, $ID), 1);
218                    } elseif($event->data == 'discussion_confirmsubscribe') {
219                        $data['subscribers'][$themail]['active'] = true;
220                        msg(sprintf($lang['subscribe_success'], $themail, $ID), 1);
221                    }
222                    io_saveFile($file, serialize($data));
223                    $event->data = 'show';
224                    return true;
225                } else {
226                    return false;
227                }
228            }
229        } else {
230            // do the data processing for comments
231            $cid  = $_REQUEST['cid'];
232            switch ($_REQUEST['comment']) {
233                case 'add':
234                    if(empty($_REQUEST['text'])) return; // don't add empty comments
235                    if(isset($_SERVER['REMOTE_USER']) && !$this->getConf('adminimport')) {
236                        $comment['user']['id'] = $_SERVER['REMOTE_USER'];
237                        $comment['user']['name'] = $INFO['userinfo']['name'];
238                        $comment['user']['mail'] = $INFO['userinfo']['mail'];
239                    } elseif((isset($_SERVER['REMOTE_USER']) && $this->getConf('adminimport') && auth_ismanager()) || !isset($_SERVER['REMOTE_USER'])) {
240                        if(empty($_REQUEST['name']) or empty($_REQUEST['mail'])) return; // don't add anonymous comments
241                        if(!mail_isvalid($_REQUEST['mail'])) {
242                            msg($lang['regbadmail'], -1);
243                            return;
244                        } else {
245                            $comment['user']['id'] = 'test'.hsc($_REQUEST['user']);
246                            $comment['user']['name'] = hsc($_REQUEST['name']);
247                            $comment['user']['mail'] = hsc($_REQUEST['mail']);
248                        }
249                    }
250                    $comment['user']['address'] = ($this->getConf('addressfield')) ? hsc($_REQUEST['address']) : '';
251                    $comment['user']['url'] = ($this->getConf('urlfield')) ? $this->_checkURL($_REQUEST['url']) : '';
252                    $comment['subscribe'] = ($this->getConf('subscribe')) ? $_REQUEST['subscribe'] : '';
253                    $comment['date'] = array('created' => $_REQUEST['date']);
254                    $comment['raw'] = cleanText($_REQUEST['text']);
255                    $repl = $_REQUEST['reply'];
256                    if($this->getConf('moderate') && !auth_ismanager()) {
257                        $comment['show'] = false;
258                    } else {
259                        $comment['show'] = true;
260                    }
261                    $this->_add($comment, $repl);
262                    break;
263
264                case 'save':
265                    $raw  = cleanText($_REQUEST['text']);
266                    $this->_save(array($cid), $raw);
267                    break;
268
269                case 'delete':
270                    $this->_save(array($cid), '');
271                    break;
272
273                case 'toogle':
274                    $this->_save(array($cid), '', 'toogle');
275                    break;
276            }
277        }
278    }
279
280    /**
281     * Main function; dispatches the visual comment actions
282     */
283    function comments(&$event, $param) {
284        if ($event->data != 'show') return; // nothing to do for us
285
286        $cid  = $_REQUEST['cid'];
287        switch ($_REQUEST['comment']) {
288            case 'edit':
289                $this->_show(NULL, $cid);
290                break;
291            default:
292                $this->_show($cid);
293                break;
294        }
295    }
296
297    /**
298     * Redirects browser to given comment anchor
299     */
300    function _redirect($cid) {
301        global $ID;
302        global $ACT;
303
304        if ($ACT !== 'show') return;
305
306        if($this->getConf('moderate') && !auth_ismanager()) {
307            msg($this->getLang('moderation'), 1);
308            @session_start();
309            global $MSG;
310            $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
311            session_write_close();
312            $url = wl($ID);
313        } else {
314            $url = wl($ID) . '#comment_' . $cid;
315        }
316
317        if (function_exists('send_redirect')) {
318            send_redirect($url);
319        } else {
320            header('Location: ' . $url);
321        }
322        exit();
323    }
324
325    /**
326     * Shows all comments of the current page
327     */
328    function _show($reply = NULL, $edit = NULL) {
329        global $ID;
330        global $INFO;
331        global $ACT;
332
333        // get .comments meta file name
334        $file = metaFN($ID, '.comments');
335
336        if (!$INFO['exists']) return;
337        if (!@file_exists($file) && !$this->getConf('automatic')) return false;
338        if (!$_SERVER['REMOTE_USER'] && !$this->getConf('showguests')) return false;
339
340        // load data
341        if (@file_exists($file)) {
342            $data = unserialize(io_readFile($file, false));
343            if (!$data['status']) return false; // comments are turned off
344        } elseif (!@file_exists($file) && $this->getConf('automatic') && $INFO['exists']) {
345            // set status to show the comment form
346            $data['status'] = 1;
347            $data['number'] = 0;
348        }
349
350        // show discussion wrapper only on certain circumstances
351        $cnt = count($data['comments']);
352        $keys = @array_keys($data['comments']);
353        if($cnt > 1 || ($cnt == 1 && $data['comments'][$keys[0]]['show'] == 1) || $this->getConf('allowguests') || isset($_SERVER['REMOTE_USER'])) {
354            $show = true;
355            // section title
356            $title = ($data['title'] ? hsc($data['title']) : $this->getLang('discussion'));
357            ptln('<div class="comment_wrapper" id="comment_wrapper">'); // the id value is used for visibility toggling the section
358            ptln('<h2><a name="discussion__section" id="discussion__section">', 2);
359            ptln($title, 4);
360            ptln('</a></h2>', 2);
361            ptln('<div class="level2 hfeed">', 2);
362        }
363
364        // now display the comments
365        if (isset($data['comments'])) {
366            if (!$this->getConf('usethreading')) {
367                $data['comments'] = $this->_flattenThreads($data['comments']);
368                uasort($data['comments'], '_sortCallBack');
369            }
370            if($this->getConf('newestfirst')) {
371                $data['comments'] = array_reverse($data['comments']);
372            }
373            foreach ($data['comments'] as $key => $value) {
374                if ($key == $edit) $this->_form($value['raw'], 'save', $edit); // edit form
375                else $this->_print($key, $data, '', $reply);
376            }
377        }
378
379        // comment form
380        if (($data['status'] == 1) && (!$reply || !$this->getConf('usethreading')) && !$edit) $this->_form('');
381
382        if($show) {
383            ptln('</div>', 2); // level2 hfeed
384            ptln('</div>'); // comment_wrapper
385        }
386
387        // check for toggle print configuration
388        if($this->getConf('visibilityButton')) {
389            // print the hide/show discussion section button
390            $this->_print_toggle_button();
391        }
392
393        return true;
394    }
395
396    function _flattenThreads($comments, $keys = null) {
397        if (is_null($keys))
398            $keys = array_keys($comments);
399
400        foreach($keys as $cid) {
401            if (!empty($comments[$cid]['replies'])) {
402                $rids = $comments[$cid]['replies'];
403                $comments = $this->_flattenThreads($comments, $rids);
404                $comments[$cid]['replies'] = array();
405            }
406            $comments[$cid]['parent'] = '';
407        }
408        return $comments;
409    }
410
411    /**
412     * Adds a new comment and then displays all comments
413     */
414    function _add($comment, $parent) {
415        global $lang;
416        global $ID;
417        global $TEXT;
418
419        $otxt = $TEXT; // set $TEXT to comment text for wordblock check
420        $TEXT = $comment['raw'];
421
422        // spamcheck against the DokuWiki blacklist
423        if (checkwordblock()) {
424            msg($this->getLang('wordblock'), -1);
425            return false;
426        }
427
428        if ((!$this->getConf('allowguests'))
429                && ($comment['user']['id'] != $_SERVER['REMOTE_USER']))
430            return false; // guest comments not allowed
431
432        $TEXT = $otxt; // restore global $TEXT
433
434        // get discussion meta file name
435        $file = metaFN($ID, '.comments');
436
437        // create comments file if it doesn't exist yet
438        if(!@file_exists($file)) {
439            $data = array('status' => 1, 'number' => 0);
440            io_saveFile($file, serialize($data));
441        } else {
442            $data = array();
443            $data = unserialize(io_readFile($file, false));
444            if ($data['status'] != 1) return false; // comments off or closed
445        }
446
447        if ($comment['date']['created']) {
448            $date = strtotime($comment['date']['created']);
449        } else {
450            $date = time();
451        }
452
453        if ($date == -1) {
454            $date = time();
455        }
456
457        $cid  = md5($comment['user']['id'].$date); // create a unique id
458
459        if (!is_array($data['comments'][$parent])) {
460            $parent = NULL; // invalid parent comment
461        }
462
463        // render the comment
464        $xhtml = $this->_render($comment['raw']);
465
466        // fill in the new comment
467        $data['comments'][$cid] = array(
468                'user'    => $comment['user'],
469                'date'    => array('created' => $date),
470                'show'    => true,
471                'raw'     => $comment['raw'],
472                'xhtml'   => $xhtml,
473                'parent'  => $parent,
474                'replies' => array(),
475                'show'    => $comment['show']
476                );
477
478        if($comment['subscribe']) {
479            $mail = $comment['user']['mail'];
480            if($data['subscribers']) {
481                if(!$data['subscribers'][$mail]) {
482                    $data['subscribers'][$mail]['hash'] = md5($mail . mt_rand());
483                    $data['subscribers'][$mail]['active'] = false;
484                    $data['subscribers'][$mail]['confirmsent'] = false;
485                } else {
486                    // convert old style subscribers and set them active
487                    if(!is_array($data['subscribers'][$mail])) {
488                        $hash = $data['subscribers'][$mail];
489                        $data['subscribers'][$mail]['hash'] = $hash;
490                        $data['subscribers'][$mail]['active'] = true;
491                        $data['subscribers'][$mail]['confirmsent'] = true;
492                    }
493                }
494            } else {
495                $data['subscribers'][$mail]['hash']   = md5($mail . mt_rand());
496                $data['subscribers'][$mail]['active'] = false;
497                $data['subscribers'][$mail]['confirmsent'] = false;
498            }
499        }
500
501        // update parent comment
502        if ($parent) $data['comments'][$parent]['replies'][] = $cid;
503
504        // update the number of comments
505        $data['number']++;
506
507        // notify subscribers of the page
508        $data['comments'][$cid]['cid'] = $cid;
509        $this->_notify($data['comments'][$cid], $data['subscribers']);
510
511        // save the comment metadata file
512        io_saveFile($file, serialize($data));
513        $this->_addLogEntry($date, $ID, 'cc', '', $cid);
514
515        $this->_redirect($cid);
516        return true;
517    }
518
519    /**
520     * Saves the comment with the given ID and then displays all comments
521     */
522    function _save($cids, $raw, $act = NULL) {
523        global $ID;
524
525        if(!$cids) return; // do nothing if we get no comment id
526
527        if ($raw) {
528            global $TEXT;
529
530            $otxt = $TEXT; // set $TEXT to comment text for wordblock check
531            $TEXT = $raw;
532
533            // spamcheck against the DokuWiki blacklist
534            if (checkwordblock()) {
535                msg($this->getLang('wordblock'), -1);
536                return false;
537            }
538
539            $TEXT = $otxt; // restore global $TEXT
540        }
541
542        // get discussion meta file name
543        $file = metaFN($ID, '.comments');
544        $data = unserialize(io_readFile($file, false));
545
546        if (!is_array($cids)) $cids = array($cids);
547        foreach ($cids as $cid) {
548
549            if (is_array($data['comments'][$cid]['user'])) {
550                $user    = $data['comments'][$cid]['user']['id'];
551                $convert = false;
552            } else {
553                $user    = $data['comments'][$cid]['user'];
554                $convert = true;
555            }
556
557            // someone else was trying to edit our comment -> abort
558            if (($user != $_SERVER['REMOTE_USER']) && (!auth_ismanager())) return false;
559
560            $date = time();
561
562            // need to convert to new format?
563            if ($convert) {
564                $data['comments'][$cid]['user'] = array(
565                        'id'      => $user,
566                        'name'    => $data['comments'][$cid]['name'],
567                        'mail'    => $data['comments'][$cid]['mail'],
568                        'url'     => $data['comments'][$cid]['url'],
569                        'address' => $data['comments'][$cid]['address'],
570                        );
571                $data['comments'][$cid]['date'] = array(
572                        'created' => $data['comments'][$cid]['date']
573                        );
574            }
575
576            if ($act == 'toogle') {     // toogle visibility
577                $now = $data['comments'][$cid]['show'];
578                $data['comments'][$cid]['show'] = !$now;
579                $data['number'] = $this->_count($data);
580
581                $type = ($data['comments'][$cid]['show'] ? 'sc' : 'hc');
582
583            } elseif ($act == 'show') { // show comment
584                $data['comments'][$cid]['show'] = true;
585                $data['number'] = $this->_count($data);
586
587                $type = 'sc'; // show comment
588
589            } elseif ($act == 'hide') { // hide comment
590                $data['comments'][$cid]['show'] = false;
591                $data['number'] = $this->_count($data);
592
593                $type = 'hc'; // hide comment
594
595            } elseif (!$raw) {          // remove the comment
596                $data['comments'] = $this->_removeComment($cid, $data['comments']);
597                $data['number'] = $this->_count($data);
598
599                $type = 'dc'; // delete comment
600
601            } else {                   // save changed comment
602                $xhtml = $this->_render($raw);
603
604                // now change the comment's content
605                $data['comments'][$cid]['date']['modified'] = $date;
606                $data['comments'][$cid]['raw']              = $raw;
607                $data['comments'][$cid]['xhtml']            = $xhtml;
608
609                $type = 'ec'; // edit comment
610            }
611        }
612
613        // save the comment metadata file
614        io_saveFile($file, serialize($data));
615        $this->_addLogEntry($date, $ID, $type, '', $cid);
616
617        $this->_redirect($cid);
618        return true;
619    }
620
621    /**
622     * Recursive function to remove a comment
623     */
624    function _removeComment($cid, $comments) {
625        if (is_array($comments[$cid]['replies'])) {
626            foreach ($comments[$cid]['replies'] as $rid) {
627                $comments = $this->_removeComment($rid, $comments);
628            }
629        }
630        unset($comments[$cid]);
631        return $comments;
632    }
633
634    /**
635     * Prints an individual comment
636     */
637    function _print($cid, &$data, $parent = '', $reply = '', $visible = true) {
638
639        if (!isset($data['comments'][$cid])) return false; // comment was removed
640        $comment = $data['comments'][$cid];
641
642        if (!is_array($comment)) return false;             // corrupt datatype
643
644        if ($comment['parent'] != $parent) return true;    // reply to an other comment
645
646        if (!$comment['show']) {                            // comment hidden
647            if (auth_ismanager()) $hidden = ' comment_hidden';
648            else return true;
649        } else {
650            $hidden = '';
651        }
652
653        // print the actual comment
654        $this->_print_comment($cid, $data, $parent, $reply, $visible, $hidden);
655        // replies to this comment entry?
656        $this->_print_replies($cid, $data, $reply, $visible);
657        // reply form
658        $this->_print_form($cid, $reply);
659
660    }
661
662    function _print_comment($cid, &$data, $parent, $reply, $visible, $hidden)
663    {
664        global $conf, $lang, $ID, $HIGH;
665        $comment = $data['comments'][$cid];
666
667        // comment head with date and user data
668        ptln('<div class="hentry'.$hidden.'">', 4);
669        ptln('<div class="comment_head">', 6);
670        ptln('<a name="comment_'.$cid.'" id="comment_'.$cid.'"></a>', 8);
671        $head = '<span class="vcard author">';
672
673        // prepare variables
674        if (is_array($comment['user'])) { // new format
675            $user    = $comment['user']['id'];
676            $name    = $comment['user']['name'];
677            $mail    = $comment['user']['mail'];
678            $url     = $comment['user']['url'];
679            $address = $comment['user']['address'];
680        } else {                         // old format
681            $user    = $comment['user'];
682            $name    = $comment['name'];
683            $mail    = $comment['mail'];
684            $url     = $comment['url'];
685            $address = $comment['address'];
686        }
687        if (is_array($comment['date'])) { // new format
688            $created  = $comment['date']['created'];
689            $modified = $comment['date']['modified'];
690        } else {                         // old format
691            $created  = $comment['date'];
692            $modified = $comment['edited'];
693        }
694
695        // show username or real name?
696        if ((!$this->getConf('userealname')) && ($user)) {
697            $showname = $user;
698        } else {
699            $showname = $name;
700        }
701
702        // show avatar image?
703        if ($this->_use_avatar()) {
704            $user_data['name'] = $name;
705            $user_data['user'] = $user;
706            $user_data['mail'] = $mail;
707            $avatar = $this->avatar->getXHTML($user_data, $name, 'left');
708            if($avatar) $head .= $avatar;
709        }
710
711        if ($this->getConf('linkemail') && $mail) {
712            $head .= $this->email($mail, $showname, 'email fn');
713        } elseif ($url) {
714            $head .= $this->external_link($this->_checkURL($url), $showname, 'urlextern url fn');
715        } else {
716            $head .= '<span class="fn">'.$showname.'</span>';
717        }
718
719        if ($address) $head .= ', <span class="adr">'.$address.'</span>';
720        $head .= '</span>, '.
721            '<abbr class="published" title="'. strftime('%Y-%m-%dT%H:%M:%SZ', $created) .'">'.
722            dformat($created, $conf['dformat']).'</abbr>';
723        if ($comment['edited']) $head .= ' (<abbr class="updated" title="'.
724                strftime('%Y-%m-%dT%H:%M:%SZ', $modified).'">'.dformat($modified, $conf['dformat']).
725                '</abbr>)';
726        ptln($head, 8);
727        ptln('</div>', 6); // class="comment_head"
728
729        // main comment content
730        ptln('<div class="comment_body entry-content"'.
731                ($this->getConf('useavatar') ? $this->_get_style() : '').'>', 6);
732        echo ($HIGH?html_hilight($comment['xhtml'],$HIGH):$comment['xhtml']).DOKU_LF;
733        ptln('</div>', 6); // class="comment_body"
734
735        if ($visible) {
736            ptln('<div class="comment_buttons">', 6);
737
738            // show reply button?
739            if (($data['status'] == 1) && !$reply && $comment['show']
740                    && ($this->getConf('allowguests') || $_SERVER['REMOTE_USER']) && $this->getConf('usethreading'))
741                $this->_button($cid, $this->getLang('btn_reply'), 'reply', true);
742
743            // show edit, show/hide and delete button?
744            if ((($user == $_SERVER['REMOTE_USER']) && ($user != '')) || (auth_ismanager())) {
745                $this->_button($cid, $lang['btn_secedit'], 'edit', true);
746                $label = ($comment['show'] ? $this->getLang('btn_hide') : $this->getLang('btn_show'));
747                $this->_button($cid, $label, 'toogle');
748                $this->_button($cid, $lang['btn_delete'], 'delete');
749            }
750            ptln('</div>', 6); // class="comment_buttons"
751        }
752        ptln('</div>', 4); // class="hentry"
753    }
754
755    function _print_form($cid, $reply)
756    {
757        if ($this->getConf('usethreading') && $reply == $cid) {
758            ptln('<div class="comment_replies">', 4);
759            $this->_form('', 'add', $cid);
760            ptln('</div>', 4); // class="comment_replies"
761        }
762    }
763
764    function _print_replies($cid, &$data, $reply, &$visible)
765    {
766        $comment = $data['comments'][$cid];
767        if (!count($comment['replies'])) {
768            return;
769        }
770        ptln('<div class="comment_replies"'.$this->_get_style().'>', 4);
771        $visible = ($comment['show'] && $visible);
772        foreach ($comment['replies'] as $rid) {
773            $this->_print($rid, $data, $cid, $reply, $visible);
774        }
775        ptln('</div>', 4);
776    }
777
778    function _use_avatar()
779    {
780        if (is_null($this->use_avatar)) {
781            $this->use_avatar = $this->getConf('useavatar')
782                    && (!plugin_isdisabled('avatar'))
783                    && ($this->avatar =& plugin_load('helper', 'avatar'));
784        }
785        return $this->use_avatar;
786    }
787
788    function _get_style()
789    {
790        if (is_null($this->style)){
791            if ($this->_use_avatar()) {
792                $this->style = ' style="margin-left: '.($this->avatar->getConf('size') + 14).'px;"';
793            } else {
794                $this->style = ' style="margin-left: 20px;"';
795            }
796        }
797        return $this->style;
798    }
799
800    /**
801     * Show the button which toggle the visibility of the discussion section
802     */
803    function _print_toggle_button() {
804        ptln('<div id="toggle_button" class="toggle_button" style="text-align: right;">');
805        ptln('<input type="submit" id="discussion__btn_toggle_visibility" title="Toggle Visibiliy" class="button" value="Hide/Show">');
806        ptln('</div>');
807    }
808
809    /**
810     * Outputs the comment form
811     */
812    function _form($raw = '', $act = 'add', $cid = NULL) {
813        global $lang;
814        global $conf;
815        global $ID;
816        global $INFO;
817
818        // not for unregistered users when guest comments aren't allowed
819        if (!$_SERVER['REMOTE_USER'] && !$this->getConf('allowguests')) {
820            ?>
821            <div class="comment_form">
822                <?php echo $this->getLang('noguests'); ?>
823            </div>
824            <?php
825            return false;
826        }
827
828        // fill $raw with $_REQUEST['text'] if it's empty (for failed CAPTCHA check)
829        if (!$raw && ($_REQUEST['comment'] == 'show')) $raw = $_REQUEST['text'];
830        ?>
831
832        <div class="comment_form">
833          <form id="discussion__comment_form" method="post" action="<?php echo script() ?>" accept-charset="<?php echo $lang['encoding'] ?>">
834            <div class="no">
835              <input type="hidden" name="id" value="<?php echo $ID ?>" />
836              <input type="hidden" name="do" value="show" />
837              <input type="hidden" name="comment" value="<?php echo $act ?>" />
838        <?php
839        // for adding a comment
840        if ($act == 'add') {
841        ?>
842              <input type="hidden" name="reply" value="<?php echo $cid ?>" />
843        <?php
844        // for guest/adminimport: show name, e-mail and subscribe to comments fields
845        if(!$_SERVER['REMOTE_USER'] or ($this->getConf('adminimport') && auth_ismanager())) {
846        ?>
847              <input type="hidden" name="user" value="<?php echo clientIP() ?>" />
848              <div class="comment_name">
849                <label class="block" for="discussion__comment_name">
850                  <span><?php echo $lang['fullname'] ?>:</span>
851                  <input type="text" class="edit<?php if($_REQUEST['comment'] == 'add' && empty($_REQUEST['name'])) echo ' error'?>" name="name" id="discussion__comment_name" size="50" tabindex="1" value="<?php echo hsc($_REQUEST['name'])?>" />
852                </label>
853              </div>
854              <div class="comment_mail">
855                <label class="block" for="discussion__comment_mail">
856                  <span><?php echo $lang['email'] ?>:</span>
857                  <input type="text" class="edit<?php if($_REQUEST['comment'] == 'add' && empty($_REQUEST['mail'])) echo ' error'?>" name="mail" id="discussion__comment_mail" size="50" tabindex="2" value="<?php echo hsc($_REQUEST['mail'])?>" />
858                </label>
859              </div>
860        <?php
861        }
862
863        // allow entering an URL
864        if ($this->getConf('urlfield')) {
865        ?>
866              <div class="comment_url">
867                <label class="block" for="discussion__comment_url">
868                  <span><?php echo $this->getLang('url') ?>:</span>
869                  <input type="text" class="edit" name="url" id="discussion__comment_url" size="50" tabindex="3" value="<?php echo hsc($_REQUEST['url'])?>" />
870                </label>
871              </div>
872        <?php
873        }
874
875        // allow entering an address
876        if ($this->getConf('addressfield')) {
877        ?>
878              <div class="comment_address">
879                <label class="block" for="discussion__comment_address">
880                  <span><?php echo $this->getLang('address') ?>:</span>
881                  <input type="text" class="edit" name="address" id="discussion__comment_address" size="50" tabindex="4" value="<?php echo hsc($_REQUEST['address'])?>" />
882                </label>
883              </div>
884        <?php
885        }
886
887        // allow setting the comment date
888        if ($this->getConf('adminimport') && (auth_ismanager())) {
889        ?>
890              <div class="comment_date">
891                <label class="block" for="discussion__comment_date">
892                  <span><?php echo $this->getLang('date') ?>:</span>
893                  <input type="text" class="edit" name="date" id="discussion__comment_date" size="50" />
894                </label>
895              </div>
896        <?php
897        }
898
899        // for saving a comment
900        } else {
901        ?>
902              <input type="hidden" name="cid" value="<?php echo $cid ?>" />
903        <?php
904        }
905        ?>
906                <div class="comment_text">
907                  <?php echo $this->getLang('entercomment'); echo ($this->getConf('wikisyntaxok') ? "" : ":");
908                        if($this->getConf('wikisyntaxok')) echo '. ' . $this->getLang('wikisyntax') . ':'; ?>
909
910                  <!-- Fix for disable the toolbar when wikisyntaxok is set to false. See discussion's script.jss -->
911                  <?php if($this->getConf('wikisyntaxok')) { ?>
912                    <div id="discussion__comment_toolbar">
913                  <?php } else { ?>
914                    <div id="discussion__comment_toolbar_disabled">
915                  <?php } ?>
916                </div>
917                <textarea class="edit<?php if($_REQUEST['comment'] == 'add' && empty($_REQUEST['text'])) echo ' error'?>" name="text" cols="80" rows="10" id="discussion__comment_text" tabindex="5"><?php
918                  if($raw) {
919                      echo formText($raw);
920                  } else {
921                      echo $_REQUEST['text'];
922                  }
923                ?></textarea>
924              </div>
925        <?php //bad and dirty event insert hook
926        $evdata = array('writable' => true);
927        trigger_event('HTML_EDITFORM_INJECTION', $evdata);
928        ?>
929              <input class="button comment_submit" id="discussion__btn_submit" type="submit" name="submit" accesskey="s" value="<?php echo $lang['btn_save'] ?>" title="<?php echo $lang['btn_save']?> [S]" tabindex="7" />
930              <input class="button comment_preview_button" id="discussion__btn_preview" type="button" name="preview" accesskey="p" value="<?php echo $lang['btn_preview'] ?>" title="<?php echo $lang['btn_preview']?> [P]" />
931
932        <?php if((!$_SERVER['REMOTE_USER'] || $_SERVER['REMOTE_USER'] && !$conf['subscribers']) && $this->getConf('subscribe')) { ?>
933              <div class="comment_subscribe">
934                <input type="checkbox" id="discussion__comment_subscribe" name="subscribe" tabindex="6" />
935                <label class="block" for="discussion__comment_subscribe">
936                  <span><?php echo $this->getLang('subscribe') ?></span>
937                </label>
938              </div>
939        <?php } ?>
940
941              <div class="clearer"></div>
942              <div id="discussion__comment_preview">&nbsp;</div>
943            </div>
944          </form>
945        </div>
946        <?php
947        if ($this->getConf('usecocomment')) echo $this->_coComment();
948    }
949
950    /**
951     * Adds a javascript to interact with coComments
952     */
953    function _coComment() {
954        global $ID;
955        global $conf;
956        global $INFO;
957
958        $user = $_SERVER['REMOTE_USER'];
959
960        ?>
961        <script type="text/javascript"><!--//--><![CDATA[//><!--
962          var blogTool  = "DokuWiki";
963          var blogURL   = "<?php echo DOKU_URL ?>";
964          var blogTitle = "<?php echo $conf['title'] ?>";
965          var postURL   = "<?php echo wl($ID, '', true) ?>";
966          var postTitle = "<?php echo tpl_pagetitle($ID, true) ?>";
967        <?php
968        if ($user) {
969        ?>
970          var commentAuthor = "<?php echo $INFO['userinfo']['name'] ?>";
971        <?php
972        } else {
973        ?>
974          var commentAuthorFieldName = "name";
975        <?php
976        }
977        ?>
978          var commentAuthorLoggedIn = <?php echo ($user ? 'true' : 'false') ?>;
979          var commentFormID         = "discussion__comment_form";
980          var commentTextFieldName  = "text";
981          var commentButtonName     = "submit";
982          var cocomment_force       = false;
983        //--><!]]></script>
984        <script type="text/javascript" src="http://www.cocomment.com/js/cocomment.js">
985        </script>
986        <?php
987    }
988
989    /**
990     * General button function
991     */
992    function _button($cid, $label, $act, $jump = false) {
993        global $ID;
994
995        $anchor = ($jump ? '#discussion__comment_form' : '' );
996
997        ?>
998        <form class="button discussion__<?php echo $act?>" method="get" action="<?php echo script().$anchor ?>">
999          <div class="no">
1000            <input type="hidden" name="id" value="<?php echo $ID ?>" />
1001            <input type="hidden" name="do" value="show" />
1002            <input type="hidden" name="comment" value="<?php echo $act ?>" />
1003            <input type="hidden" name="cid" value="<?php echo $cid ?>" />
1004            <input type="submit" value="<?php echo $label ?>" class="button" title="<?php echo $label ?>" />
1005          </div>
1006        </form>
1007        <?php
1008        return true;
1009    }
1010
1011    /**
1012     * Adds an entry to the comments changelog
1013     *
1014     * @author Esther Brunner <wikidesign@gmail.com>
1015     * @author Ben Coburn <btcoburn@silicodon.net>
1016     */
1017    function _addLogEntry($date, $id, $type = 'cc', $summary = '', $extra = '') {
1018        global $conf;
1019
1020        $changelog = $conf['metadir'].'/_comments.changes';
1021
1022        if(!$date) $date = time(); //use current time if none supplied
1023        $remote = $_SERVER['REMOTE_ADDR'];
1024        $user   = $_SERVER['REMOTE_USER'];
1025
1026        $strip = array("\t", "\n");
1027        $logline = array(
1028                'date'  => $date,
1029                'ip'    => $remote,
1030                'type'  => str_replace($strip, '', $type),
1031                'id'    => $id,
1032                'user'  => $user,
1033                'sum'   => str_replace($strip, '', $summary),
1034                'extra' => str_replace($strip, '', $extra)
1035                );
1036
1037        // add changelog line
1038        $logline = implode("\t", $logline)."\n";
1039        io_saveFile($changelog, $logline, true); //global changelog cache
1040        $this->_trimRecentCommentsLog($changelog);
1041
1042        // tell the indexer to re-index the page
1043        @unlink(metaFN($id, '.indexed'));
1044    }
1045
1046    /**
1047     * Trims the recent comments cache to the last $conf['changes_days'] recent
1048     * changes or $conf['recent'] items, which ever is larger.
1049     * The trimming is only done once a day.
1050     *
1051     * @author Ben Coburn <btcoburn@silicodon.net>
1052     */
1053    function _trimRecentCommentsLog($changelog) {
1054        global $conf;
1055
1056        if (@file_exists($changelog) &&
1057                (filectime($changelog) + 86400) < time() &&
1058                !@file_exists($changelog.'_tmp')) {
1059
1060            io_lock($changelog);
1061            $lines = file($changelog);
1062            if (count($lines)<$conf['recent']) {
1063                // nothing to trim
1064                io_unlock($changelog);
1065                return true;
1066            }
1067
1068            io_saveFile($changelog.'_tmp', '');                  // presave tmp as 2nd lock
1069            $trim_time = time() - $conf['recent_days']*86400;
1070            $out_lines = array();
1071
1072            $num = count($lines);
1073            for ($i=0; $i<$num; $i++) {
1074                $log = parseChangelogLine($lines[$i]);
1075                if ($log === false) continue;                      // discard junk
1076                if ($log['date'] < $trim_time) {
1077                    $old_lines[$log['date'].".$i"] = $lines[$i];     // keep old lines for now (append .$i to prevent key collisions)
1078                } else {
1079                    $out_lines[$log['date'].".$i"] = $lines[$i];     // definitely keep these lines
1080                }
1081            }
1082
1083            // sort the final result, it shouldn't be necessary,
1084            // however the extra robustness in making the changelog cache self-correcting is worth it
1085            ksort($out_lines);
1086            $extra = $conf['recent'] - count($out_lines);        // do we need extra lines do bring us up to minimum
1087            if ($extra > 0) {
1088                ksort($old_lines);
1089                $out_lines = array_merge(array_slice($old_lines,-$extra),$out_lines);
1090            }
1091
1092            // save trimmed changelog
1093            io_saveFile($changelog.'_tmp', implode('', $out_lines));
1094            @unlink($changelog);
1095            if (!rename($changelog.'_tmp', $changelog)) {
1096                // rename failed so try another way...
1097                io_unlock($changelog);
1098                io_saveFile($changelog, implode('', $out_lines));
1099                @unlink($changelog.'_tmp');
1100            } else {
1101                io_unlock($changelog);
1102            }
1103            return true;
1104        }
1105    }
1106
1107    /**
1108     * Sends a notify mail on new comment
1109     *
1110     * @param  array  $comment  data array of the new comment
1111     *
1112     * @author Andreas Gohr <andi@splitbrain.org>
1113     * @author Esther Brunner <wikidesign@gmail.com>
1114     */
1115    function _notify($comment, &$subscribers) {
1116        global $conf;
1117        global $ID;
1118        global $INFO;
1119
1120        $notify_text = io_readfile($this->localfn('subscribermail'));
1121        $confirm_text = io_readfile($this->localfn('confirmsubscribe'));
1122        $subject_notify = '['.$conf['title'].'] '.$this->getLang('mail_newcomment');
1123        $subject_subscribe = '['.$conf['title'].'] '.$this->getLang('subscribe');
1124        $from = $conf['mailfrom'];
1125        $from = str_replace('@USER@',$_SERVER['REMOTE_USER'],$from);
1126        $from = str_replace('@NAME@',$INFO['userinfo']['name'],$from);
1127        $from = str_replace('@MAIL@',$INFO['userinfo']['mail'],$from);
1128
1129        $search = array(
1130                '@PAGE@',
1131                '@TITLE@',
1132                '@DATE@',
1133                '@NAME@',
1134                '@TEXT@',
1135                '@COMMENTURL@',
1136                '@UNSUBSCRIBE@',
1137                '@DOKUWIKIURL@',
1138        );
1139
1140        // prepare email body
1141        if($conf['notify'] || $conf['subscribers']) {
1142            $replace = array(
1143                    $ID,
1144                    $conf['title'],
1145                    dformat($comment['date']['created'], $conf['dformat']),
1146                    $comment['user']['name'],
1147                    $comment['raw'],
1148                    wl($ID, '', true) . '#comment_' . $comment['cid'],
1149                    wl($ID, 'do=unsubscribe', true, '&'),
1150                    DOKU_URL,
1151                    );
1152            $body = str_replace($search, $replace, $notify_text);
1153        }
1154
1155        // send mail to notify address
1156        if ($conf['notify']) {
1157            $to = $conf['notify'];
1158            mail_send($to, $subject_notify, $body, $from, '', '');
1159        }
1160
1161        // notify page subscribers
1162        if ($conf['subscribers']) {
1163            $to   = ''; // put all recipients in bcc field
1164            $data = array('id' => $ID, 'addresslist' => '', 'self' => false);
1165            trigger_event('COMMON_NOTIFY_ADDRESSLIST', $data, 'subscription_addresslist');
1166            $bcc = $data['addresslist'];
1167            mail_send($to, $subject_notify, $body, $from, '', $bcc);
1168        }
1169
1170        // notify comment subscribers
1171        if (!empty($subscribers)) {
1172
1173            foreach($subscribers as $mail => $data) {
1174                $to = $mail;
1175
1176                if($data['active']) {
1177                    $replace = array(
1178                            $ID,
1179                            $conf['title'],
1180                            dformat($comment['date']['created'], $conf['dformat']),
1181                            $comment['user']['name'],
1182                            $comment['raw'],
1183                            wl($ID, '', true) . '#comment_' . $comment['cid'],
1184                            wl($ID, 'do=discussion_unsubscribe&hash=' . $data['hash'], true, '&'),
1185                            DOKU_URL,
1186                            );
1187
1188                    $body = str_replace($search, $replace, $notify_text);
1189                    mail_send($to, $subject_notify, $body, $from);
1190                } elseif(!$data['active'] && !$data['confirmsent']) {
1191                    $search = array(
1192                            '@PAGE@',
1193                            '@TITLE@',
1194                            '@SUBSCRIBE@',
1195                            '@DOKUWIKIURL@',
1196                            );
1197                    $replace = array(
1198                            $ID,
1199                            $conf['title'],
1200                            wl($ID, 'do=discussion_confirmsubscribe&hash=' . $data['hash'], true, '&'),
1201                            DOKU_URL,
1202                            );
1203
1204                    $body = str_replace($search, $replace, $confirm_text);
1205                    mail_send($to, $subject_subscribe, $body, $from);
1206                    $subscribers[$mail]['confirmsent'] = true;
1207                }
1208            }
1209        }
1210    }
1211
1212    /**
1213     * Counts the number of visible comments
1214     */
1215    function _count($data) {
1216        $number = 0;
1217        foreach ($data['comments'] as $cid => $comment) {
1218            if ($comment['parent']) continue;
1219            if (!$comment['show']) continue;
1220            $number++;
1221            $rids = $comment['replies'];
1222            if (count($rids)) $number = $number + $this->_countReplies($data, $rids);
1223        }
1224        return $number;
1225    }
1226
1227    function _countReplies(&$data, $rids) {
1228        $number = 0;
1229        foreach ($rids as $rid) {
1230            if (!isset($data['comments'][$rid])) continue; // reply was removed
1231            if (!$data['comments'][$rid]['show']) continue;
1232            $number++;
1233            $rids = $data['comments'][$rid]['replies'];
1234            if (count($rids)) $number = $number + $this->_countReplies($data, $rids);
1235        }
1236        return $number;
1237    }
1238
1239    /**
1240     * Renders the comment text
1241     */
1242    function _render($raw) {
1243        if ($this->getConf('wikisyntaxok')) {
1244            $xhtml = $this->render($raw);
1245        } else { // wiki syntax not allowed -> just encode special chars
1246            $xhtml = hsc(trim($raw));
1247            $xhtml = str_replace("\n", '<br />', $xhtml);
1248        }
1249        return $xhtml;
1250    }
1251
1252    /**
1253     * Finds out whether there is a discussion section for the current page
1254     */
1255    function _hasDiscussion(&$title) {
1256        global $ID;
1257
1258        $cfile = metaFN($ID, '.comments');
1259
1260        if (!@file_exists($cfile)) {
1261            if ($this->getConf('automatic')) {
1262                return true;
1263            } else {
1264                return false;
1265            }
1266        }
1267
1268        $comments = unserialize(io_readFile($cfile, false));
1269
1270        if ($comments['title']) $title = hsc($comments['title']);
1271        $num = $comments['number'];
1272        if ((!$comments['status']) || (($comments['status'] == 2) && (!$num))) return false;
1273        else return true;
1274    }
1275
1276    /**
1277     * Creates a new thread page
1278     */
1279    function _newThread() {
1280        global $ID, $INFO;
1281
1282        $ns    = cleanID($_REQUEST['ns']);
1283        $title = str_replace(':', '', $_REQUEST['title']);
1284        $back  = $ID;
1285        $ID    = ($ns ? $ns.':' : '').cleanID($title);
1286        $INFO  = pageinfo();
1287
1288        // check if we are allowed to create this file
1289        if ($INFO['perm'] >= AUTH_CREATE) {
1290
1291            //check if locked by anyone - if not lock for my self
1292            if ($INFO['locked']) return 'locked';
1293            else lock($ID);
1294
1295            // prepare the new thread file with default stuff
1296            if (!@file_exists($INFO['filepath'])) {
1297                global $TEXT;
1298
1299                $TEXT = pageTemplate(array(($ns ? $ns.':' : '').$title));
1300                if (!$TEXT) {
1301                    $data = array('id' => $ID, 'ns' => $ns, 'title' => $title, 'back' => $back);
1302                    $TEXT = $this->_pageTemplate($data);
1303                }
1304                return 'preview';
1305            } else {
1306                return 'edit';
1307            }
1308        } else {
1309            return 'show';
1310        }
1311    }
1312
1313    /**
1314     * Adapted version of pageTemplate() function
1315     */
1316    function _pageTemplate($data) {
1317        global $conf, $INFO;
1318
1319        $id   = $data['id'];
1320        $user = $_SERVER['REMOTE_USER'];
1321        $tpl  = io_readFile(DOKU_PLUGIN.'discussion/_template.txt');
1322
1323        // standard replacements
1324        $replace = array(
1325                '@NS@'   => $data['ns'],
1326                '@PAGE@' => strtr(noNS($id),'_',' '),
1327                '@USER@' => $user,
1328                '@NAME@' => $INFO['userinfo']['name'],
1329                '@MAIL@' => $INFO['userinfo']['mail'],
1330                '@DATE@' => dformat($conf['dformat']),
1331                );
1332
1333        // additional replacements
1334        $replace['@BACK@']  = $data['back'];
1335        $replace['@TITLE@'] = $data['title'];
1336
1337        // avatar if useavatar and avatar plugin available
1338        if ($this->getConf('useavatar')
1339                && (@file_exists(DOKU_PLUGIN.'avatar/syntax.php'))
1340                && (!plugin_isdisabled('avatar'))) {
1341            $replace['@AVATAR@'] = '{{avatar>'.$user.' }} ';
1342        } else {
1343            $replace['@AVATAR@'] = '';
1344        }
1345
1346        // tag if tag plugin is available
1347        if ((@file_exists(DOKU_PLUGIN.'tag/syntax/tag.php'))
1348                && (!plugin_isdisabled('tag'))) {
1349            $replace['@TAG@'] = "\n\n{{tag>}}";
1350        } else {
1351            $replace['@TAG@'] = '';
1352        }
1353
1354        // do the replace
1355        $tpl = str_replace(array_keys($replace), array_values($replace), $tpl);
1356        return $tpl;
1357    }
1358
1359    /**
1360     * Checks if the CAPTCHA string submitted is valid
1361     *
1362     * @author     Andreas Gohr <gohr@cosmocode.de>
1363     * @adaption   Esther Brunner <wikidesign@gmail.com>
1364     */
1365    function _captchaCheck() {
1366        if (plugin_isdisabled('captcha') || (!$captcha = plugin_load('helper', 'captcha')))
1367            return; // CAPTCHA is disabled or not available
1368
1369        // do nothing if logged in user and no CAPTCHA required
1370        if (!$captcha->getConf('forusers') && $_SERVER['REMOTE_USER']) return;
1371
1372        // compare provided string with decrypted captcha
1373        $rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'], auth_cookiesalt());
1374        $code = $captcha->_generateCAPTCHA($captcha->_fixedIdent(), $rand);
1375
1376        if (!$_REQUEST['plugin__captcha_secret'] ||
1377                !$_REQUEST['plugin__captcha'] ||
1378                strtoupper($_REQUEST['plugin__captcha']) != $code) {
1379
1380            // CAPTCHA test failed! Continue to edit instead of saving
1381            msg($captcha->getLang('testfailed'), -1);
1382            if ($_REQUEST['comment'] == 'save') $_REQUEST['comment'] = 'edit';
1383            elseif ($_REQUEST['comment'] == 'add') $_REQUEST['comment'] = 'show';
1384        }
1385        // if we arrive here it was a valid save
1386    }
1387
1388    /**
1389     * checks if the submitted reCAPTCHA string is valid
1390     *
1391     * @author Adrian Schlegel <adrian@liip.ch>
1392     */
1393    function _recaptchaCheck() {
1394        if (plugin_isdisabled('recaptcha') || (!$recaptcha = plugin_load('helper', 'recaptcha')))
1395            return; // reCAPTCHA is disabled or not available
1396
1397        // do nothing if logged in user and no reCAPTCHA required
1398        if (!$recaptcha->getConf('forusers') && $_SERVER['REMOTE_USER']) return;
1399
1400        $resp = $recaptcha->check();
1401        if (!$resp->is_valid) {
1402            msg($recaptcha->getLang('testfailed'),-1);
1403            if ($_REQUEST['comment'] == 'save') $_REQUEST['comment'] = 'edit';
1404            elseif ($_REQUEST['comment'] == 'add') $_REQUEST['comment'] = 'show';
1405        }
1406    }
1407
1408    /**
1409     * Add discussion plugin version to the indexer version
1410     * This means that all pages will be indexed again in order to add the comments
1411     * to the index whenever there has been a change that concerns the index content.
1412     */
1413    function idx_version(&$event, $param) {
1414        $event->data['discussion'] = '0.1';
1415    }
1416
1417    /**
1418     * Adds the comments to the index
1419     */
1420    function idx_add_discussion(&$event, $param) {
1421
1422        // get .comments meta file name
1423        $file = metaFN($event->data['page'], '.comments');
1424
1425        if (@file_exists($file)) $data = unserialize(io_readFile($file, false));
1426        if ((!$data['status']) || ($data['number'] == 0)) return; // comments are turned off
1427
1428        // now add the comments
1429        if (isset($data['comments'])) {
1430            foreach ($data['comments'] as $key => $value) {
1431                $event->data['body'] .= $this->_addCommentWords($key, $data);
1432            }
1433        }
1434    }
1435
1436    /**
1437     * Adds the words of a given comment to the index
1438     */
1439    function _addCommentWords($cid, &$data, $parent = '') {
1440
1441        if (!isset($data['comments'][$cid])) return ''; // comment was removed
1442        $comment = $data['comments'][$cid];
1443
1444        if (!is_array($comment)) return '';             // corrupt datatype
1445        if ($comment['parent'] != $parent) return '';   // reply to an other comment
1446        if (!$comment['show']) return '';               // hidden comment
1447
1448        $text = $comment['raw'];                        // we only add the raw comment text
1449        if (is_array($comment['replies'])) {             // and the replies
1450            foreach ($comment['replies'] as $rid) {
1451                $text .= $this->_addCommentWords($rid, $data, $cid);
1452            }
1453        }
1454        return ' '.$text;
1455    }
1456
1457    /**
1458     * Only allow http(s) URLs and append http:// to URLs if needed
1459     */
1460    function _checkURL($url) {
1461        if(preg_match("#^http://|^https://#", $url)) {
1462            return hsc($url);
1463        } elseif(substr($url, 0, 4) == 'www.') {
1464            return hsc('http://' . $url);
1465        } else {
1466            return '';
1467        }
1468    }
1469}
1470
1471function _sortCallback($a, $b) {
1472    if (is_array($a['date'])) { // new format
1473        $createdA  = $a['date']['created'];
1474    } else {                         // old format
1475        $createdA  = $a['date'];
1476    }
1477
1478    if (is_array($b['date'])) { // new format
1479        $createdB  = $b['date']['created'];
1480    } else {                         // old format
1481        $createdB  = $b['date'];
1482    }
1483
1484    if ($createdA == $createdB)
1485        return 0;
1486    else
1487        return ($createdA < $createdB) ? -1 : 1;
1488}
1489
1490// vim:ts=4:sw=4:et:enc=utf-8:
1491