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