Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/publish/comment.php - 598 lines - 18651 bytes - Summary - Text - Print

   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&amp;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

title

Description

title

Description

title

title

Body