| Textpattern | PHP Cross Reference | Content Management Systems |
1 <?php 2 3 /* 4 This is Textpattern 5 Copyright 2005 by Dean Allen - all rights reserved. 6 7 Use of this software denotes acceptance of the Textpattern license agreement 8 9 $HeadURL: https://textpattern.googlecode.com/svn/releases/4.5.4/source/textpattern/publish/comment.php $ 10 $LastChangedRevision: 4058 $ 11 12 */ 13 14 // ------------------------------------------------------------- 15 function fetchComments($id) 16 { 17 $rs = safe_rows( 18 "*, unix_timestamp(posted) as time", 19 "txp_discuss", 'parentid='.intval($id).' and visible='.VISIBLE.' order by posted asc' 20 ); 21 22 if ($rs) return $rs; 23 } 24 25 // ------------------------------------------------------------- 26 function discuss($id) 27 { 28 $rs = safe_row('*, unix_timestamp(Posted) as uPosted, unix_timestamp(LastMod) as uLastMod, unix_timestamp(Expires) as uExpires', 'textpattern', 'ID='.intval($id).' and Status >= 4'); 29 if ($rs) { 30 populateArticleData($rs); 31 $result = parse_form('comments_display'); 32 return $result; 33 } 34 35 return ''; 36 } 37 38 39 // ------------------------------------------------------------- 40 function getNextNonce($check_only = false) 41 { 42 static $nonce = ''; 43 if (!$nonce && !$check_only) 44 $nonce = md5( uniqid( rand(), true ) ); 45 return $nonce; 46 } 47 function getNextSecret($check_only = false) 48 { 49 static $secret = ''; 50 if (!$secret && !$check_only) 51 $secret = md5( uniqid( rand(), true ) ); 52 return $secret; 53 } 54 55 function commentForm($id, $atts=NULL) 56 { 57 global $prefs; 58 extract($prefs); 59 60 $h5 = ($doctype == 'html5'); 61 62 extract(lAtts(array( 63 'isize' => '25', 64 'msgrows' => '5', 65 'msgcols' => '25', 66 'msgstyle' => '', 67 'form' => 'comment_form', 68 'previewlabel' => gTxt('preview'), 69 'submitlabel' => gTxt('submit'), 70 'rememberlabel' => gTxt('remember'), 71 'forgetlabel' => gTxt('forget') 72 ),$atts, 0)); 73 74 $namewarn = false; 75 $emailwarn = false; 76 $commentwarn = false; 77 $name = pcs('name'); 78 $email = clean_url(pcs('email')); 79 $web = clean_url(pcs('web')); 80 $n_message = 'message'; 81 82 extract( doDeEnt ( psa( array( 83 'checkbox_type', 84 'remember', 85 'forget', 86 'parentid', 87 'preview', 88 'message', 89 'submit', 90 'backpage' 91 ) ) ) ); 92 if ($message == '') 93 { //Second or later preview will have randomized message-field name 94 $in = getComment(); 95 $message = doDeEnt($in['message']); 96 } 97 if ( $preview ) { 98 $name = ps('name'); 99 $email = clean_url(ps('email')); 100 $web = clean_url(ps('web')); 101 $nonce = getNextNonce(); 102 $secret = getNextSecret(); 103 safe_insert("txp_discuss_nonce", "issue_time=now(), nonce='".doSlash($nonce)."', secret='".doSlash($secret)."'"); 104 $n_message = md5('message'.$secret); 105 106 $namewarn = ($comments_require_name && !trim($name)); 107 $emailwarn = ($comments_require_email && !trim($email)); 108 $commentwarn = (!trim($message)); 109 110 $evaluator =& get_comment_evaluator(); 111 if ($namewarn) $evaluator -> add_estimate(RELOAD,1,gTxt('comment_name_required')); 112 if ($emailwarn) $evaluator -> add_estimate(RELOAD,1,gTxt('comment_email_required')); 113 if ($commentwarn) $evaluator -> add_estimate(RELOAD,1,gTxt('comment_required')); 114 115 } 116 else 117 { 118 $rememberCookie = cs('txp_remember'); 119 if($rememberCookie === '') 120 { 121 $checkbox_type = 'remember'; 122 $remember = 1; 123 } 124 else if($rememberCookie == 1) 125 $checkbox_type = 'forget'; 126 else 127 $checkbox_type = 'remember'; 128 } 129 130 // If the form fields are filled (anything other than blank), pages 131 // really should not be saved by a public cache. rfc2616/14.9.1 132 if ($name || $email || $web) { 133 header('Cache-Control: private'); 134 } 135 136 $parentid = (!$parentid) ? $id : $parentid; 137 138 $url = $GLOBALS['pretext']['request_uri']; 139 140 // Experimental clean urls with only 404-error-document on apache 141 // possibly requires messy urls for POST requests. 142 if (defined('PARTLY_MESSY') and (PARTLY_MESSY)) 143 { 144 $url = hu.'?id='.intval($parentid); 145 } 146 147 $out = '<form id="txpCommentInputForm" method="post" action="'.txpspecialchars($url).'#cpreview">'. 148 149 # prevent XHTML Strict validation gotchas 150 n.'<div class="comments-wrapper">'.n.n; 151 152 $Form = fetch('Form', 'txp_form', 'name', $form); 153 154 $required = ($h5) ? ' required' : ''; 155 156 $msgstyle = ($msgstyle ? ' style="'.$msgstyle.'"' : ''); 157 $msgrows = ($msgrows and is_numeric($msgrows)) ? ' rows="'.intval($msgrows).'"' : ''; 158 $msgcols = ($msgcols and is_numeric($msgcols)) ? ' cols="'.intval($msgcols).'"' : ''; 159 160 $textarea = '<textarea id="message" name="'.$n_message.'"'.$msgcols.$msgrows.$msgstyle.$required. 161 ' class="txpCommentInputMessage'.(($commentwarn) ? ' comments_error"' : '"'). 162 '>'.txpspecialchars(substr(trim($message), 0, 65535)).'</textarea>'; 163 164 // by default, the submit button is visible but disabled 165 $comment_submit_button = fInput('submit', 'submit', $submitlabel, 'button disabled', '', '', '', '', 'txpCommentSubmit', true); 166 167 // if all fields checkout, the submit button is active/clickable 168 if ($preview) { 169 $comment_submit_button = fInput('submit', 'submit', $submitlabel, 'button', '', '', '', '', 'txpCommentSubmit', false); 170 } 171 172 if ($checkbox_type == 'forget') 173 { 174 // inhibit default remember 175 if ($forget == 1) 176 { 177 destroyCookies(); 178 } 179 180 $checkbox = checkbox('forget', 1, $forget, '', 'forget').' '.tag(txpspecialchars($forgetlabel), 'label', ' for="forget"'); 181 } 182 183 else 184 { 185 // inhibit default remember 186 if ($remember != 1) 187 { 188 destroyCookies(); 189 } 190 191 $checkbox = checkbox('remember', 1, $remember, '', 'remember').' '.tag(txpspecialchars($rememberlabel), 'label', ' for="remember"'); 192 } 193 194 $checkbox .= ' '.hInput('checkbox_type', $checkbox_type); 195 196 $vals = array( 197 'comment_name_input' => fInput('text', 'name', $name, 'comment_name_input'.($namewarn ? ' comments_error' : ''), '', '', $isize, '', 'name', false, $h5 && $comments_require_name), 198 'comment_email_input' => fInput($h5 ? 'email' : 'text', 'email', $email, 'comment_email_input'.($emailwarn ? ' comments_error' : ''), '', '', $isize, '', 'email', false, $h5 && $comments_require_email), 199 'comment_web_input' => fInput($h5 ? 'text' /* TODO: type = 'url' once browsers are less strict */ : 'text', 'web', $web, 'comment_web_input', '', '', $isize, '', 'web', false, false), 200 'comment_message_input' => $textarea.'<!-- plugin-place-holder -->', 201 'comment_remember' => $checkbox, 202 'comment_preview' => fInput('submit', 'preview', $previewlabel, 'button', '', '', '', '', 'txpCommentPreview', false), 203 'comment_submit' => $comment_submit_button 204 ); 205 206 foreach ($vals as $a => $b) 207 { 208 $Form = str_replace('<txp:'.$a.' />', $b, $Form); 209 } 210 211 $form = parse($Form); 212 213 $out .= $form. 214 n.hInput('parentid', $parentid); 215 216 $split = rand(1, 31); 217 218 $out .= ($preview) ? n.hInput(substr($nonce, 0, $split), substr($nonce, $split)) : ''; 219 220 $out .= (!$preview) ? 221 n.hInput('backpage', $url) : 222 n.hInput('backpage', $backpage); 223 224 $out = str_replace( '<!-- plugin-place-holder -->', callback_event('comment.form'), $out); 225 226 $out .= n.n.'</div>'.n.'</form>'; 227 228 return $out; 229 } 230 231 // ------------------------------------------------------------- 232 function popComments($id) 233 { 234 global $sitename,$s,$thisarticle; 235 $preview = gps('preview'); 236 $h3 = ($preview) ? hed(gTxt('message_preview'),3) : ''; 237 $discuss = discuss($id); 238 ob_start('parse'); 239 $out = fetch_form('popup_comments'); 240 $out = str_replace("<txp:popup_comments />",$discuss,$out); 241 242 return $out; 243 } 244 245 // ------------------------------------------------------------- 246 function setCookies($name,$email,$web) 247 { 248 $cookietime = time() + (365*24*3600); 249 ob_start(); 250 setcookie("txp_name", $name, $cookietime, "/"); 251 setcookie("txp_email", $email, $cookietime, "/"); 252 setcookie("txp_web", $web, $cookietime, "/"); 253 setcookie("txp_last", date("H:i d/m/Y"),$cookietime,"/"); 254 setcookie("txp_remember", '1', $cookietime, "/"); 255 } 256 257 // ------------------------------------------------------------- 258 function destroyCookies() 259 { 260 $cookietime = time()-3600; 261 ob_start(); 262 setcookie("txp_name", '', $cookietime, "/"); 263 setcookie("txp_email", '', $cookietime, "/"); 264 setcookie("txp_web", '', $cookietime, "/"); 265 setcookie("txp_last", '', $cookietime, "/"); 266 setcookie("txp_remember", '0', $cookietime + (365*25*3600), "/"); 267 } 268 269 // ------------------------------------------------------------- 270 function getComment() 271 { 272 // comment spam filter plugins: call this function to fetch comment contents 273 274 $c = psa( array( 275 'parentid', 276 'name', 277 'email', 278 'web', 279 'message', 280 'backpage', 281 'remember' 282 ) ); 283 284 $n = array(); 285 286 foreach (stripPost() as $k => $v) 287 { 288 if (preg_match('#^[A-Fa-f0-9]{32}$#', $k.$v)) 289 { 290 $n[] = doSlash($k.$v); 291 } 292 } 293 294 $c['nonce'] = ''; 295 $c['secret'] = ''; 296 if (!empty($n)) { 297 $rs = safe_row('nonce, secret', 'txp_discuss_nonce', "nonce in ('".join("','", $n)."')"); 298 $c['nonce'] = $rs['nonce']; 299 $c['secret'] = $rs['secret']; 300 } 301 $c['message'] = ps(md5('message'.$c['secret'])); 302 return $c; 303 } 304 305 // ------------------------------------------------------------- 306 function saveComment() 307 { 308 global $siteurl,$comments_moderate,$comments_sendmail, 309 $comments_disallow_images,$prefs; 310 311 $ref = serverset('HTTP_REFERRER'); 312 $in = getComment(); 313 $evaluator =& get_comment_evaluator(); 314 315 extract($in); 316 317 if (!checkCommentsAllowed($parentid)) 318 txp_die(gTxt('comments_closed'), '403'); 319 320 $ip = serverset('REMOTE_ADDR'); 321 322 if (!checkBan($ip)) 323 txp_die(gTxt('you_have_been_banned'), '403'); 324 325 $blacklisted = is_blacklisted($ip); 326 if ($blacklisted) 327 txp_die(gTxt('your_ip_is_blacklisted_by'.' '.$blacklisted), '403'); 328 329 $web = clean_url($web); 330 $email = clean_url($email); 331 if ($remember == 1 || ps('checkbox_type') == 'forget' && ps('forget') != 1) 332 setCookies($name, $email, $web); 333 else 334 destroyCookies(); 335 336 $name = doSlash(strip_tags(deEntBrackets($name))); 337 $web = doSlash(strip_tags(deEntBrackets($web))); 338 $email = doSlash(strip_tags(deEntBrackets($email))); 339 340 $message = substr(trim($message), 0, 65535); 341 $message2db = doSlash(markup_comment($message)); 342 343 $isdup = safe_row("message,name", "txp_discuss", 344 "name='$name' and message='$message2db' and ip='".doSlash($ip)."'"); 345 346 if ( ($prefs['comments_require_name'] && !trim($name)) 347 || ($prefs['comments_require_email'] && !trim($email)) 348 || (!trim($message))) 349 { 350 $evaluator -> add_estimate(RELOAD,1); // The error-messages are added in the preview-code 351 } 352 353 if ($isdup) 354 $evaluator -> add_estimate(RELOAD,1); // FIXME? Tell the user about dupe? 355 356 if ( ($evaluator->get_result() != RELOAD) && checkNonce($nonce) ) { 357 callback_event('comment.save'); 358 $visible = $evaluator->get_result(); 359 if ($visible != RELOAD) { 360 $parentid = assert_int($parentid); 361 $commentid = safe_insert( 362 "txp_discuss", 363 "parentid = $parentid, 364 name = '$name', 365 email = '$email', 366 web = '$web', 367 ip = '".doSlash($ip)."', 368 message = '$message2db', 369 visible = ".intval($visible).", 370 posted = now()" 371 ); 372 if ($commentid) { 373 safe_update("txp_discuss_nonce", "used = 1", "nonce='".doSlash($nonce)."'"); 374 if ($prefs['comment_means_site_updated']) { 375 update_lastmod(); 376 } 377 callback_event('comment.saved', '', false, compact('message', 'name', 'email', 'web', 'parentid', 'commentid', 'ip', 'visible')); 378 mail_comment($message, $name, $email, $web, $parentid, $commentid); 379 380 $updated = update_comments_count($parentid); 381 382 $backpage = substr($backpage, 0, $prefs['max_url_len']); 383 $backpage = preg_replace("/[\x0a\x0d#].*$/s",'',$backpage); 384 $backpage = preg_replace("#(https?://[^/]+)/.*$#","$1",hu).$backpage; 385 if (defined('PARTLY_MESSY') and (PARTLY_MESSY)) 386 { 387 $backpage = permlinkurl_id($parentid); 388 } 389 $backpage .= ((strstr($backpage,'?')) ? '&' : '?') . 'commented='.(($visible==VISIBLE) ? '1' : '0'); 390 391 txp_status_header('302 Found'); 392 if($comments_moderate){ 393 header('Location: '.$backpage.'#txpCommentInputForm'); 394 }else{ 395 header('Location: '.$backpage.'#c'.sprintf("%06s",$commentid)); 396 } 397 log_hit('302'); 398 $evaluator->write_trace(); 399 exit; 400 } 401 } 402 } 403 // Force another Preview 404 $_POST['preview'] = RELOAD; 405 //$evaluator->write_trace(); 406 } 407 408 // ------------------------------------------------------------- 409 class comment_evaluation { 410 var $status; 411 var $message; 412 var $txpspamtrace = array(); 413 var $status_text = array(); 414 415 function comment_evaluation() { 416 global $prefs; 417 extract(getComment()); 418 $this->status = array( SPAM => array(), 419 MODERATE => array(), 420 VISIBLE => array(), 421 RELOAD => array() 422 ); 423 $this->status_text = array( SPAM => gTxt('spam'), 424 MODERATE => gTxt('unmoderated'), 425 VISIBLE => gTxt('visible'), 426 RELOAD => gTxt('reload') 427 ); 428 $this->message = $this->status; 429 $this -> txpspamtrace[] = "Comment on $parentid by $name (".safe_strftime($prefs['archive_dateformat'],time()).")"; 430 if ($prefs['comments_moderate']) 431 $this->status[MODERATE][]=0.5; 432 else 433 $this->status[VISIBLE][]=0.5; 434 } 435 436 function add_estimate($type = SPAM, $probability = 0.75, $msg='') { 437 global $production_status; 438 439 if (!array_key_exists($type, $this->status)) 440 trigger_error(gTxt('unknown_spam_estimate'), E_USER_WARNING); 441 442 $this -> txpspamtrace[] = " $type; ".max(0,min(1,$probability))."; $msg"; 443 //FIXME trace is only viewable for RELOADS. Maybe add info to HTTP-Headers in debug-mode 444 445 $this->status[$type][] = max(0,min(1,$probability)); 446 if (trim($msg)) $this->message[$type][] = $msg; 447 } 448 449 function get_result($result_type='numeric') { 450 $result = array(); 451 foreach ($this->status as $key => $value) 452 $result[$key] = array_sum($value)/max(1,count($value)); 453 arsort($result, SORT_NUMERIC); 454 reset($result); 455 return (($result_type == 'numeric') ? key($result) : $this->status_text[key($result)]); 456 } 457 function get_result_message() { 458 return $this->message[$this->get_result()]; 459 } 460 function write_trace() { 461 global $prefs; 462 $file = $prefs['tempdir'].DS.'evaluator_trace.php'; 463 if (!file_exists($file)) { 464 $fp = fopen($file,'wb'); 465 if ($fp) 466 fwrite($fp,"<?php return; ?>\n". 467 "This trace-file tracks saved comments. (created ".safe_strftime($prefs['archive_dateformat'],time()).")\n". 468 "Format is: Type; Probability; Message (Type can be -1 => spam, 0 => moderate, 1 => visible)\n\n"); 469 } else { 470 $fp = fopen($file,'ab'); 471 } 472 if ($fp) { 473 fwrite($fp, implode("\n", $this->txpspamtrace )); 474 fwrite($fp, "\n RESULT: ".$this->get_result()."\n\n"); 475 fclose($fp); 476 } 477 } 478 } 479 480 function &get_comment_evaluator() { 481 static $instance; 482 483 // If the instance is not there, create one 484 if(!isset($instance)) { 485 $instance = new comment_evaluation(); 486 } 487 return $instance; 488 } 489 490 // ------------------------------------------------------------- 491 function checkNonce($nonce) 492 { 493 if (!$nonce || !preg_match('#^[a-zA-Z0-9]*$#',$nonce)) 494 return false; 495 // delete expired nonces 496 safe_delete("txp_discuss_nonce", "issue_time < date_sub(now(),interval 10 minute)"); 497 // check for nonce 498 return (safe_row("*", "txp_discuss_nonce", "nonce='".doSlash($nonce)."' and used = 0")) ? true : false; 499 } 500 501 // ------------------------------------------------------------- 502 function checkBan($ip) 503 { 504 return (!fetch("ip", "txp_discuss_ipban", "ip", $ip)) ? true : false; 505 } 506 507 // ------------------------------------------------------------- 508 function checkCommentsAllowed($id) 509 { 510 global $use_comments, $comments_disabled_after, $thisarticle; 511 512 $id = intval($id); 513 514 if (!$use_comments || !$id) 515 return false; 516 517 if (isset($thisarticle['thisid']) && ($thisarticle['thisid'] == $id) && isset($thisarticle['annotate'])) 518 { 519 $Annotate = $thisarticle['annotate']; 520 $uPosted = $thisarticle['posted']; 521 } 522 else 523 { 524 extract( 525 safe_row( 526 "Annotate,unix_timestamp(Posted) as uPosted", 527 "textpattern", "ID = $id" 528 ) 529 ); 530 } 531 532 if ($Annotate != 1) 533 return false; 534 535 if($comments_disabled_after) { 536 $lifespan = ( $comments_disabled_after * 86400 ); 537 $timesince = ( time() - $uPosted ); 538 return ( $lifespan > $timesince ); 539 } 540 541 return true; 542 } 543 544 // ------------------------------------------------------------- 545 function comments_help() 546 { 547 return ('<a id="txpCommentHelpLink" href="'.HELP_URL.'?item=textile_comments&language='.LANG.'" onclick="window.open(this.href, \'popupwindow\', \'width=300,height=400,scrollbars,resizable\'); return false;">'.gTxt('textile_help').'</a>'); 548 } 549 550 // ------------------------------------------------------------- 551 function mail_comment($message, $cname, $cemail, $cweb, $parentid, $discussid) 552 { 553 global $sitename, $comments_sendmail; 554 555 if (!$comments_sendmail) return; 556 $evaluator =& get_comment_evaluator(); 557 if ($comments_sendmail == 2 && $evaluator->get_result() == SPAM) return; 558 559 $parentid = assert_int($parentid); 560 $discussid = assert_int($discussid); 561 $article = safe_row("Section, Posted, ID, url_title, AuthorID, Title", "textpattern", "ID = $parentid"); 562 extract($article); 563 extract(safe_row("RealName, email", "txp_users", "name = '".doSlash($AuthorID)."'")); 564 565 $out = gTxt('greeting')." $RealName,".n.n; 566 $out .= str_replace('{title}',$Title,gTxt('comment_recorded')).n; 567 $out .= permlinkurl_id($parentid).n; 568 if (has_privs('discuss', $AuthorID)) 569 $out .= hu.'textpattern/index.php?event=discuss&step=discuss_edit&discussid='.$discussid.n; 570 $out .= gTxt('status').": ".$evaluator->get_result('text').'. '.implode(',',$evaluator->get_result_message()).n; 571 $out .= n; 572 $out .= gTxt('comment_name').": $cname".n; 573 $out .= gTxt('comment_email').": $cemail".n; 574 $out .= gTxt('comment_web').": $cweb".n; 575 $out .= gTxt('comment_comment').": $message"; 576 577 $subject = strtr(gTxt('comment_received'),array('{site}' => $sitename, '{title}' => $Title)); 578 579 $success = txpMail($email, $subject, $out, $cemail); 580 } 581 582 // ------------------------------------------------------------- 583 # deprecated, use fInput instead 584 function input($type,$name,$val,$size='',$class='',$tab='',$chkd='') 585 { 586 trigger_error(gTxt('deprecated_function_with', array('{name}' => __FUNCTION__, '{with}' => 'fInput')), E_USER_NOTICE); 587 $o = array( 588 '<input type="'.$type.'" name="'.$name.'" id="'.$name.'" value="'.$val.'"', 589 ($size) ? ' size="'.$size.'"' : '', 590 ($class) ? ' class="'.$class.'"' : '', 591 ($tab) ? ' tabindex="'.$tab.'"' : '', 592 ($chkd) ? ' checked="checked"' : '', 593 ' />'.n 594 ); 595 return join('',$o); 596 } 597 598 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
title