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