xref: /plugin/discussion/action.php (revision a768ba620648a5515ac0cfe24148acf7bbcad11b)
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_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
11require_once(DOKU_PLUGIN.'action.php');
12
13if (!defined('NL')) define('NL',"\n");
14
15class action_plugin_discussion extends DokuWiki_Action_Plugin{
16
17  /**
18   * Return some info
19   */
20  function getInfo(){
21    return array(
22      'author' => 'Esther Brunner',
23      'email'  => 'wikidesign@gmail.com',
24      'date'   => '2007-01-05',
25      'name'   => 'Discussion Plugin',
26      'desc'   => 'Enables discussion features',
27      'url'    => 'http://www.wikidesign.ch/en/plugin/discussion/start',
28    );
29  }
30
31  /**
32   * Register the eventhandlers
33   */
34  function register(&$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      'RENDERER_CONTENT_POSTPROCESS',
51      'AFTER',
52      $this,
53      'add_toc_item',
54      array()
55    );
56  }
57
58  /**
59   * Main function; dispatches the comment actions
60   */
61  function comments(&$event, $param){
62    if ($event->data != 'show') return; // nothing to do for us
63
64    $cid  = $_REQUEST['cid'];
65
66    switch ($_REQUEST['comment']){
67
68      case 'add':
69        $comment = array(
70          'user'    => array(
71            'id'      => hsc($_REQUEST['user']),
72            'name'    => hsc($_REQUEST['name']),
73            'mail'    => hsc($_REQUEST['mail']),
74            'url'     => hsc($_REQUEST['url']),
75            'address' => hsc($_REQUEST['address'])),
76          'date'    => array('created' => $_REQUEST['date']),
77          'raw'     => cleanText($_REQUEST['text'])
78        );
79        $repl = $_REQUEST['reply'];
80        $this->_add($comment, $repl);
81        break;
82
83      case 'edit':
84        $this->_show(NULL, $cid);
85        break;
86
87      case 'save':
88        $raw  = cleanText($_REQUEST['text']);
89        $this->_save($cid, $raw);
90        break;
91
92      case 'delete':
93        $this->_save($cid, '');
94        break;
95
96      case 'toogle':
97        $this->_save($cid, '', true);
98        break;
99
100      default: // 'show' => $this->_show(), 'reply' => $this->_show($cid)
101        $this->_show($cid);
102    }
103  }
104
105  /**
106   * Shows all comments of the current page
107   */
108  function _show($reply = NULL, $edit = NULL){
109    global $ID, $INFO;
110
111    // get .comments meta file name
112    $file = metaFN($ID, '.comments');
113
114    if (!@file_exists($file)){
115      // create .comments meta file if automatic setting is switched on
116      if ($this->getConf('automatic') && $INFO['exists']){
117        $data = array('status' => 1, 'number' => 0);
118        io_saveFile($file, serialize($data));
119      }
120    } else { // load data
121      $data = unserialize(io_readFile($file, false));
122    }
123
124    if (!$data['status']) return false; // comments are turned off
125
126    // section title
127    $title = $this->getLang('discussion');
128    echo '<div class="comment_wrapper">';
129    echo '<h2><a name="discussion__section" id="discussion__section">'.$title.'</a></h2>';
130    echo '<div class="level2 hfeed">';
131
132    // now display the comments
133    if (isset($data['comments'])){
134      foreach ($data['comments'] as $key => $value){
135        if ($key == $edit) $this->_form($value['raw'], 'save', $edit); // edit form
136        else $this->_print($key, $data, '', $reply);
137      }
138    }
139
140    // comment form
141    if (($data['status'] == 1) && !$reply && !$edit) $this->_form('');
142
143    echo '</div>'; // level2
144    echo '</div>'; // comment_wrapper
145
146    return true;
147  }
148
149  /**
150   * Adds a new comment and then displays all comments
151   */
152  function _add($comment, $parent){
153    global $ID;
154    global $TEXT;
155
156    $otxt = $TEXT; // set $TEXT to comment text for wordblock check
157    $TEXT = $comment['raw'];
158
159    // spamcheck against the DokuWiki blacklist
160    if (checkwordblock()){
161      msg($this->getLang('wordblock'), -1);
162      $this->_show();
163      return false;
164    }
165
166    $TEXT = $otxt; // restore global $TEXT
167
168    // get discussion meta file name
169    $file = metaFN($ID, '.comments');
170
171    $data = array();
172    $data = unserialize(io_readFile($file, false));
173
174    if ($data['status'] != 1) return false;                // comments off or closed
175    if ((!$this->getConf('allowguests'))
176      && ($comment['user']['id'] != $_SERVER['REMOTE_USER']))
177      return false;                                        // guest comments not allowed
178
179    if ($comment['date']['created']) $date = strtotime($comment['date']['created']);
180    else $date = time();
181    if ($date == -1) $date = time();
182    $cid  = md5($comment['user']['id'].$date);             // create a unique id
183
184    if (!is_array($data['comments'][$parent])) $parent = NULL; // invalid parent comment
185
186    // render the comment
187    $xhtml = $this->_render($comment['raw']);
188
189    // fill in the new comment
190    $data['comments'][$cid] = array(
191      'user'    => $comment['user'],
192      'date'    => array('created' => $date),
193      'show'    => true,
194      'raw'     => $comment['raw'],
195      'xhtml'   => $xhtml,
196      'parent'  => $parent,
197      'replies' => array()
198    );
199
200    // update parent comment
201    if ($parent) $data['comments'][$parent]['replies'][] = $cid;
202
203    // update the number of comments
204    $data['number']++;
205
206    // save the comment metadata file
207    io_saveFile($file, serialize($data));
208    $this->_addLogEntry($date, $ID, 'cc', '', $cid);
209
210    // notify subscribers of the page
211    $this->_notify($data['comments'][$cid]);
212
213    $this->_show();
214    return true;
215  }
216
217  /**
218   * Saves the comment with the given ID and then displays all comments
219   */
220  function _save($cid, $raw, $toogle = false){
221    global $ID;
222    global $INFO;
223
224    if ($raw){
225      global $TEXT;
226
227      $otxt = $TEXT; // set $TEXT to comment text for wordblock check
228      $TEXT = $raw;
229
230      // spamcheck against the DokuWiki blacklist
231      if (checkwordblock()){
232        msg($this->getLang('wordblock'), -1);
233        $this->_show();
234        return false;
235      }
236
237      $TEXT = $otxt; // restore global $TEXT
238    }
239
240    // get discussion meta file name
241    $file = metaFN($ID, '.comments');
242
243    $data = array();
244    $data = unserialize(io_readFile($file, false));
245
246    if (is_array($data['comments'][$cid]['user'])){
247      $user    = $data['comments'][$cid]['user']['id'];
248      $convert = false;
249    } else {
250      $user    = $data['comments'][$cid]['user'];
251      $convert = true;
252    }
253
254    // someone else was trying to edit our comment -> abort
255    if (($user != $_SERVER['REMOTE_USER']) && ($INFO['perm'] != AUTH_ADMIN)) return false;
256
257    $date = time();
258
259    // need to convert to new format?
260    if ($convert){
261      $data['comments'][$cid]['user'] = array(
262        'id'      => $user,
263        'name'    => $data['comments'][$cid]['name'],
264        'mail'    => $data['comments'][$cid]['mail'],
265        'url'     => $data['comments'][$cid]['url'],
266        'address' => $data['comments'][$cid]['address'],
267      );
268      $data['comments'][$cid]['date'] = array(
269        'created' => $data['comments'][$cid]['date']
270      );
271    }
272
273    if ($toogle){     // toogle visibility
274      $now = $data['comments'][$cid]['show'];
275      $data['comments'][$cid]['show'] = !$now;
276      $data['number'] = $this->_count($data);
277
278      $type = ($data['comments'][$cid]['show'] ? 'sc' : 'hc');
279
280    } elseif (!$raw){ // remove the comment
281      unset($data['comments'][$cid]);
282      $data['number'] = $this->_count($data);
283
284      $type = 'dc';
285
286    } else {          // save changed comment
287      $xhtml = $this->_render($raw);
288
289      // now change the comment's content
290      $data['comments'][$cid]['date']['modified'] = $date;
291      $data['comments'][$cid]['raw']              = $raw;
292      $data['comments'][$cid]['xhtml']            = $xhtml;
293
294      $type = 'ec';
295    }
296
297    // save the comment metadata file
298    io_saveFile($file, serialize($data));
299    $this->_addLogEntry($date, $ID, $type, '', $cid);
300
301    $this->_show();
302    return true;
303  }
304
305  /**
306   * Prints an individual comment
307   */
308  function _print($cid, &$data, $parent = '', $reply = '', $visible = true){
309    global $conf;
310    global $lang;
311    global $ID;
312    global $INFO;
313
314    if (!isset($data['comments'][$cid])) return false; // comment was removed
315    $comment = $data['comments'][$cid];
316
317    if (!is_array($comment)) return false;             // corrupt datatype
318
319    if ($comment['parent'] != $parent) return true;    // reply to an other comment
320
321    if (!$comment['show']){                            // comment hidden
322      if ($INFO['perm'] == AUTH_ADMIN) echo '<div class="comment_hidden">'.NL;
323      else return true;
324    }
325
326    // comment head with date and user data
327    echo '<div class="hentry"><div class="comment_head">'.NL.
328      '<a name="comment__'.$cid.'" id="comment__'.$cid.'"></a>'.NL.
329      '<span class="vcard author">';
330
331    // prepare variables
332    if (is_array($comment['user'])){ // new format
333      $user    = $comment['user']['id'];
334      $name    = $comment['user']['name'];
335      $mail    = $comment['user']['mail'];
336      $url     = $comment['user']['url'];
337      $address = $comment['user']['address'];
338    } else {                         // old format
339      $user    = $comment['user'];
340      $name    = $comment['name'];
341      $mail    = $comment['mail'];
342      $url     = $comment['url'];
343      $address = $comment['address'];
344    }
345    if (is_array($comment['date'])){ // new format
346      $created  = $comment['date']['created'];
347      $modified = $comment['date']['modified'];
348    } else {                         // old format
349      $created  = $comment['date'];
350      $modified = $comment['edited'];
351    }
352
353    // show gravatar image
354    if ($this->getConf('usegravatar')){
355      $default = DOKU_URL.'lib/plugins/discussion/images/default.gif';
356      $size    = $this->getConf('gravatar_size');
357      if ($mail) $src = ml('http://www.gravatar.com/avatar.php?'.
358        'gravatar_id='.md5($mail).
359        '&default='.urlencode($default).
360        '&size='.$size.
361        '&rating='.$this->getConf('gravatar_rating').
362        '&.jpg', 'cache=recache');
363      else $src = $default;
364      $title = ($name ? $name : obfuscate($mail));
365      echo '<img src="'.$src.'" class="medialeft photo" title="'.$title.'"'.
366        ' alt="'.$title.'" width="'.$size.'" height="'.$size.'" />'.NL;
367      $style = ' style="margin-left: '.($size + 14).'px;"';
368    } else {
369      $style = ' style="margin-left: 20px;"';
370    }
371
372    if ($this->getConf('linkemail') && $mail){
373      echo $this->email($mail, $name, 'email fn');
374    } elseif ($url){
375      echo $this->external_link($url, $name, 'urlextern url fn');
376    } else {
377      echo '<span class="fn">'.$name.'</span>';
378    }
379    if ($address) echo ', <span class="adr">'.$address.'</span>';
380    echo '</span>, <abbr class="published" title="'.gmdate('Y-m-d\TH:i:s\Z', $created).
381      '">'.date($conf['dformat'], $created).'</abbr>';
382    if ($comment['edited']) echo ' (<abbr class="updated" title="'.
383      gmdate('Y-m-d\TH:i:s\Z', $modified).'">'.date($conf['dformat'], $modified).
384      '</abbr>)';
385    echo ':'.NL.'</div>'.NL; // class="comment_head"
386
387    // main comment content
388    echo '<div class="comment_body entry-content"'.
389      ($this->getConf('usegravatar') ? $style : '').'>'.NL.
390      $comment['xhtml'].NL.
391      '</div>'.NL.'</div>'.NL; // class="comment_body" and class="hentry"
392
393    if ($visible){
394      // show hide/show toogle button?
395      echo '<div class="comment_buttons">'.NL;
396      if ($INFO['perm'] == AUTH_ADMIN){
397        if (!$comment['show']) $label = $this->getLang('btn_show');
398        else $label = $this->getLang('btn_hide');
399
400        $this->_button($cid, $label, 'toogle');
401      }
402
403      // show reply button?
404      if (($data['status'] == 1) && !$reply && $comment['show']
405        && ($this->getConf('allowguests') || $_SERVER['REMOTE_USER']))
406        $this->_button($cid, $this->getLang('btn_reply'), 'reply', true);
407
408      // show edit and delete button?
409      if ((($user == $_SERVER['REMOTE_USER']) && ($user != ''))
410        || ($INFO['perm'] == AUTH_ADMIN))
411        $this->_button($cid, $lang['btn_secedit'], 'edit', true);
412      if ($INFO['perm'] == AUTH_ADMIN)
413        $this->_button($cid, $lang['btn_delete'], 'delete');
414      echo '</div>'.NL; // class="comment_buttons"
415      echo '<div class="comment_line" '.
416        ($this->getConf('usegravatar') ? $style : '').'>&nbsp;</div>'.NL;
417    }
418
419    // replies to this comment entry?
420    if (count($comment['replies'])){
421      echo '<div class="comment_replies"'.$style.'>'.NL;
422      $visible = ($comment['show'] && $visible);
423      foreach ($comment['replies'] as $rid){
424        $this->_print($rid, $data, $cid, $reply, $visible);
425      }
426      echo '</div>'.NL; // class="comment_replies"
427    }
428
429    if (!$comment['show']) echo '</div>'.NL; // class="comment_hidden"
430
431    // reply form
432    if ($reply == $cid){
433      echo '<div class="comment_replies">'.NL;
434      $this->_form('', 'add', $cid);
435      echo '</div>'.NL; // class="comment_replies"
436    }
437  }
438
439  /**
440   * Outputs the comment form
441   */
442  function _form($raw = '', $act = 'add', $cid = NULL){
443    global $lang;
444    global $conf;
445    global $ID;
446    global $INFO;
447
448    // not for unregistered users when guest comments aren't allowed
449    if (!$_SERVER['REMOTE_USER'] && !$this->getConf('allowguests')) return false;
450
451    // fill $raw with $_REQUEST['text'] if it's empty (for failed CAPTCHA check)
452    if (!$raw && ($_REQUEST['comment'] == 'show')) $raw = $_REQUEST['text'];
453
454    ?>
455    <div class="comment_form">
456      <form id="discussion__comment_form" method="post" action="<?php echo script() ?>" accept-charset="<?php echo $lang['encoding'] ?>" onsubmit="return validate(this);">
457        <div class="no">
458          <input type="hidden" name="id" value="<?php echo $ID ?>" />
459          <input type="hidden" name="do" value="show" />
460          <input type="hidden" name="comment" value="<?php echo $act ?>" />
461    <?php
462
463    // for adding a comment
464    if ($act == 'add'){
465      ?>
466          <input type="hidden" name="reply" value="<?php echo $cid ?>" />
467      <?php
468      // for registered user (and we're not in admin import mode)
469      if ($conf['useacl'] && $_SERVER['REMOTE_USER']
470        && (!($this->getConf('adminimport') && ($INFO['perm'] == AUTH_ADMIN)))){
471      ?>
472          <input type="hidden" name="user" value="<?php echo hsc($_SERVER['REMOTE_USER']) ?>" />
473          <input type="hidden" name="name" value="<?php echo hsc($INFO['userinfo']['name']) ?>" />
474          <input type="hidden" name="mail" value="<?php echo hsc($INFO['userinfo']['mail']) ?>" />
475      <?php
476      // for guest: show name and e-mail entry fields
477      } else {
478      ?>
479          <input type="hidden" name="user" value="<?php echo clientIP() ?>" />
480          <div class="comment_name">
481            <label class="block" for="discussion__comment_name">
482              <span><?php echo $lang['fullname'] ?>:</span>
483              <input type="text" class="edit" name="name" id="discussion__comment_name" size="50" tabindex="1" value="<?php echo hsc($_REQUEST['name'])?>" />
484            </label>
485          </div>
486          <div class="comment_mail">
487            <label class="block" for="discussion__comment_mail">
488              <span><?php echo $lang['email'] ?>:</span>
489              <input type="text" class="edit" name="mail" id="discussion__comment_mail" size="50" tabindex="2" value="<?php echo hsc($_REQUEST['mail'])?>" />
490            </label>
491          </div>
492      <?php
493      }
494
495      // allow entering an URL
496      if ($this->getConf('urlfield')){
497      ?>
498          <div class="comment_url">
499            <label class="block" for="discussion__comment_url">
500              <span><?php echo $this->getLang('url') ?>:</span>
501              <input type="text" class="edit" name="url" id="discussion__comment_url" size="50" tabindex="3" value="<?php echo hsc($_REQUEST['url'])?>" />
502            </label>
503          </div>
504      <?php
505      }
506
507      // allow entering an address
508      if ($this->getConf('addressfield')){
509      ?>
510          <div class="comment_address">
511            <label class="block" for="discussion__comment_address">
512              <span><?php echo $this->getLang('address') ?>:</span>
513              <input type="text" class="edit" name="address" id="discussion__comment_address" size="50" tabindex="4" value="<?php echo hsc($_REQUEST['address'])?>" />
514            </label>
515          </div>
516      <?php
517      }
518
519      // allow setting the comment date
520      if ($this->getConf('adminimport') && ($INFO['perm'] == AUTH_ADMIN)){
521      ?>
522          <div class="comment_date">
523            <label class="block" for="discussion__comment_date">
524              <span><?php echo $this->getLang('date') ?>:</span>
525              <input type="text" class="edit" name="date" id="discussion__comment_date" size="50" />
526            </label>
527          </div>
528      <?php
529      }
530
531    // for saving a comment
532    } else {
533    ?>
534          <input type="hidden" name="cid" value="<?php echo $cid ?>" />
535    <?php
536    }
537    ?>
538          <div class="comment_text">
539            <textarea class="edit" name="text" cols="80" rows="10" id="discussion__comment_text" tabindex="5"><?php echo formText($raw) ?></textarea>
540          </div>
541    <?php //bad and dirty event insert hook
542    $evdata = array('writable' => true);
543    trigger_event('HTML_EDITFORM_INJECTION', $evdata);
544    ?>
545          <input class="button" type="submit" name="submit" value="<?php echo $lang['btn_save'] ?>" tabindex="6" />
546        </div>
547      </form>
548    </div>
549    <?php
550    if ($this->getConf('usecocomment')) echo $this->_coComment();
551  }
552
553  /**
554   * Adds a javascript to interact with coComments
555   */
556  function _coComment(){
557    global $ID;
558    global $conf;
559    global $INFO;
560
561    $user = $_SERVER['REMOTE_USER'];
562
563    ?>
564    <script type="text/javascript"><!--//--><![CDATA[//><!--
565      var blogTool  = "DokuWiki";
566      var blogURL   = "<?php echo DOKU_URL ?>";
567      var blogTitle = "<?php echo $conf['title'] ?>";
568      var postURL   = "<?php echo wl($ID, '', true) ?>";
569      var postTitle = "<?php echo tpl_pagetitle($ID, true) ?>";
570    <?php
571    if ($user){
572    ?>
573      var commentAuthor = "<?php echo $INFO['userinfo']['name'] ?>";
574    <?php
575    } else {
576    ?>
577      var commentAuthorFieldName = "name";
578    <?php
579    }
580    ?>
581      var commentAuthorLoggedIn = <?php echo ($user ? 'true' : 'false') ?>;
582      var commentFormID         = "discussion__comment_form";
583      var commentTextFieldName  = "text";
584      var commentButtonName     = "submit";
585      var cocomment_force       = false;
586    //--><!]]></script>
587    <script type="text/javascript" src="http://www.cocomment.com/js/cocomment.js">
588    </script>
589    <?php
590  }
591
592  /**
593   * General button function
594   */
595  function _button($cid, $label, $act, $jump = false){
596    global $ID;
597    $anchor = ($jump ? '#discussion__comment_form' : '' );
598
599    ?>
600    <form class="button" method="post" action="<?php echo script().$anchor ?>">
601      <div class="no">
602        <input type="hidden" name="id" value="<?php echo $ID ?>" />
603        <input type="hidden" name="do" value="show" />
604        <input type="hidden" name="comment" value="<?php echo $act ?>" />
605        <input type="hidden" name="cid" value="<?php echo $cid ?>" />
606        <input type="submit" value="<?php echo $label ?>" class="button" title="<?php echo $label ?>" />
607      </div>
608    </form>
609    <?php
610    return true;
611  }
612
613  /**
614   * Adds an entry to the comments changelog
615   *
616   * @author Esther Brunner <wikidesign@gmail.com>
617   * @author Ben Coburn <btcoburn@silicodon.net>
618   */
619  function _addLogEntry($date, $id, $type = 'cc', $summary = '', $extra = ''){
620    global $conf;
621
622    $changelog = $conf['metadir'].'/_comments.changes';
623
624    if(!$date) $date = time(); //use current time if none supplied
625    $remote = $_SERVER['REMOTE_ADDR'];
626    $user   = $_SERVER['REMOTE_USER'];
627
628    $strip = array("\t", "\n");
629    $logline = array(
630      'date'  => $date,
631      'ip'    => $remote,
632      'type'  => str_replace($strip, '', $type),
633      'id'    => $id,
634      'user'  => $user,
635      'sum'   => str_replace($strip, '', $summary),
636      'extra' => str_replace($strip, '', $extra)
637    );
638
639    // add changelog line
640    $logline = implode("\t", $logline)."\n";
641    io_saveFile($changelog, $logline, true); //global changelog cache
642    $this->_trimRecentCommentsLog($changelog);
643  }
644
645  /**
646   * Trims the recent comments cache to the last $conf['changes_days'] recent
647   * changes or $conf['recent'] items, which ever is larger.
648   * The trimming is only done once a day.
649   *
650   * @author Ben Coburn <btcoburn@silicodon.net>
651   */
652  function _trimRecentCommentsLog($changelog){
653    global $conf;
654
655    if (@file_exists($changelog) &&
656      (filectime($changelog) + 86400) < time() &&
657      !@file_exists($changelog.'_tmp')){
658
659      io_lock($changelog);
660      $lines = file($changelog);
661      if (count($lines)<$conf['recent']) {
662          // nothing to trim
663          io_unlock($changelog);
664          return true;
665      }
666
667      io_saveFile($changelog.'_tmp', '');                  // presave tmp as 2nd lock
668      $trim_time = time() - $conf['recent_days']*86400;
669      $out_lines = array();
670
671      for ($i=0; $i<count($lines); $i++) {
672        $log = parseChangelogLine($lines[$i]);
673        if ($log === false) continue;                      // discard junk
674        if ($log['date'] < $trim_time) {
675          $old_lines[$log['date'].".$i"] = $lines[$i];     // keep old lines for now (append .$i to prevent key collisions)
676        } else {
677          $out_lines[$log['date'].".$i"] = $lines[$i];     // definitely keep these lines
678        }
679      }
680
681      // sort the final result, it shouldn't be necessary,
682      // however the extra robustness in making the changelog cache self-correcting is worth it
683      ksort($out_lines);
684      $extra = $conf['recent'] - count($out_lines);        // do we need extra lines do bring us up to minimum
685      if ($extra > 0) {
686        ksort($old_lines);
687        $out_lines = array_merge(array_slice($old_lines,-$extra),$out_lines);
688      }
689
690      // save trimmed changelog
691      io_saveFile($changelog.'_tmp', implode('', $out_lines));
692      @unlink($changelog);
693      if (!rename($changelog.'_tmp', $changelog)) {
694        // rename failed so try another way...
695        io_unlock($changelog);
696        io_saveFile($changelog, implode('', $out_lines));
697        @unlink($changelog.'_tmp');
698      } else {
699        io_unlock($changelog);
700      }
701      return true;
702    }
703  }
704
705  /**
706   * Sends a notify mail on new comment
707   *
708   * @param  array  $comment  data array of the new comment
709   *
710   * @author Andreas Gohr <andi@splitbrain.org>
711   * @author Esther Brunner <wikidesign@gmail.com>
712   */
713  function _notify($comment){
714    global $conf;
715    global $ID;
716
717    if ((!$conf['subscribers']) && (!$conf['notify'])) return; //subscribers enabled?
718    $bcc  = subscriber_addresslist($ID);
719    if ((empty($bcc)) && (!$conf['notify'])) return;
720    $to   = $conf['notify'];
721    $text = io_readFile($this->localFN('subscribermail'));
722
723    $search = array(
724      '@PAGE@',
725      '@TITLE@',
726      '@DATE@',
727      '@NAME@',
728      '@TEXT@',
729      '@UNSUBSCRIBE@',
730      '@DOKUWIKIURL@',
731    );
732    $replace = array(
733      $ID,
734      $conf['title'],
735      date($conf['dformat'], $comment['date']['created']),
736      $comment['user']['name'],
737      $comment['raw'],
738      wl($ID, 'do=unsubscribe', true, '&'),
739      DOKU_URL,
740    );
741    $text = str_replace($search, $replace, $text);
742
743    $subject = '['.$conf['title'].'] '.$this->getLang('mail_newcomment');
744
745    mail_send($to, $subject, $text, $conf['mailfrom'], '', $bcc);
746  }
747
748  /**
749   * Counts the number of visible comments
750   */
751  function _count($data){
752    $number = 0;
753    foreach ($data['comments'] as $cid => $comment){
754      if ($comment['parent']) continue;
755      if (!$comment['show']) continue;
756      $number++;
757      $rids = $comment['replies'];
758      if (count($rids)) $number = $number + $this->_countReplies($data, $rids);
759    }
760    return $number;
761  }
762
763  function _countReplies(&$data, $rids){
764    $number = 0;
765    foreach ($rids as $rid){
766      if (!isset($data['comments'][$rid])) continue; // reply was removed
767      if (!$data['comments'][$rid]['show']) continue;
768      $number++;
769      $rids = $data['comments'][$rid]['replies'];
770      if (count($rids)) $number = $number + $this->_countReplies($data, $rids);
771    }
772    return $number;
773  }
774
775  /**
776   * Renders the comment text
777   */
778  function _render($raw){
779    if ($this->getConf('wikisyntaxok')){
780      $xhtml = $this->render($raw);
781    } else { // wiki syntax not allowed -> just encode special chars
782      $xhtml = htmlspecialchars(trim($raw));
783    }
784    return $xhtml;
785  }
786
787  /**
788   * Adds a TOC item for the discussion section
789   */
790  function add_toc_item(&$event, $param){
791    if ($event->data[0] != 'xhtml') return; // nothing to do for us
792    if (!$this->_hasDiscussion()) return;   // no discussion section
793
794    $pattern = '/<div id="toc__inside">(.*?)<\/div>\s<\/div>/s';
795    if (!preg_match($pattern, $event->data[1], $match)) return; // no TOC on this page
796
797    // ok, then let's do it!
798    global $conf;
799
800    $title   = $this->getLang('discussion');
801    $section = '#discussion__section';
802    $level   = 3 - $conf['toptoclevel'];
803
804    $item = '<li class="level'.$level.'"><div class="li"><span class="li"><a href="'.
805      $section.'" class="toc">'.$title.'</a></span></div></li>';
806
807    if ($level == 1) $search = "</ul>\n</div>";
808    else $search = "</ul>\n</li></ul>\n</div>";
809
810    $new = str_replace($search, $item.$search, $match[0]);
811    $event->data[1] = preg_replace($pattern, $new, $event->data[1]);
812  }
813
814  /**
815   * Finds out whether there is a discussion section for the current page
816   */
817  function _hasDiscussion(){
818    global $ID;
819
820    $cfile = metaFN($ID, '.comments');
821
822    if (!@file_exists($cfile)){
823      if ($this->getConf('automatic')) return true;
824      else return false;
825    }
826
827    $comments = unserialize(io_readFile($cfile, false));
828
829    $num = $comments['number'];
830    if ((!$comments['status']) || (($comments['status'] == 2) && (!$num))) return false;
831    else return true;
832  }
833
834  /**
835   * Checks if 'newthread' was given as action or the comment form was submitted
836   */
837  function handle_act_preprocess(&$event, $param){
838    if ($event->data == 'newthread'){
839      // we can handle it -> prevent others
840      // $event->stopPropagation();
841      $event->preventDefault();
842
843      $event->data = $this->_newThread();
844    }
845    if ((in_array($_REQUEST['comment'], array('add', 'save')))
846      && (@file_exists(DOKU_PLUGIN.'captcha/action.php'))){
847      $this->_captchaCheck();
848    }
849  }
850
851  /**
852   * Creates a new thread page
853   */
854  function _newThread(){
855    global $ID;
856    global $INFO;
857
858    $ns    = cleanID($_REQUEST['ns']);
859    $title = str_replace(':', '', $_REQUEST['title']);
860    $back  = $ID;
861    $ID    = ($ns ? $ns.':' : '').cleanID($title);
862    $INFO  = pageinfo();
863
864    // check if we are allowed to create this file
865    if ($INFO['perm'] >= AUTH_CREATE){
866
867      //check if locked by anyone - if not lock for my self
868      if ($INFO['locked']) return 'locked';
869      else lock($ID);
870
871      // prepare the new thread file with default stuff
872      if (!@file_exists($INFO['filepath'])){
873        global $TEXT;
874        global $conf;
875
876        $TEXT = pageTemplate(array(($ns ? $ns.':' : '').$title));
877        if (!$TEXT){
878          $TEXT = "<- [[:$back]]\n\n====== $title ======\n\n";
879          if ($this->getConf('usegravatar'))
880            $TEXT .= '{{gravatar>'.$INFO['userinfo']['mail'].' }} ';
881          $TEXT .= "//".$INFO['userinfo']['name'].", ".date($conf['dformat']).": //\n\n";
882          if ((@file_exists(DOKU_PLUGIN.'tag/syntax/tag.php'))
883            && (!plugin_isdisabled('tag')))
884            $TEXT .= "\n\n{{tag>}}";
885          $TEXT .= "\n\n~~DISCUSSION~~";
886        }
887        return 'preview';
888      } else {
889        return 'edit';
890      }
891    } else {
892      return 'show';
893    }
894  }
895
896  /**
897   * Checks if the CAPTCHA string submitted is valid
898   *
899   * @author     Andreas Gohr <gohr@cosmocode.de>
900   * @adaption   Esther Brunner <wikidesign@gmail.com>
901   */
902  function _captchaCheck(){
903    if (@file_exists(DOKU_PLUGIN.'captcha/disabled')) return; // CAPTCHA is disabled
904
905    require_once(DOKU_PLUGIN.'captcha/action.php');
906    $captcha = new action_plugin_captcha;
907
908    // do nothing if logged in user and no CAPTCHA required
909    if (!$captcha->getConf('forusers') && $_SERVER['REMOTE_USER']) return;
910
911    // compare provided string with decrypted captcha
912    $rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'], auth_cookiesalt());
913    $code = $captcha->_generateCAPTCHA($captcha->_fixedIdent(), $rand);
914
915    if (!$_REQUEST['plugin__captcha_secret'] ||
916      !$_REQUEST['plugin__captcha'] ||
917      strtoupper($_REQUEST['plugin__captcha']) != $code){
918
919      // CAPTCHA test failed! Continue to edit instead of saving
920      msg($captcha->getLang('testfailed'), -1);
921      if ($_REQUEST['comment'] == 'save') $_REQUEST['comment'] = 'edit';
922      elseif ($_REQUEST['comment'] == 'add') $_REQUEST['comment'] = 'show';
923    }
924    // if we arrive here it was a valid save
925  }
926
927}
928
929//Setup VIM: ex: et ts=4 enc=utf-8 :
930