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' => '2006-12-20', 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">'; 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' => htmlspecialchars($comment['user']), 191 'name' => htmlspecialchars($comment['name']), 192 'mail' => htmlspecialchars($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'] = htmlspecialchars($comment['url']); 202 if ($comment['address']) 203 $data['comments'][$cid]['address'] = htmlspecialchars($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="comment_head">'.NL; 312 echo '<a name="comment__'.$cid.'" id="comment__'.$cid.'">'.NL; 313 314 // show gravatar image 315 if ($this->getConf('usegravatar')){ 316 $default = DOKU_URL.'lib/plugins/discussion/images/default.gif'; 317 $size = $this->getConf('gravatar_size'); 318 if ($comment['mail']) $src = ml('http://www.gravatar.com/avatar.php?'. 319 'gravatar_id='.md5($comment['mail']). 320 '&default='.urlencode($default). 321 '&size='.$size. 322 '&rating='.$this->getConf('gravatar_rating'). 323 '&.jpg', 'cache=recache'); 324 else $src = $default; 325 $title = ($comment['name'] ? $comment['name'] : obfuscate($comment['mail'])); 326 echo '<img src="'.$src.'" class="medialeft" title="'.$title.'"'. 327 ' alt="'.$title.'" width="'.$size.'" height="'.$size.'" />'.NL; 328 $style = ' style="margin-left: '.($size + 14).'px;"'; 329 } else { 330 $style = ' style="margin-left: 20px;"'; 331 } 332 333 echo '</a>'.NL; 334 if ($this->getConf('linkemail') && $comment['mail']){ 335 echo $this->email($comment['email'], $comment['name']); 336 } elseif ($comment['url']){ 337 echo $this->external_link($comment['url'], $comment['name'], 'urlextern'); 338 } else { 339 echo $comment['name']; 340 } 341 if ($comment['address']) echo ', '.htmlentities($comment['address']); 342 echo ', '.date($conf['dformat'], $comment['date']); 343 if ($comment['edited']) echo ' ('.date($conf['dformat'], $comment['edited']).')'; 344 echo ':'.NL; 345 echo '</div>'.NL; // class="comment_head" 346 347 // main comment content 348 echo '<div class="comment_body"'.($this->getConf('usegravatar') ? $style : '').'>'.NL; 349 echo $comment['xhtml'].NL; 350 echo '</div>'.NL; // class="comment_body" 351 352 353 if ($visible){ 354 // show hide/show toogle button? 355 echo '<div class="comment_buttons">'.NL; 356 if ($INFO['perm'] == AUTH_ADMIN){ 357 if (!$comment['show']) $label = $this->getLang('btn_show'); 358 else $label = $this->getLang('btn_hide'); 359 360 $this->_button($cid, $label, 'toogle'); 361 } 362 363 // show reply button? 364 if (($data['status'] == 1) && !$reply && $comment['show'] 365 && ($this->getConf('allowguests') || $_SERVER['REMOTE_USER'])) 366 $this->_button($cid, $this->getLang('btn_reply'), 'reply', true); 367 368 // show edit and delete button? 369 if ((($comment['user'] == $_SERVER['REMOTE_USER']) && ($comment['user'] != '')) 370 || ($INFO['perm'] == AUTH_ADMIN)) 371 $this->_button($cid, $lang['btn_secedit'], 'edit', true); 372 if ($INFO['perm'] == AUTH_ADMIN) 373 $this->_button($cid, $lang['btn_delete'], 'delete'); 374 echo '</div>'.NL; // class="comment_buttons" 375 echo '<div class="comment_line" '.($this->getConf('usegravatar') ? $style : '').'> </div>'.NL; 376 } 377 378 // replies to this comment entry? 379 if (count($comment['replies'])){ 380 echo '<div class="comment_replies"'.$style.'>'.NL; 381 $visible = ($comment['show'] && $visible); 382 foreach ($comment['replies'] as $rid){ 383 $this->_print($rid, $data, $cid, $reply, $visible); 384 } 385 echo '</div>'.NL; // class="comment_replies" 386 } 387 388 if (!$comment['show']) echo '</div>'.NL; // class="comment_hidden" 389 390 // reply form 391 if ($reply == $cid){ 392 echo '<div class="comment_replies">'.NL; 393 $this->_form('', 'add', $cid); 394 echo '</div>'.NL; // class="comment_replies" 395 } 396 } 397 398 /** 399 * Outputs the comment form 400 */ 401 function _form($raw = '', $act = 'add', $cid = NULL){ 402 global $lang; 403 global $conf; 404 global $ID; 405 global $INFO; 406 407 // not for unregistered users when guest comments aren't allowed 408 if (!$_SERVER['REMOTE_USER'] && !$this->getConf('allowguests')) return false; 409 410 // fill $raw with $_REQUEST['text'] if it's empty (for failed CAPTCHA check) 411 if (!$raw && ($_REQUEST['comment'] == 'show')) $raw = $_REQUEST['text']; 412 413 ?> 414 <div class="comment_form"> 415 <form id="discussion__comment_form" method="post" action="<?php echo script() ?>" accept-charset="<?php echo $lang['encoding'] ?>" onsubmit="return validate(this);"> 416 <div class="no"> 417 <input type="hidden" name="id" value="<?php echo $ID ?>" /> 418 <input type="hidden" name="do" value="show" /> 419 <input type="hidden" name="comment" value="<?php echo $act ?>" /> 420 <?php 421 422 // for adding a comment 423 if ($act == 'add'){ 424 ?> 425 <input type="hidden" name="reply" value="<?php echo $cid ?>" /> 426 <?php 427 // for registered user (and we're not in admin import mode) 428 if ($conf['useacl'] && $_SERVER['REMOTE_USER'] 429 && (!($this->getConf('adminimport') && ($INFO['perm'] == AUTH_ADMIN)))){ 430 ?> 431 <input type="hidden" name="user" value="<?php echo hsc($_SERVER['REMOTE_USER']) ?>" /> 432 <input type="hidden" name="name" value="<?php echo hsc($INFO['userinfo']['name']) ?>" /> 433 <input type="hidden" name="mail" value="<?php echo hsc($INFO['userinfo']['mail']) ?>" /> 434 <?php 435 // for guest: show name and e-mail entry fields 436 } else { 437 ?> 438 <input type="hidden" name="user" value="<?php echo clientIP() ?>" /> 439 <div class="comment_name"> 440 <label class="block" for="discussion__comment_name"> 441 <span><?php echo $lang['fullname'] ?>:</span> 442 <input type="text" class="edit" name="name" id="discussion__comment_name" size="50" tabindex="1" value="<?php echo hsc($_REQUEST['name'])?>" /> 443 </label> 444 </div> 445 <div class="comment_mail"> 446 <label class="block" for="discussion__comment_mail"> 447 <span><?php echo $lang['email'] ?>:</span> 448 <input type="text" class="edit" name="mail" id="discussion__comment_mail" size="50" tabindex="2" value="<?php echo hsc($_REQUEST['mail'])?>" /> 449 </label> 450 </div> 451 <?php 452 } 453 454 // allow entering an URL 455 if ($this->getConf('urlfield')){ 456 ?> 457 <div class="comment_url"> 458 <label class="block" for="discussion__comment_url"> 459 <span><?php echo $this->getLang('url') ?>:</span> 460 <input type="text" class="edit" name="url" id="discussion__comment_url" size="50" tabindex="3" value="<?php echo hsc($_REQUEST['url'])?>" /> 461 </label> 462 </div> 463 <?php 464 } 465 466 // allow entering an address 467 if ($this->getConf('addressfield')){ 468 ?> 469 <div class="comment_address"> 470 <label class="block" for="discussion__comment_address"> 471 <span><?php echo $this->getLang('address') ?>:</span> 472 <input type="text" class="edit" name="address" id="discussion__comment_address" size="50" tabindex="4" value="<?php echo hsc($_REQUEST['address'])?>" /> 473 </label> 474 </div> 475 <?php 476 } 477 478 // allow setting the comment date 479 if ($this->getConf('adminimport') && ($INFO['perm'] == AUTH_ADMIN)){ 480 ?> 481 <div class="comment_date"> 482 <label class="block" for="discussion__comment_date"> 483 <span><?php echo $this->getLang('date') ?>:</span> 484 <input type="text" class="edit" name="date" id="discussion__comment_date" size="50" /> 485 </label> 486 </div> 487 <?php 488 } 489 490 // for saving a comment 491 } else { 492 ?> 493 <input type="hidden" name="cid" value="<?php echo $cid ?>" /> 494 <?php 495 } 496 ?> 497 <div class="comment_text"> 498 <textarea class="edit" name="text" cols="80" rows="10" id="discussion__comment_text" tabindex="5"><?php echo formText($raw) ?></textarea> 499 </div> 500 <?php //bad and dirty event insert hook 501 $evdata = array('writable' => true); 502 trigger_event('HTML_EDITFORM_INJECTION', $evdata); 503 ?> 504 <input class="button" type="submit" name="submit" value="<?php echo $lang['btn_save'] ?>" tabindex="6" /> 505 </div> 506 </form> 507 </div> 508 <?php 509 if ($this->getConf('usecocomment')) echo $this->_coComment(); 510 } 511 512 /** 513 * Adds a javascript to interact with coComments 514 */ 515 function _coComment(){ 516 global $ID; 517 global $conf; 518 global $INFO; 519 520 $user = $_SERVER['REMOTE_USER']; 521 522 ?> 523 <script type="text/javascript"><!--//--><![CDATA[//><!-- 524 var blogTool = "DokuWiki"; 525 var blogURL = "<?php echo DOKU_URL ?>"; 526 var blogTitle = "<?php echo $conf['title'] ?>"; 527 var postURL = "<?php echo wl($ID, '', true) ?>"; 528 var postTitle = "<?php echo tpl_pagetitle($ID, true) ?>"; 529 <?php 530 if ($user){ 531 ?> 532 var commentAuthor = "<?php echo $INFO['userinfo']['name'] ?>"; 533 <?php 534 } else { 535 ?> 536 var commentAuthorFieldName = "name"; 537 <?php 538 } 539 ?> 540 var commentAuthorLoggedIn = <?php echo ($user ? 'true' : 'false') ?>; 541 var commentFormID = "discussion__comment_form"; 542 var commentTextFieldName = "text"; 543 var commentButtonName = "submit"; 544 var cocomment_force = false; 545 //--><!]]></script> 546 <script type="text/javascript" src="http://www.cocomment.com/js/cocomment.js"> 547 </script> 548 <?php 549 } 550 551 /** 552 * General button function 553 */ 554 function _button($cid, $label, $act, $jump = false){ 555 global $ID; 556 $anchor = ($jump ? '#discussion__comment_form' : '' ); 557 558 ?> 559 <form class="button" method="post" action="<?php echo script().$anchor ?>"> 560 <div class="no"> 561 <input type="hidden" name="id" value="<?php echo $ID ?>" /> 562 <input type="hidden" name="do" value="show" /> 563 <input type="hidden" name="comment" value="<?php echo $act ?>" /> 564 <input type="hidden" name="cid" value="<?php echo $cid ?>" /> 565 <input type="submit" value="<?php echo $label ?>" class="button" title="<?php echo $label ?>" /> 566 </div> 567 </form> 568 <?php 569 return true; 570 } 571 572 /** 573 * Adds an entry to the comments changelog 574 * 575 * @author Esther Brunner <wikidesign@gmail.com> 576 * @author Ben Coburn <btcoburn@silicodon.net> 577 */ 578 function _addLogEntry($date, $id, $type = 'cc', $summary = '', $extra = ''){ 579 global $conf; 580 581 $changelog = $conf['metadir'].'/_comments.changes'; 582 583 if(!$date) $date = time(); //use current time if none supplied 584 $remote = $_SERVER['REMOTE_ADDR']; 585 $user = $_SERVER['REMOTE_USER']; 586 587 $strip = array("\t", "\n"); 588 $logline = array( 589 'date' => $date, 590 'ip' => $remote, 591 'type' => str_replace($strip, '', $type), 592 'id' => $id, 593 'user' => $user, 594 'sum' => str_replace($strip, '', $summary), 595 'extra' => str_replace($strip, '', $extra) 596 ); 597 598 // add changelog line 599 $logline = implode("\t", $logline)."\n"; 600 io_saveFile($changelog, $logline, true); //global changelog cache 601 $this->_trimRecentCommentsLog($changelog); 602 } 603 604 /** 605 * Trims the recent comments cache to the last $conf['changes_days'] recent 606 * changes or $conf['recent'] items, which ever is larger. 607 * The trimming is only done once a day. 608 * 609 * @author Ben Coburn <btcoburn@silicodon.net> 610 */ 611 function _trimRecentCommentsLog($changelog){ 612 global $conf; 613 614 if (@file_exists($changelog) && 615 (filectime($changelog) + 86400) < time() && 616 !@file_exists($changelog.'_tmp')){ 617 618 io_lock($changelog); 619 $lines = file($changelog); 620 if (count($lines)<$conf['recent']) { 621 // nothing to trim 622 io_unlock($changelog); 623 return true; 624 } 625 626 io_saveFile($changelog.'_tmp', ''); // presave tmp as 2nd lock 627 $trim_time = time() - $conf['recent_days']*86400; 628 $out_lines = array(); 629 630 for ($i=0; $i<count($lines); $i++) { 631 $log = parseChangelogLine($lines[$i]); 632 if ($log === false) continue; // discard junk 633 if ($log['date'] < $trim_time) { 634 $old_lines[$log['date'].".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions) 635 } else { 636 $out_lines[$log['date'].".$i"] = $lines[$i]; // definitely keep these lines 637 } 638 } 639 640 // sort the final result, it shouldn't be necessary, 641 // however the extra robustness in making the changelog cache self-correcting is worth it 642 ksort($out_lines); 643 $extra = $conf['recent'] - count($out_lines); // do we need extra lines do bring us up to minimum 644 if ($extra > 0) { 645 ksort($old_lines); 646 $out_lines = array_merge(array_slice($old_lines,-$extra),$out_lines); 647 } 648 649 // save trimmed changelog 650 io_saveFile($changelog.'_tmp', implode('', $out_lines)); 651 @unlink($changelog); 652 if (!rename($changelog.'_tmp', $changelog)) { 653 // rename failed so try another way... 654 io_unlock($changelog); 655 io_saveFile($changelog, implode('', $out_lines)); 656 @unlink($changelog.'_tmp'); 657 } else { 658 io_unlock($changelog); 659 } 660 return true; 661 } 662 } 663 664 /** 665 * Sends a notify mail on new comment 666 * 667 * @param array $comment data array of the new comment 668 * 669 * @author Andreas Gohr <andi@splitbrain.org> 670 * @author Esther Brunner <wikidesign@gmail.com> 671 */ 672 function _notify($comment){ 673 global $conf; 674 global $ID; 675 676 if ((!$conf['subscribers']) && (!$conf['notify'])) return; //subscribers enabled? 677 $bcc = subscriber_addresslist($ID); 678 if ((empty($bcc)) && (!$conf['notify'])) return; 679 $to = $conf['notify']; 680 $text = io_readFile($this->localFN('subscribermail')); 681 682 $text = str_replace('@PAGE@', $ID, $text); 683 $text = str_replace('@TITLE@', $conf['title'], $text); 684 $text = str_replace('@DATE@', date($conf['dformat'], $comment['date']), $text); 685 $text = str_replace('@NAME@', $comment['name'], $text); 686 $text = str_replace('@TEXT@', $comment['raw'], $text); 687 $text = str_replace('@UNSUBSCRIBE@', wl($ID, 'do=unsubscribe', true, '&'), $text); 688 $text = str_replace('@DOKUWIKIURL@', DOKU_URL, $text); 689 690 $subject = '['.$conf['title'].'] '.$this->getLang('mail_newcomment'); 691 692 mail_send($to, $subject, $text, $conf['mailfrom'], '', $bcc); 693 } 694 695 /** 696 * Counts the number of visible comments 697 */ 698 function _count($data){ 699 $number = 0; 700 foreach ($data['comments'] as $cid => $comment){ 701 if ($comment['parent']) continue; 702 if (!$comment['show']) continue; 703 $number++; 704 $rids = $comment['replies']; 705 if (count($rids)) $number = $number + $this->_countReplies($data, $rids); 706 } 707 return $number; 708 } 709 710 function _countReplies(&$data, $rids){ 711 $number = 0; 712 foreach ($rids as $rid){ 713 if (!isset($data['comments'][$rid])) continue; // reply was removed 714 if (!$data['comments'][$rid]['show']) continue; 715 $number++; 716 $rids = $data['comments'][$rid]['replies']; 717 if (count($rids)) $number = $number + $this->_countReplies($data, $rids); 718 } 719 return $number; 720 } 721 722 /** 723 * Renders the comment text 724 */ 725 function _render($raw){ 726 if ($this->getConf('wikisyntaxok')){ 727 $xhtml = $this->render($raw); 728 } else { // wiki syntax not allowed -> just encode special chars 729 $xhtml = htmlspecialchars(trim($raw)); 730 } 731 return $xhtml; 732 } 733 734 /** 735 * Adds a TOC item for the discussion section 736 */ 737 function add_toc_item(&$event, $param){ 738 if ($event->data[0] != 'xhtml') return; // nothing to do for us 739 if (!$this->_hasDiscussion()) return; // no discussion section 740 741 $pattern = '/<div id="toc__inside">(.*?)<\/div>\s<\/div>/s'; 742 if (!preg_match($pattern, $event->data[1], $match)) return; // no TOC on this page 743 744 // ok, then let's do it! 745 global $conf; 746 747 $title = $this->getLang('discussion'); 748 $section = '#discussion__section'; 749 $level = 3 - $conf['toptoclevel']; 750 751 $item = '<li class="level'.$level.'"><div class="li"><span class="li"><a href="'. 752 $section.'" class="toc">'.$title.'</a></span></div></li>'; 753 754 if ($level == 1) $search = "</ul>\n</div>"; 755 else $search = "</ul>\n</li></ul>\n</div>"; 756 757 $new = str_replace($search, $item.$search, $match[0]); 758 $event->data[1] = preg_replace($pattern, $new, $event->data[1]); 759 } 760 761 /** 762 * Finds out whether there is a discussion section for the current page 763 */ 764 function _hasDiscussion(){ 765 global $ID; 766 767 $cfile = metaFN($ID, '.comments'); 768 769 if (!@file_exists($cfile)){ 770 if ($this->getConf('automatic')) return true; 771 else return false; 772 } 773 774 $comments = unserialize(io_readFile($cfile, false)); 775 776 $num = $comments['number']; 777 if ((!$comments['status']) || (($comments['status'] == 2) && (!$num))) return false; 778 else return true; 779 } 780 781 /** 782 * Checks if 'newthread' was given as action or the comment form was submitted 783 */ 784 function handle_act_preprocess(&$event, $param){ 785 if ($event->data == 'newthread'){ 786 // we can handle it -> prevent others 787 // $event->stopPropagation(); 788 $event->preventDefault(); 789 790 $event->data = $this->_handle_newThread(); 791 } 792 if ((in_array($_REQUEST['comment'], array('add', 'save'))) 793 && (@file_exists(DOKU_PLUGIN.'captcha/action.php'))){ 794 $this->_handle_captchaCheck(); 795 } 796 } 797 798 /** 799 * Creates a new thread page 800 */ 801 function _handle_newThread(){ 802 global $ID; 803 global $INFO; 804 805 $ns = cleanID($_REQUEST['ns']); 806 $title = str_replace(':', '', $_REQUEST['title']); 807 $back = $ID; 808 $ID = ($ns ? $ns.':' : '').cleanID($title); 809 $INFO = pageinfo(); 810 811 // check if we are allowed to create this file 812 if ($INFO['perm'] >= AUTH_CREATE){ 813 814 //check if locked by anyone - if not lock for my self 815 if ($INFO['locked']) return 'locked'; 816 else lock($ID); 817 818 // prepare the new thread file with default stuff 819 if (!@file_exists($INFO['filepath'])){ 820 global $TEXT; 821 global $conf; 822 823 $TEXT = pageTemplate(array(($ns ? $ns.':' : '').$title)); 824 if (!$TEXT){ 825 $TEXT = "<- [[:$back]]\n\n====== $title ======\n\n"; 826 if ($this->getConf('usegravatar')) 827 $TEXT .= '{{gravatar>'.$INFO['userinfo']['mail'].' }} '; 828 $TEXT .= "//".$INFO['userinfo']['name'].", ".date($conf['dformat']).": //\n\n"; 829 if ((@file_exists(DOKU_PLUGIN.'tag/syntax/tag.php')) 830 && (!plugin_isdisabled('tag'))) 831 $TEXT .= "\n\n{{tag>}}"; 832 $TEXT .= "\n\n~~DISCUSSION~~"; 833 } 834 return 'preview'; 835 } else { 836 return 'edit'; 837 } 838 } else { 839 return 'show'; 840 } 841 } 842 843 /** 844 * Checks if the CAPTCHA string submitted is valid 845 * 846 * @author Andreas Gohr <gohr@cosmocode.de> 847 * @adaption Esther Brunner <wikidesign@gmail.com> 848 */ 849 function _handle_captchaCheck(){ 850 if (@file_exists(DOKU_PLUGIN.'captcha/disabled')) return; // CAPTCHA is disabled 851 852 require_once(DOKU_PLUGIN.'captcha/action.php'); 853 $captcha = new action_plugin_captcha; 854 855 // do nothing if logged in user and no CAPTCHA required 856 if (!$captcha->getConf('forusers') && $_SERVER['REMOTE_USER']) return; 857 858 // compare provided string with decrypted captcha 859 $rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'], auth_cookiesalt()); 860 $code = $captcha->_generateCAPTCHA($captcha->_fixedIdent(), $rand); 861 862 if (!$_REQUEST['plugin__captcha_secret'] || 863 !$_REQUEST['plugin__captcha'] || 864 strtoupper($_REQUEST['plugin__captcha']) != $code){ 865 866 // CAPTCHA test failed! Continue to edit instead of saving 867 msg($captcha->getLang('testfailed'), -1); 868 if ($_REQUEST['comment'] == 'save') $_REQUEST['comment'] = 'edit'; 869 elseif ($_REQUEST['comment'] == 'add') $_REQUEST['comment'] = 'show'; 870 } 871 // if we arrive here it was a valid save 872 } 873 874} 875 876//Setup VIM: ex: et ts=4 enc=utf-8 : 877