b2evolution

Multilingual multiuser multiblog engine

b2evolution Technical Documentation (Version 1.9) [ class tree: plugins ] [ index: plugins ] [ all elements ]

Source for file _basic_antispam.plugin.php

Documentation is available at _basic_antispam.plugin.php

  1. <?php
  2. /**
  3.  * This file implements the basic Antispam plugin.
  4.  *
  5.  * This file is part of the b2evolution project - {@link http://b2evolution.net/}
  6.  *
  7.  * @copyright (c)2003-2006 by Francois PLANQUE - {@link http://fplanque.net/}
  8.  *  Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
  9.  *
  10.  *  {@internal License choice
  11.  *  - If you have received this file as part of a package, please find the license.txt file in
  12.  *    the same folder or the closest folder above for complete license terms.
  13.  *  - If you have received this file individually (e-g: from http://cvs.sourceforge.net/viewcvs.py/evocms/)
  14.  *    then you must choose one of the following licenses before using the file:
  15.  *    - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
  16.  *    - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
  17.  *  }}}
  18.  *
  19.  *  {@internal Open Source relicensing agreement:
  20.  *  Daniel HAHLER grants Francois PLANQUE the right to license
  21.  *  Daniel HAHLER's contributions to this file and the b2evolution project
  22.  *  under any OSI approved OSS license (http://www.opensource.org/licenses/).
  23.  *  }}}
  24.  *
  25.  * @package plugins
  26.  *
  27.  *  {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  28.  * @author blueyed: Daniel HAHLER - {@link http://daniel.hahler.de/}
  29.  *
  30.  * @version $Id: _basic_antispam.plugin.php,v 1.23.2.5 2007/01/30 20:04:23 blueyed Exp $
  31.  */
  32. if!defined('EVO_MAIN_INIT') ) die'Please, do not access this page directly.' );
  33.  
  34.  
  35. /**
  36.  * Basic Antispam Plugin
  37.  *
  38.  * This plugin doublechecks referers/referrers for Hit logging and trackbacks.
  39.  *
  40.  * @todo Ideas:
  41.  *   - forbid cloned comments (same content) (on the same entry or all entries)
  42.  *   - detect same/similar URLs in a short period (also look at author name: if it differs, it's more likely to be spam)
  43.  */
  44. class basic_antispam_plugin extends Plugin
  45. {
  46.     /**
  47.      * Variables below MUST be overriden by plugin implementations,
  48.      * either in the subclass declaration or in the subclass constructor.
  49.      */
  50.     var $name = 'Basic Antispam';
  51.     var $code = '';
  52.     var $priority = 60;
  53.     var $version = '1.9.3';
  54.     var $author = 'The b2evo Group';
  55.     var $group = 'antispam';
  56.  
  57.  
  58.     /**
  59.      * Init
  60.      */
  61.     function PluginInit$params )
  62.     {
  63.         $this->short_desc = T_('Basic antispam methods');
  64.         $this->long_desc = T_('This plugin provides basic methods to detect & block spam on referers, comments & trackbacks.');
  65.     }
  66.  
  67.  
  68.     function GetDefaultSettings()
  69.     {
  70.         return array(
  71.                 'allow_anon_comments' => array(
  72.                     'type' => 'checkbox',
  73.                     'label' => T_('Allow anonymous comments'),
  74.                     'note' => T_('Allow non-registered visitors to leave comments.'),
  75.                     'defaultvalue' => '1',
  76.                 ),
  77.                 'check_dupes' => array(
  78.                     'type' => 'checkbox',
  79.                     'label' => T_('Detect feedback duplicates'),
  80.                     'note' => T_('Check this to check comments and trackback for duplicate content.'),
  81.                     'defaultvalue' => '1',
  82.                 ),
  83.                 'max_number_of_links_feedback' => array(
  84.                     'type' => 'integer',
  85.                     'label' => T_('Feedback sensitivity to links'),
  86.                     'note' => T_('If a comment has more than this number of links in it, it will get 100 percent spam karma. -1 to disable it.'),
  87.                     'help' => '#set_max_number_of_links',
  88.                     'defaultvalue' => '4',
  89.                     'size' => 3,
  90.                 ),
  91.                 'nofollow_for_hours' => array(
  92.                     'type' => 'integer',
  93.                     'label' => T_('Apply rel="nofollow"'),
  94.                     'note'=>T_('hours. For how long should rel="nofollow" be applied to comment links? (0 means never, -1 means always)'),
  95.                     'defaultvalue' => '-1'// use "nofollow" infinitely by default so lazy admins won't promote spam
  96.                     'size' => 5,
  97.                 ),
  98.  
  99.                 'check_url_referers' => array(
  100.                     'type' => 'checkbox',
  101.                     'label' => T_('Check referers for URL'),
  102.                     'note' => T_('Check refering pages, if they contain our URL. This may generate a lot of additional traffic!'),
  103.                     'defaultvalue' => '0',
  104.                 ),
  105.  
  106.             );
  107.     }
  108.  
  109.  
  110.     /**
  111.      * We check if this is an anonymous visitor and do not allow comments, if we're setup
  112.      * to do so.
  113.      */
  114.     function ItemCanComment$params )
  115.     {
  116.         ifis_logged_in(&& $this->Settings->get('allow_anon_comments') )
  117.         {
  118.             return T_('Comments are not allowed from anonymous visitors.');
  119.         }
  120.  
  121.         // return NULL
  122.     }
  123.  
  124.  
  125.     /**
  126.      * Handle max_number_of_links_feedback setting.
  127.      *
  128.      * Try to detect as many links as possible
  129.      */
  130.     function GetSpamKarmaForComment$params )
  131.     {
  132.         $max_comments $this->Settings->get('max_number_of_links_feedback');
  133.         if$max_comments != -)
  134.         // not deactivated:
  135.             $count preg_match_all'~(https?|ftp)://~i'$params['Comment']->content$matches );
  136.  
  137.             if$count $max_comments )
  138.             {
  139.                 return 100;
  140.             }
  141.  
  142.             if$count == )
  143.             {
  144.                 return 0;
  145.             }
  146.  
  147.             return (100/$max_comments$count;
  148.         }
  149.     }
  150.  
  151.  
  152.     /**
  153.      * Disable/Enable events according to settings.
  154.      *
  155.      * "AppendHitLog" gets enabled according to check_url_referers setting.
  156.      * "BeforeTrackbackInsert" gets disabled, if we do not check for duplicate content.
  157.      */
  158.     function BeforeEnable()
  159.     {
  160.         if$this->Settings->get('check_url_referers') )
  161.         {
  162.             $this->enable_event'AppendHitLog' );
  163.         }
  164.         else
  165.         {
  166.             $this->disable_event'AppendHitLog' );
  167.         }
  168.  
  169.         if$this->Settings->get('check_dupes') )
  170.         {
  171.             $this->disable_event'BeforeTrackbackInsert' );
  172.         }
  173.         else
  174.         {
  175.             $this->enable_event'BeforeTrackbackInsert' );
  176.         }
  177.  
  178.         return true;
  179.     }
  180.  
  181.  
  182.     /**
  183.      * - Check for duplicate trackbacks.
  184.      */
  185.     function BeforeTrackbackInsert$params )
  186.     {
  187.         if$this->is_duplicate_comment$params['Comment') )
  188.         {
  189.             $this->msgT_('The trackback seems to be a duplicate.')'error' );
  190.         }
  191.     }
  192.  
  193.  
  194.     /**
  195.      * Check for duplicate comments.
  196.      */
  197.     function BeforeCommentFormInsert$params )
  198.     {
  199.         if$this->is_duplicate_comment$params['Comment') )
  200.         {
  201.             $this->msgT_('The comment seems to be a duplicate.')'error' );
  202.         }
  203.     }
  204.  
  205.  
  206.     /**
  207.      * If we use "makelink", handle nofollow rel attrib.
  208.      *
  209.      * @uses basic_antispam_plugin::apply_nofollow()
  210.      */
  211.     function FilterCommentAuthor$params )
  212.     {
  213.         if$params['makelink')
  214.         {
  215.             return false;
  216.         }
  217.  
  218.         $this->apply_nofollow$params['data']$params['Comment');
  219.     }
  220.  
  221.  
  222.     /**
  223.      * Handle nofollow in author URL (if it's made clickable)
  224.      *
  225.      * @uses basic_antispam_plugin::FilterCommentAuthor()
  226.      */
  227.     function FilterCommentAuthorUrl$params )
  228.     {
  229.         $this->FilterCommentAuthor$params );
  230.     }
  231.  
  232.  
  233.     /**
  234.      * Handle nofollow rel attrib in comment content.
  235.      *
  236.      * @uses basic_antispam_plugin::FilterCommentAuthor()
  237.      */
  238.     function FilterCommentContent$params )
  239.     {
  240.         $this->apply_nofollow$params['data']$params['Comment');
  241.     }
  242.  
  243.  
  244.     /**
  245.      * Do we want to apply rel="nofollow" tag?
  246.      *
  247.      * @return boolean 
  248.      */
  249.     function apply_nofollow$data$Comment )
  250.     {
  251.         global $localtimenow;
  252.  
  253.         $hours $this->Settings->get('nofollow_for_hours')// 0=never, -1 always, otherwise for x hours
  254.  
  255.         if$hours == )
  256.         // "never"
  257.             return;
  258.         }
  259.  
  260.         if$hours // -1 is "always"
  261.             && mysql2timestamp$Comment->date <= $localtimenow $hours*3600 ) )
  262.         {
  263.             return;
  264.         }
  265.  
  266.         $data preg_replace_callback'~(<a\s)([^>]+)>~i'create_function'$m''
  267.                 if( preg_match( \'~\brel=([\\\'"])(.*?)\1~\', $m[2], $match ) )
  268.                 { // there is already a rel attrib:
  269.                     $rel_values = explode( " ", $match[2] );
  270.  
  271.                     if( ! in_array( \'nofollow\', $rel_values ) )
  272.                     {
  273.                         $rel_values[] = \'nofollow\';
  274.                     }
  275.  
  276.                     return $m[1]
  277.                         .preg_replace(
  278.                             \'~\brel=([\\\'"]).*?\1~\',
  279.                             \'rel=$1\'.implode( " ", $rel_values ).\'$1\',
  280.                             $m[2] )
  281.                         .">";
  282.                 }
  283.                 else
  284.                 {
  285.                     return $m[1].$m[2].\' rel="nofollow">\';
  286.                 }' )$data );
  287.     }
  288.  
  289.  
  290.     /**
  291.      * Check if the deprecated hit_doublecheck_referer setting is set and then
  292.      * do not disable the AppendHitLog event. Also removes the old setting.
  293.      */
  294.     function AfterInstall()
  295.     {
  296.         global $Settings;
  297.  
  298.         if$Settings->get('hit_doublecheck_referer') )
  299.         // old general settings, "transform it"
  300.             $this->Settings->set'check_url_referers''1' );
  301.             $this->Settings->dbupdate();
  302.         }
  303.  
  304.         $Settings->delete('hit_doublecheck_referer');
  305.         $Settings->dbupdate();
  306.     }
  307.  
  308.  
  309.     /**
  310.      * Check if our Host+URI is in the referred page, preferrably through
  311.      * {@link register_shutdown_function()}.
  312.      *
  313.      * @return boolean true, if we handle {@link Hit::record_the_hit() recording of the Hit} ourself
  314.      */
  315.     function AppendHitLog$params )
  316.     {
  317.         global $debug_no_register_shutdown;
  318.  
  319.         $Hit $params['Hit'];
  320.  
  321.         if$Hit->referer_type != 'referer' )
  322.         {
  323.             return false;
  324.         }
  325.  
  326.         ifempty($debug_no_register_shutdown&& function_exists'register_shutdown_function' ) )
  327.         // register it as a shutdown function, because it will be slow!
  328.             $this->debug_log'AppendHitLog: loading referering page.. (through register_shutdown_function())' );
  329.  
  330.             register_shutdown_functionarray&$this'double_check_referer' )$Hit->referer )// this will also call Hit::record_the_hit()
  331.         }
  332.         else
  333.         {
  334.             // flush now, so that the meat of the page will get shown before it tries to check back against the refering URL.
  335.             flush();
  336.  
  337.             $this->debug_log'AppendHitLog: loading referering page..' );
  338.  
  339.             $this->double_check_referer($Hit->referer)// this will also call Hit::record_the_hit()
  340.         }
  341.  
  342.         return true// we handle recording
  343.     }
  344.  
  345.  
  346.     /**
  347.      * This function gets called (as a {@link register_shutdown_function() shutdown function}, if possible) and checks
  348.      * if the referering URL's content includes the current URL - if not it is probably spam!
  349.      *
  350.      * On success, this methods records the hit.
  351.      *
  352.      * @uses Hit::record_the_hit()
  353.      */
  354.     function double_check_referer$referer )
  355.     {
  356.         global $Hit$ReqURI;
  357.  
  358.         if$this->is_referer_linking_us$referer$ReqURI ) )
  359.         {
  360.             $Hit->record_the_hit();
  361.         }
  362.  
  363.         return;
  364.     }
  365.  
  366.  
  367.     /**
  368.      * Check the content of a given URL (referer), if the requested URI (with different hostname variations)
  369.      * is present.
  370.      *
  371.      * @todo Use DB cache to avoid checking the same page again and again! (Plugin DB table)
  372.      *
  373.      * @param string 
  374.      * @param string URI to append to matching pattern for hostnames
  375.      * @return boolean 
  376.      */
  377.     function is_referer_linking_us$referer$uri )
  378.     {
  379.         global $misc_inc_path$lib_subdir$ReqHost;
  380.  
  381.         ifempty($referer) )
  382.         {
  383.             return false;
  384.         }
  385.  
  386.         // Load page content (max. 500kb), using fsockopen:
  387.         $url_parsed parse_url($referer);
  388.         ifempty($url_parsed['scheme']) ) {
  389.             $url_parsed parse_url('http://'.$referer);
  390.         }
  391.  
  392.         $host $url_parsed['host'];
  393.         $port empty($url_parsed['port']80 $url_parsed['port');
  394.         $path empty($url_parsed['path']'/' $url_parsed['path'];
  395.         ifempty($url_parsed['query']) )
  396.         {
  397.             $path .= '?'.$url_parsed['query'];
  398.         }
  399.  
  400.         $fp @fsockopen($host$port$errno$errstr30);
  401.         if$fp )
  402.         // could not access referring page
  403.             $this->debug_log'is_referer_linking_us(): could not access &laquo;'.$referer.'&raquo; (host: '.$host.'): '.$errstr.' (#'.$errno.')' );
  404.             return false;
  405.         }
  406.  
  407.         // Set timeout for data:
  408.         iffunction_exists('stream_set_timeout') )
  409.             stream_set_timeout$fp20 )// PHP 4.3.0
  410.         else
  411.             socket_set_timeout$fp20 )// PHP 4
  412.  
  413.         // Send request:
  414.         $out "GET $path HTTP/1.0\r\n";
  415.         $out .= "Host: $host:$port\r\n";
  416.         $out .= "Connection: Close\r\n\r\n";
  417.         fwrite($fp$out);
  418.  
  419.         // Skip headers:
  420.         $i 0;
  421.         $source_charset 'iso-8859-1'// default
  422.         while( ($s fgets($fp4096)) !== false )
  423.         {
  424.             $i++;
  425.             if$s == "\r\n" || $i 100 /* max 100 head lines */ )
  426.             {
  427.                 break;
  428.             }
  429.             ifpreg_match('~^Content-Type:.*?charset=([\w-]+)~i'$s$match ) )
  430.             {
  431.                 $source_charset $match[1];
  432.             }
  433.         }
  434.  
  435.         // Get the refering page's content
  436.         $content_ref_page '';
  437.         $bytes_read 0;
  438.         while( ($s fgets($fp4096)) !== false )
  439.         {
  440.             $content_ref_page .= $s;
  441.             $bytes_read += strlen($s);
  442.             if$bytes_read 512000 )
  443.             // do not pull more than 500kb of data!
  444.                 break;
  445.             }
  446.         }
  447.         fclose($fp);
  448.  
  449.         ifstrlen($content_ref_page) )
  450.         {
  451.             $this->debug_log'is_referer_linking_us(): empty $content_ref_page ('.bytesreadable($bytes_read).' read)' );
  452.             return false;
  453.         }
  454.  
  455.  
  456.         /**
  457.          * IDNA converter class
  458.          */
  459.         require_once $misc_inc_path.'ext/_idna_convert.class.php';
  460.         $IDNA new Net_IDNA_php4();
  461.  
  462.         $have_idn_name false;
  463.  
  464.         // Build the search pattern:
  465.         // We match for basically for 'href="[SERVER][URI]', where [SERVER] is a list of possible hosts (especially IDNA)
  466.         $search_pattern '~\shref=["\']?https?://(';
  467.         $possible_hosts array$_SERVER['HTTP_HOST');
  468.         if$_SERVER['SERVER_NAME'!= $_SERVER['HTTP_HOST')
  469.         {
  470.             $possible_hosts[$_SERVER['SERVER_NAME'];
  471.         }
  472.         $search_pattern_hosts array();
  473.         foreach$possible_hosts as $l_host )
  474.         {
  475.             ifpreg_match'~^([^.]+\.)(.*?)([^.]+\.[^.]+)$~'$l_host$match ) )
  476.             // we have subdomains in this hostname
  477.                 ifstristr$match[1]'www' ) )
  478.                 // search also for hostname without 'www.'
  479.                     $search_pattern_hosts[$match[2].$match[3];
  480.                 }
  481.             }
  482.             $search_pattern_hosts[$l_host;
  483.         }
  484.         $search_pattern_hosts array_unique($search_pattern_hosts);
  485.         foreach$search_pattern_hosts as $l_host )
  486.         // add IDN, because this could be linked:
  487.             $l_idn_host $IDNA->decode$l_host )// the decoded puny-code ("xn--..") name (utf8)
  488.  
  489.             if$l_idn_host != $l_host )
  490.             {
  491.                 $have_idn_name true;
  492.                 $search_pattern_hosts[$l_idn_host;
  493.             }
  494.         }
  495.  
  496.         // add hosts to pattern, preg_quoted
  497.         for$i 0$n count($search_pattern_hosts)$i $n$i++ )
  498.         {
  499.             $search_pattern_hosts[$ipreg_quote$search_pattern_hosts[$i]'~' );
  500.         }
  501.         $search_pattern .= implode'|'$search_pattern_hosts ).')';
  502.         ifempty($uri) )
  503.         // host(s) should end with "/", "'", '"', "?" or whitespace
  504.             $search_pattern .= '[/"\'\s?]';
  505.         }
  506.         else
  507.         {
  508.             $search_pattern .= preg_quote($uri'~');
  509.             // URI should end with "'", '"' or whitespace
  510.             $search_pattern .= '["\'\s]';
  511.         }
  512.         $search_pattern .= '~i';
  513.  
  514.         if$have_idn_name )
  515.         // Convert charset to UTF-8, because the decoded domain name is UTF-8, too:
  516.             ifcan_convert_charsets'utf-8'$source_charset ) )
  517.             {
  518.                 $content_ref_page convert_charset$content_ref_page'utf-8'$source_charset );
  519.             }
  520.             else
  521.             {
  522.                 $this->debug_log'is_referer_linking_us(): warning: cannot convert charset of referring page' );
  523.             }
  524.         }
  525.  
  526.         ifpreg_match$search_pattern$content_ref_page ) )
  527.         {
  528.             $this->debug_log'is_referer_linking_us(): found current URL in page ('.bytesreadable($bytes_read).' read)' );
  529.  
  530.             return true;
  531.         }
  532.         else
  533.         {
  534.             ifstrpos$referer$ReqHost === && empty($uri) )
  535.             // Referer is the same host.. just search for $uri
  536.                 ifstrpos$content_ref_page$uri !== false )
  537.                 {
  538.                     $this->debug_log'is_referer_linking_us(): found current URI in page ('.bytesreadable($bytes_read).' read)' );
  539.  
  540.                     return true;
  541.                 }
  542.             }
  543.             $this->debug_log'is_referer_linking_us(): '.sprintf('did not find &laquo;%s&raquo; in &laquo;%s&raquo; (%s bytes read).'$search_pattern$refererbytesreadable($bytes_read) ) );
  544.  
  545.             return false;
  546.         }
  547.     }
  548.  
  549.  
  550.     /**
  551.      * Simple check for duplicate comment/content from same author
  552.      *
  553.      * @param Comment 
  554.      */
  555.     function is_duplicate_comment$Comment )
  556.     {
  557.         global $DB;
  558.  
  559.         if$this->Settings->get('check_dupes') )
  560.         {
  561.             return false;
  562.         }
  563.  
  564.         $sql '
  565.                 SELECT comment_ID
  566.                   FROM T_comments
  567.                  WHERE comment_post_ID = '.$Comment->item_ID;
  568.  
  569.         ifisset($Comment->author_user_ID) )
  570.         // registered user:
  571.             $sql .= ' AND comment_author_ID = '.$Comment->author_user_ID;
  572.         }
  573.         else
  574.         // visitor (also trackback):
  575.             $sql_ors array();
  576.             ifempty($Comment->author) )
  577.             {
  578.                 $sql_ors['comment_author = '.$DB->quote($Comment->author);
  579.             }
  580.             ifempty($Comment->author_email) )
  581.             {
  582.                 $sql_ors['comment_author_email = '.$DB->quote($Comment->author_email);
  583.             }
  584.             ifempty($Comment->author_url) )
  585.             {
  586.                 $sql_ors['comment_author_url = '.$DB->quote($Comment->author_url);
  587.             }
  588.  
  589.             ifempty($sql_ors) )
  590.             {
  591.                 $sql .= ' AND ( '.implode' OR '$sql_ors ).' )';
  592.             }
  593.         }
  594.  
  595.         $sql .= ' AND comment_content = '.$DB->quote($Comment->content).' LIMIT 1';
  596.  
  597.         return $DB->get_var$sql00'Checking for duplicate feedback content.' );
  598.     }
  599.  
  600.  
  601.     /**
  602.      * A little housekeeping.
  603.      * @return true 
  604.      */
  605.     function PluginVersionChanged$params )
  606.     {
  607.         $this->Settings->delete('check_url_trackbacks');
  608.         $this->Settings->dbupdate();
  609.         return true;
  610.     }
  611.  
  612. }
  613.  
  614.  
  615. /*
  616.  * $Log: _basic_antispam.plugin.php,v $
  617.  * Revision 1.23.2.5  2007/01/30 20:04:23  blueyed
  618.  * MFH: Fixed "basic antispam de-activates itself"
  619.  *
  620.  * Revision 1.23.2.4  2006/12/26 03:18:51  fplanque
  621.  * assigned a few significant plugin groups
  622.  *
  623.  * Revision 1.23.2.3  2006/12/21 16:15:15  blueyed
  624.  * MFH: Basic Antispam Plugin:
  625.  * - Use fsockopen instead of url fopen to get refering page contents
  626.  * - Removed "check_url_trackbacks" setting: it has been unreliable and is against the trackback specs anyway. This is what pingbacks are for.
  627.  * - Convert charset of the refering page contents, if we have a decoded/utf-8 encoded IDN
  628.  * - Some improvements to matching pattern
  629.  *
  630.  * Revision 1.23.2.2  2006/11/04 19:55:13  fplanque
  631.  * Reinjected old Log blocks. Removing them from CVS was a bad idea -- especially since Daniel has decided branch 1.9 was his HEAD...
  632.  *
  633.  * Revision 1.23  2006/07/10 20:19:31  blueyed
  634.  * Fixed PluginInit behaviour. It now gets called on both installed and non-installed Plugins, but with the "is_installed" param appropriately set.
  635.  *
  636.  * Revision 1.22  2006/07/07 21:26:49  blueyed
  637.  * Bumped to 1.9-dev
  638.  *
  639.  * Revision 1.21  2006/07/07 19:28:32  blueyed
  640.  * Trans fix. "%" would need to be escaped.. :/
  641.  *
  642.  * Revision 1.20  2006/06/22 19:47:06  blueyed
  643.  * "Block spam referers" as global option
  644.  *
  645.  * Revision 1.19  2006/06/16 21:30:57  fplanque
  646.  * Started clean numbering of plugin versions (feel free do add dots...)
  647.  *
  648.  * Revision 1.18  2006/06/05 17:45:06  blueyed
  649.  * Disable events at settings time, according to Settings checkboxes.
  650.  *
  651.  * Revision 1.17  2006/06/01 18:36:10  fplanque
  652.  * no message
  653.  *
  654.  * Revision 1.16  2006/05/30 21:25:27  blueyed
  655.  * todo-question
  656.  *
  657.  * Revision 1.15  2006/05/30 20:32:57  blueyed
  658.  * Lazy-instantiate "expensive" properties of Comment and Item.
  659.  *
  660.  * Revision 1.14  2006/05/30 19:39:56  fplanque
  661.  * plugin cleanup
  662.  *
  663.  * Revision 1.13  2006/05/30 00:18:29  blueyed
  664.  * http://dev.b2evolution.net/todo.php?p=87686
  665.  *
  666.  * Revision 1.12  2006/05/29 21:13:19  fplanque
  667.  * no message
  668.  *
  669.  * Revision 1.11  2006/05/29 21:03:07  fplanque
  670.  * Also count links if < tags have been filtered before!
  671.  *
  672.  * Revision 1.10  2006/05/20 01:56:07  blueyed
  673.  * ItemCanComment hook; "disable anonymous feedback" through basic antispam plugin
  674.  *
  675.  * Revision 1.9  2006/05/14 16:30:37  blueyed
  676.  * SQL error fixed with empty visitor comments
  677.  *
  678.  * Revision 1.8  2006/05/12 21:35:24  blueyed
  679.  * Apply karma by number of links in a comment. Note: currently the default is to not allow A tags in comments!
  680.  *
  681.  * Revision 1.7  2006/05/02 22:43:39  blueyed
  682.  * typo
  683.  *
  684.  * Revision 1.6  2006/05/02 15:32:01  blueyed
  685.  * Moved blocking of "spam referers" into basic antispam plugin: does not block backoffice requests in general and can be easily get disabled.
  686.  *
  687.  * Revision 1.5  2006/05/02 04:36:25  blueyed
  688.  * Spam karma changed (-100..100 instead of abs/max); Spam weight for plugins; publish/delete threshold
  689.  *
  690.  * Revision 1.4  2006/05/02 01:27:55  blueyed
  691.  * Moved nofollow handling to basic antispam plugin; added Filter events to Comment class
  692.  *
  693.  * Revision 1.3  2006/05/01 05:20:38  blueyed
  694.  * Check for duplicate content in comments/trackback.
  695.  *
  696.  * Revision 1.2  2006/05/01 04:25:07  blueyed
  697.  * Normalization
  698.  *
  699.  * Revision 1.1  2006/04/29 23:11:23  blueyed
  700.  * Added basic_antispam_plugin; Moved double-check-referers there; added check, if trackback links to us
  701.  *
  702.  */
  703. ?>

Documentation generated on Tue, 18 Dec 2007 19:10:48 +0100 by phpDocumentor 1.4.0