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