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