'ip', 'referrer' => 'referer', 'email' => 'email', 'url' => 'url', 'name' => 'author', 'body' => 'body' ); var $path; function introspect(&$propbag) { global $serendipity; $this->title = PLUGIN_EVENT_SPAMBLOCK_BAYES_NAME; $propbag->add ( 'description', PLUGIN_EVENT_SPAMBLOCK_BAYES_DESC); $propbag->add ( 'name', $this->title); $propbag->add ( 'version', '0.5.5' ); $propbag->add ( 'event_hooks', array ('frontend_saveComment' => true, 'backend_spamblock_comments_shown' => true, 'external_plugin' => true, 'backend_view_comment' => true, 'backend_comments_top' => true, 'backend_sendcomment' => true, 'backend_sidebar_entries' => true, 'backend_sidebar_admin_appearance' => true, 'backend_sidebar_entries_event_display_spamblock_bayes' => true, 'xmlrpc_comment_spam' => true, 'xmlrpc_comment_ham' => true, )); $propbag->add ( 'groups', array ('ANTISPAM' ) ); $propbag->add ( 'author', 'kleinerChemiker, Malte Paskuda, based upon b8 by Tobias Leupold'); $propbag->add('configuration', array( 'method', 'moderateBarrier', 'blockBarrier', 'autolearn', 'ignore', 'menu', 'recycler', 'recyclerdelete', 'emptyAll', 'path', 'logtype', 'logfile' )); $propbag->add('legal', array( 'services' => array( ), 'frontend' => array( 'Anti-Spam measurements by this plugin can transfer user data and metadata (??? plugin description missing ???)', 'All user data and metadata (IP address, comment fields) can be logged to database or file' ), 'backend' => array( ), 'cookies' => array( ), 'stores_user_input' => true, 'stores_ip' => true, 'uses_ip' => true, 'transmits_user_input' => true )); } function introspect_config_item($name, &$propbag) { global $serendipity; switch($name) { case 'method': $propbag->add('type', 'select'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_METHOD); $propbag->add('select_values', array( 'moderate' => PLUGIN_EVENT_SPAMBLOCK_BAYES_METHOD_MODERATE, 'block' => PLUGIN_EVENT_SPAMBLOCK_BAYES_METHOD_BLOCK, 'custom' => PLUGIN_EVENT_SPAMBLOCK_BAYES_METHOD_CUSTOM, )); $propbag->add('default', 'moderation'); break; case 'moderateBarrier': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_BARRIER_MODERATE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_BARRIER_MODERATE_DESC); $propbag->add('default', 70); break; case 'blockBarrier': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_BARRIER_BLOCK); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_BARRIER_BLOCK_DESC); $propbag->add('default', 90); break; case 'autolearn': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_AUTOLEARN); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_AUTOLEARN_DESC); $propbag->add('default', false); break; case 'menu': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_DESC); $propbag->add('default', true); break; case 'recycler': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_RECYCLER); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_DESC); $propbag->add('default', true); break; case 'recyclerdelete': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_DELETE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_DELETE_DESC); $propbag->add('default', ''); return true; break; case 'emptyAll': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_EMPTY_ALL); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_EMPTY_ALL_DESC); $propbag->add('default', false); break; case 'path': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_PATH); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_PATH_DESC); $propbag->add('default', $serendipity['serendipityHTTPPath'] . 'plugins/serendipity_event_spamblock_bayes/'); return true; break; case 'logfile': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGFILE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGFILE_DESC); $propbag->add('default', $serendipity['serendipityPath'] . 'spamblock-bayes.log'); break; case 'logtype': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGTYPE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGTYPE_DESC); $propbag->add('default', 'none'); $propbag->add('radio', array( 'value' => array('file', 'db', 'none'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGTYPE_FILE, PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGTYPE_DB, PLUGIN_EVENT_SPAMBLOCK_BAYES_LOGTYPE_NONE) )); $propbag->add('radio_per_row', '1'); break; case 'ignore': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BAYES_IGNORE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BAYES_IGNORE_DESC); $propbag->add('default', ''); return true; break; default: return false; } return true; } function generate_content(&$title) { $title = $this->title; } function install() { $this->setupDB(); } function learnFromOld() { global $serendipity; //approved comments are ham $sql = "SELECT author,email,url,body,ip,referer FROM {$serendipity['dbPrefix']}comments WHERE status = 'approved' LIMIT 100;"; $ham_comments = serendipity_db_query ( $sql ); if (is_array($ham_comments[0])) { foreach ($ham_comments as $comment) { $this->startLearn($comment, 'ham'); } } //maybe unset helps against the ram-issue unset($ham_comments); //learn via the spamblock-log what is spam: $sql = "SELECT author,email,url,body,ip,referer FROM {$serendipity['dbPrefix']}spamblocklog WHERE type = 'REJECTED' LIMIT 100;"; $spam_comments = serendipity_db_query ( $sql ); if (is_array($spam_comments[0])) { foreach ($spam_comments as $comment) { $this->startLearn($comment, 'spam'); } } } /* * get ratings of every part of the comment and combine * Wrapper for classify() */ function startClassify($comment) { $divider = 0; $ratings = array(); $types = array_keys($this->type); foreach($types as $type) { $rating = $this->classify($comment[$this->type[$type]], $this->type[$type]); if (is_numeric($rating)) { $ratings[] = $rating; $divider++; } } #catch error when failing to rate anything if (empty($ratings)) { return 0; } if($this->get_config('method', 'moderate') == 'custom') { $spamBarrier = max(array( $this->get_config('moderateBarrier', 70) / 100, $this->get_config('blockBarrier', 90) / 100 )); } else { $spamBarrier = 0.9; } #If a field is clearly spam, a spammer probably mixed #its spam with valid content to fool the spamfilter. $max_ratings = array(); $min_ratings = array(); foreach ($ratings as $rating) { if ($rating >= $spamBarrier) { $max_ratings[] = $rating; } if ($rating <= 0.1) { $min_ratings[] = $rating; } } if (count($max_ratings) > count($min_ratings)) { return max($ratings); } return (array_sum($ratings) / $divider); } #Wrapper to call learn() function startLearn($comment, $category) { $types = array_keys($this->type); foreach ($types as $type) { $this->learn($comment[$this->type[$type]], $category, $this->type[$type]); } } /* * classify a string in the boundaries of 0 (ham) to 1 (spam) * */ function classify($comment = '', $type) { global $serendipity; $ignore = explode(',', $this->get_config('ignore', '')); if (in_array($type, $ignore)) { //we ignore fields on the ignorelist return; } $spam_texts = $this->get_config("{$type}_spam", 0); $ham_texts = $this->get_config("{$type}_ham", 0); if ($comment == '' && ! is_string($comment)) { return false; } if ($spam_texts == 0 || $ham_texts == 0) { return false; } if ($type == $this->type['ip']) { $tokens = array($comment => 1); } else { $tokens = $this->tokenize($comment); } if ($tokens === false|| empty($tokens)) { return false; } $words = array_keys($tokens); foreach ($words AS $word) { $temp[] = '\'' . serendipity_db_escape_string($word) . '\''; } #Die gespeicherten Werte der Tokens aus DB holen $sql = 'SELECT token, ham, spam FROM ' . $serendipity ['dbPrefix'] . 'spamblock_bayes WHERE ' . serendipity_db_in_sql ( 'token', $temp ) .' AND type = \''. $type .'\''; unset ($temp); $stored_tokens = serendipity_db_query ( $sql, FALSE, 'assoc', FALSE, 'token' ); foreach($tokens as $word => $count) { $word_count[$word] = $count; if (!isset($stored_tokens[$word])) { $rating = 1; } else if (empty($word)) { continue; } else { $rating = ($stored_tokens[$word]['ham'] / $ham_texts) / (($stored_tokens[$word]['ham'] / $ham_texts) + ($stored_tokens[$word]['spam'] / $spam_texts)); } $ratings[$word] = (0.15 + (($stored_tokens[$word]['ham'] + $stored_tokens[$word]['spam']) * $rating)) / (0.3 + $stored_tokens[$word]['ham'] + $stored_tokens[$word]['spam']); # Importance (distance to 0.5) $importance[$word] = abs(0.5 - $ratings[$word]); } //importance can be null if the comment don't contains real tokens //(like the smiley :) ) if (is_array($importance)) { arsort($importance); reset($importance); } #number of important words $n = 0; $probability = 0; foreach($tokens as $word => $count) { if ($importance[$word] > 0.2) { $n++; $probability += $ratings[$word]; } } if ( $n > 0 ) { $probability = $probability / $n; } else { // was: = 1, but if undecided, we better want to be at // "undecided" than "ham" $probability = 0.5; } return abs(1 - $probability); } /* * learn string as ham or spam * $text: string * $category: string (ham, spam) * $type: string (ip, body, ...) **/ function learn($text, $group, $type) { global $serendipity; $this->setupDB(); if ($group != 'ham' && $group != 'spam') { return FALSE; } if ($text == '' or ! is_string ($text)) { return FALSE; } #split text in tokens if ($type == $this->type['ip']) { $tokens = array( $text => 1); } else { $tokens = $this->tokenize($text); } $words = array_keys($tokens); foreach ($words AS $word) { $temp[] = '\'' . serendipity_db_escape_string($word) . '\''; } #get already saved value of tokens $sql = 'SELECT token, ' . $group . ' FROM ' . $serendipity ['dbPrefix'] . 'spamblock_bayes WHERE ' . serendipity_db_in_sql('token', $temp) . 'AND type = \'' . $type . '\' '; unset ($temp); $stored_values = serendipity_db_query ( $sql, FALSE, 'assoc', FALSE, 'token', $group ); #Save new amount of all tokens foreach ($tokens as $token => $value) { if (isset ($stored_values [$token])) { $new_value [$token] = $stored_values [$token] + $value; if ($serendipity['dbType'] == 'mysql' || $serendipity['dbType'] == 'mysqli') { $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, $group, type) VALUES('$token', $value, '$type') ON DUPLICATE KEY UPDATE $group = $group + VALUES($group);"; } else { $sql = "UPDATE {$serendipity['dbPrefix']}spamblock_bayes SET $group = $group + $value WHERE token = '$token' AND type = '$type';"; } } else { $new_value [$token] = $value; if ($serendipity['dbType'] == 'mysql' || $serendipity['dbType'] == 'mysqli') { $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, $group, type) VALUES('$token', $value, '$type') ON DUPLICATE KEY UPDATE $group = $group + VALUES($group);"; } else { $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, $group, type) VALUES('$token', $value, '$type')"; } } serendipity_db_query ($sql); } #Save amount of ham/spam $this->set_config("{$type}_{$group}", $this->get_config("{$type}_{$group}", 0) + 1); return true; } /* * Split text in words * param1: string $text * return: array Tokens **/ function tokenize($text = '') { if ($text == '' or ! is_string($text)) { return false; } //preg_split won't accept e.g. Umlaute as part of \w mb_regex_encoding('UTF-8'); $tokens = mb_split("\W", $text ); #preg_match_all('/[\w]+/u', "aaa�bbb", $words); $temp = array (); foreach ( $tokens as $token ) { if (isset ( $temp ["$token"] )) { $temp ["$token"] ++; } else { $temp ["$token"] = 1; } } #prevent the whitespaces to get saved in the database, they #would displace more important markers if (isset($temp[""])) { unset($temp[""]); } return $temp; } function getAmount($category, $type) { global $serendipity; $sql = "SELECT $category FROM {$serendipity['dbPrefix']}spamblock_bayes WHERE type = '$type'"; $ratings = serendipity_db_query($sql); $amount = 0; if (is_array($ratings)) { foreach($ratings as $rating) { $amount += $rating[0]; } } return $amount; } /** * initialize the db at first install or change after upgrade * */ function setupDB() { global $serendipity; #main-table $sql = "CREATE TABLE IF NOT EXISTS {$serendipity['dbPrefix']}spamblock_bayes ( token VARCHAR(100) NOT NULL, ham BIGINT UNSIGNED NOT NULL DEFAULT '0', spam BIGINT UNSIGNED NOT NULL DEFAULT '0', type VARCHAR(20) DEFAULT '{$this->type['body']}' ) {UTF_8};"; serendipity_db_schema_import($sql); #recycler-table switch ($serendipity['dbType']) { case 'mysql': case 'mysqli': $sql = "CREATE TABLE IF NOT EXISTS {$serendipity['dbPrefix']}spamblock_bayes_recycler LIKE {$serendipity['dbPrefix']}comments"; break; case 'sqlite': case 'sqlite3': case 'pdo-sqlite': case 'pdo-sqliteoo': $sql = "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '{$serendipity['dbPrefix']}comments';"; $sql = serendipity_db_query($sql); if (is_array($sql)) { $sql = $sql[0][0]; } $sql = str_replace("{$serendipity['dbPrefix']}comments", "{$serendipity['dbPrefix']}spamblock_bayes_recycler", $sql); if (strpos("sql", "NOT EXISTS") === false) { $sql = str_replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS", $sql); } break; default: $sql = "CREATE TABLE IF NOT EXISTS {$serendipity['dbPrefix']}spamblock_bayes_recycler as SELECT * FROM {$serendipity['dbPrefix']}comments ORDER BY id LIMIT 1 WITH NO DATA"; } serendipity_db_query($sql); $dbversion = $this->get_config('dbversion', 1); if ($dbversion == '1') { $this->updateDB1(); } $dbversion = $this->get_config('dbversion', 1); if ($dbversion == '2') { $this->updateDB2(); } } #when upgrading to 0.3, type has to get added function updateDB1() { global $serendipity; $sql = "ALTER TABLE {$serendipity['dbPrefix']}spamblock_bayes ADD type VARCHAR(20) DEFAULT '{$this->type['body']}'"; serendipity_db_query($sql); $sql = "ALTER TABLE {$serendipity['dbPrefix']}spamblock_bayes DROP {PRIMARY}"; serendipity_db_schema_import($sql); $this->set_config($this->type['body'] .'_spam' , $this->get_config('spam', 0)); $this->set_config($this->type['body'] . '_ham' , $this->get_config('ham', 0)); $this->set_config('dbversion', 2); } #when upgrading to 0.3.9 #This Upgrade shall give a perfomance-boost which is needed #for proper import/export in large databases function updateDB2() { global $serendipity; set_time_limit(0); serendipity_db_begin_transaction(); #Under mySQL, we may have duplicates in the Database (hello, #Hello) which prevent us from using an index. So we remove them. $sql1 = "CREATE TEMPORARY TABLE {$serendipity['dbPrefix']}spamblock_bayes_temp ( token VARCHAR(100) NOT NULL, ham BIGINT UNSIGNED NOT NULL DEFAULT '0', spam BIGINT UNSIGNED NOT NULL DEFAULT '0', type VARCHAR(20) DEFAULT '{$this->type['body']}', {PRIMARY} (token, type) ) {UTF_8};"; serendipity_db_schema_import($sql1); if ($serendipity['dbType'] == 'mysql' || $serendipity['dbType'] == 'mysqli') { $sql2 = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes_temp (token, ham, spam, type) SELECT orig.token, orig.ham, orig.spam, orig.type FROM {$serendipity['dbPrefix']}spamblock_bayes as orig ON DUPLICATE KEY UPDATE ham = {$serendipity['dbPrefix']}spamblock_bayes_temp.ham + VALUES(ham), spam = {$serendipity['dbPrefix']}spamblock_bayes_temp.spam + VALUES(spam);"; serendipity_db_query($sql2); } else { $sql = "SELECT token, ham, spam, type FROM {$serendipity['dbPrefix']}spamblock_bayes;"; $results = serendipity_db_query($sql); if (is_array($results)) { foreach ($results as $result) { $token = $result['token']; $ham = $result['ham']; $spam = $result['spam']; $type = $result['type']; $sql = "SELECT token FROM {$serendipity['dbPrefix']}spamblock_bayes_temp WHERE token = '$token' AND type = '$type';"; $tester = serendipity_db_query($sql); if (empty($tester['0'])) { $sql2 = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes_temp (token, ham, spam, type) VALUES ('$token', $ham, $spam, '$type');"; } else { $sql2 = "UPDATE {$serendipity['dbPrefix']}spamblock_bayes_temp WHERE token = '$token' AND type = '$type' SET ham = ham + $ham, spam = spam + $spam;"; } serendipity_db_query($sql2); } } } $sql3 = "DROP TABLE {$serendipity['dbPrefix']}spamblock_bayes;"; serendipity_db_query($sql3); $sql4 = "CREATE TABLE {$serendipity['dbPrefix']}spamblock_bayes ( token VARCHAR(100) NOT NULL, ham BIGINT UNSIGNED NOT NULL DEFAULT '0', spam BIGINT UNSIGNED NOT NULL DEFAULT '0', type VARCHAR(20) DEFAULT '{$this->type['body']}', {PRIMARY} (token, type) ) {UTF_8};"; serendipity_db_schema_import($sql4); $sql5 = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, ham, spam, type) SELECT token, ham, spam, type FROM {$serendipity['dbPrefix']}spamblock_bayes_temp; "; serendipity_db_schema_import($sql5); serendipity_db_end_transaction(true); $this->set_config('dbversion', 3); } function deleteDB() { global $serendipity; $sql = "DROP TABLE {$serendipity['dbPrefix']}spamblock_bayes"; serendipity_db_query($sql); foreach($this->type as $type) { $this->set_config("{$type}_ham", 0); $this->set_config("{$type}_spam", 0); } $this->set_config('dbversion', 1); } function checkIfSpam($comment) { $rating = $this->startClassify($comment); $this->lastRating = $rating; //a rating greater 0.8 is probably spam if ($rating >= 0.8) { $autolearn = $this->get_config('autolearn', false); if( ($rating > 0.9) && $autolearn) { $this->startLearn($comment, 'spam'); } return true; } return false; } function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get ( 'event_hooks' ); if (isset ( $hooks [$event] )) { switch ($event) { case 'external_plugin' : //catch learnAction here because the GET-Params prevent //the normal switch/case to find this if (strpos($eventData, 'learnAction') !== false) { $goodtoken = $this->is_goodtoken($_REQUEST['path'], $_REQUEST['id']); if (!serendipity_checkPermission('adminComments')&&!$goodtoken) { return; } $this->learnAction($_REQUEST['id'], $_REQUEST['category'], $_REQUEST['action'], $_REQUEST['entry_id']); echo DONE; return true; break; } switch ($eventData) { case 'learncomment': if (!serendipity_checkPermission('adminComments')) { break; } $category = $_REQUEST ['category']; $ids = $_REQUEST ['id']; $ids = explode(';', $ids); foreach($ids as $id) { $comment = $this->getComment($id); if (is_array ($comment)) { $comment = $comment['0']; $entry_id = $comment['entry_id']; } $this->startLearn($comment, $category); //Ham shall be approved, Spam deleted if ($category == 'ham') { serendipity_approveComment($id, $entry_id); } elseif ($category == 'spam') { if($this->get_config('method', 'moderate') == 'custom') { $spamBarrier = min(array( $this->get_config('moderateBarrier', 70) / 100, $this->get_config('blockBarrier', 90) / 100 )); } else { $spamBarrier = 0.7; } //spam shall not get through the filter twice - so make sure, it really is marked as spam $loop = 0; while ($this->startClassify($comment) < $spamBarrier && $loop < 5) { $this->startLearn($comment, $category); //prevent infinite loop $loop++; } if ($this->get_config('recycler', true)) { $this->recycleComment($id, $entry_id); } serendipity_deleteComment($id, $entry_id); } } break; case 'spamblock_bayes.load.gif': header('Content-Type: image/gif'); echo file_get_contents(dirname(__FILE__). '/img/spamblock_bayes.load.gif'); break; case 'spamblock_bayes.spam.png': header('Content-Type: image/png'); echo file_get_contents(dirname(__FILE__). '/img/spamblock_bayes.spam.png'); break; case 'jquery.tablesorter.js': header('Content-Type: text/javascript'); echo file_get_contents(dirname(__FILE__). '/jquery.tablesorter.js'); break; case 'jquery.heatcolor.js': header('Content-Type: text/javascript'); echo file_get_contents(dirname(__FILE__). '/jquery.heatcolor.js'); break; case 'jquery.excerpt.js': header('Content-Type: text/javascript'); echo file_get_contents(dirname(__FILE__). '/jquery.excerpt.js'); break; case 'serendipity_event_spamblock_bayes.js': header('Content-Type: text/javascript'); echo file_get_contents(dirname(__FILE__). '/serendipity_event_spamblock_bayes.js'); break; case 'getRating': $ids = $_REQUEST ['id']; $ids = explode(';', $ids); //we get the comments in wrong order $comments = array_reverse($this->getComment($ids)); $i = 0; foreach ($comments as $comment) { $ratings .= preg_replace('/\..*/', '', $this->startClassify($comment) * 100) .'%;'. $ids[$i] . ';'; $i++; } echo $ratings; break; case 'bayesMenuLearn': if (!serendipity_checkPermission('adminComments')) { break; } //the POST-Data of the form is almost exactly like the result of the database-query $comment = $_POST; if (serendipity_db_bool($comment['ham'])) { $category = 'ham'; } else { $category = 'spam'; } $this->startLearn($comment, $category); $redirect= ''; echo $redirect . $url; break; case 'bayesLearnFromOld': if (!serendipity_checkPermission('adminComments')) { break; } $this->learnFromOld(); #redirect the user back to the menu $redirect= ''; echo $redirect . $url; break; case 'bayesDeleteDatabase': if (!serendipity_checkPermission('adminComments')) { break; } $this->deleteDB(); $redirect= ''; echo $redirect . $url; break; case 'bayesSetupDatabase': if (!serendipity_checkPermission('adminComments')) { break; } $this->setupDB(); $redirect= ''; echo $redirect . $url; break; case 'bayesRecycler': if (!serendipity_checkPermission('adminComments')) { break; } if ( !empty($_REQUEST['serendipity']['selected'])) { $ids = array_keys($_REQUEST['serendipity']['selected']); } else { if ( !empty($_REQUEST['serendipity']['comments'])) { $ids = array_keys($_REQUEST['serendipity']['comments']); } } if (isset($_REQUEST['restore'])) { if ( !empty($ids)) { $ids = array_keys($_REQUEST['serendipity']['selected']); #When restoring a comment we can be pretty sure it's a valid one $comments = $this->getRecyclerComment($ids); foreach ($comments as $comment) { $this->startLearn($comment, 'ham'); } $this->restoreComments($ids); if (in_array(0, $ids)) { #this happened when the recyclercode was broken $msg = "Not able to restore comment with id 0"; $msgtype = 'error'; } if (count($ids) > 1) { $msg = 'Comments '. implode(', ', $ids) .' restored'; } else { $msg = 'Comment '. implode(', ', $ids) .' restored'; } $msgtype = 'success'; } else { $msg = 'No comment selected'; $msgtype = 'message'; } } if (isset($_REQUEST['empty'])) { if (isset($_REQUEST['recyclerSpam'])) { if ($this->get_config('emptyAll', false)) { $comments = $this->getAllRecyclerComments(); } else { $comments = $this->getRecyclerComment($ids); } foreach ($comments as $comment) { $this->startLearn($comment, 'spam'); } } if ($this->get_config('emptyAll', false)) { $success = $this->emptyRecycler(); } else { $success = $this->deleteFromRecycler($ids); } if (serendipity_db_bool($success)) { $msg = 'Recycler emptied'; $msgtype = 'success'; } else { $msg = urlencode($success); $msgtype = 'error'; } } $redirect= ''; } else { $url .= '" />'; } echo $redirect . $url; break; case 'bayesAnalyse': if(isset($_REQUEST['comments'])) { $comment_ids = array_keys($_REQUEST['comments']); } else { $msg = 'Please select at least one comment'; $msgtype = 'message'; } $redirect= ''; } else { $url .= '" />'; } echo $redirect . $url; break; case 'bayesImport': #Showing the menu $redirect= ''; echo $redirect . $url; break; case 'bayesExportDatabase': $key = $_POST['key']; $exportKey = $this->get_config('exportKey', ""); if (! ((serendipity_checkPermission('adminComments')) || ((! $exportKey == "") && ($exportKey == $key)))) { break; } $this->set_config('exportKey', ""); $this->exportDatabase(); header('Content-type: application/x-download'); header('Content-Disposition: attachment; filename=spamblock_bayes.csv'); echo file_get_contents($serendipity['serendipityPath']. 'templates_c/spamblock_bayes.csv'); break; case 'bayesTrojaGetKey': $publicTrojaKey = openssl_get_publickey(file_get_contents(dirname(__FILE__). '/publicTrojaKey.pem')); header('HTTP/1.1 200 OK'); $key = mt_rand(); $this->set_config('exportKey', $key); openssl_public_encrypt($key, $enc_key, $publicTrojaKey, OPENSSL_PKCS1_PADDING); echo base64_encode($enc_key); break; case 'bayesTrojaRegister': if (!serendipity_checkPermission('adminComments')) { break; } $this->set_config('awaitingTrojaRequest', true); $this->set_config('troja_registered', true); $trojaUrlTarget = $this->trojaUrl . 'register'; $data = array('url' => $serendipity['baseURL']); $trojaUrlTarget .= "?" . http_build_query($data); $response = $this->getRequest($trojaUrlTarget); parse_str($response, $params); $registered = urldecode($params['registered']); if ($registered == 1) { $msg = "Registered"; $msgtype = "success"; } else { $msg = "Could not register this blog (already registered?)"; $msgtype = "error"; } $redirect = ''; echo $redirect . $url; break; case 'bayesTrojaRemove': if (!serendipity_checkPermission('adminComments')) { break; } $this->set_config('awaitingTrojaRequest', true); $this->set_config('troja_registered', false); $trojaUrlTarget = $this->trojaUrl . 'remove'; $data = array('url' => $serendipity['baseURL']); $trojaUrlTarget .= "?" . http_build_query($data); $response = $this->getRequest($trojaUrlTarget); parse_str($response, $params); $removed = urldecode($params['removed']); if ($removed == 1) { $msg = "Removed"; $msgtype = "success"; } else { $msg = "Could not remove this blog"; $msgtype = "error"; } $redirect = ''; echo $redirect . $url; break; case 'bayesTrojaAccept': $waiting = serendipity_db_bool($this->get_config('awaitingTrojaRequest', false)); if ($waiting === true) { header('HTTP/1.1 200 OK'); $this->set_config('awaitingTrojaRequest', false); } else { header('HTTP/1.1 403 Forbidden'); } echo ""; break; case 'bayesTrojaRequestDB': if (!serendipity_checkPermission('adminComments')) { break; } $trojaUrlTarget = $this->trojaUrl . 'requestDB'; $url = $serendipity['baseURL']; $try = 0; while (trim($url) == $serendipity['baseURL']) { $try++; $response = $this->getRequest($trojaUrlTarget); parse_str($response, $params); $url = urldecode($params['url']); if ($try > 3) { break; } } $key = $params['key']; $error = false; if (trim($url) == "http://".$serendipity['baseURL'] || trim($url) == $serendipity['baseURL']) { $msg = "Got only this blog as target to import from"; $msgtype = "error"; $error = true; } if ($url == "") { $msg = "Got no target to import from"; $msgtype = "error"; $error = true; } if ($error) { $redirect = ''; echo $redirect . $url; return; } else { $msg = "Imported from $url"; $msgtype = "success"; } $this->fetchDatabase(trim($url), $key); $redirect = ''; echo $redirect . $url; break; } return true; break; case 'frontend_saveComment' : if (! is_array ( $eventData ) || serendipity_db_bool ( $eventData ['allow_comments'] )) { if (!isset($serendipity['csuccess'])) { $serendipity['csuccess'] = 'true'; } $comment = array( $this->type['url'] => $addData['url'], $this->type['body'] => $addData['comment'], $this->type['name'] => $addData['name'], $this->type['email'] => $addData['email'], $this->type['ip'] => serendipity_db_escape_string(isset($addData['ip']) ? $addData['ip'] : $_SERVER['REMOTE_ADDR']), $this->type['referrer'] => substr((isset($_SESSION['HTTP_REFERER']) ? serendipity_db_escape_string($_SESSION['HTTP_REFERER']) : ''), 0, 200) ); if ($this->checkIfSpam($comment)) { $method = $this->get_config('method', 'moderate'); if ($method == 'moderate') { $this->moderate($eventData, $addData); return false; } elseif($method == 'block') { $this->block($eventData, $addData); return false; } } $blockBarrier = $this->get_config('blockBarrier', 90) / 100; $moderateBarrier = $this->get_config('moderateBarrier', 70) / 100; //now this either wasn't spam or method custom is selected. if ($this->lastRating > $blockBarrier) { $this->block($eventData, $addData); return false; } elseif ($this->lastRating > $moderateBarrier) { $this->moderate($eventData, $addData); return false; } } return true; break; case 'backend_view_comment': $path = $this->path = $this->get_config('path', $serendipity['serendipityHTTPPath'] . 'plugins/serendipity_event_spamblock_bayes/'); if (!empty($path) && $path != 'default' && $path != 'none' && $path != 'empty') { $path_defined = true; $imgpath = $path . 'img/'; } else { $path_defined = false; $imgpath = $serendipity['baseURL'] . 'index.php?/plugin/'; } $comment = $eventData; //change $comment into the needed form $comment[$this->type['body']] = $comment['fullBody']; unset($comment['fullBody']); if ($serendipity['version'][0] == 1) { $eventData['action_more'] = ''. PLUGIN_EVENT_SPAMBLOCK_BAYES_HAM.' '. PLUGIN_EVENT_SPAMBLOCK_BAYES_SPAM.' '. preg_replace('/\..*/', '', $this->startClassify($comment) * 100) .'% '; } else { $eventData['action_more'] = ' '; } return true; break; case 'backend_sendcomment': $delete = PLUGIN_EVENT_SPAMBLOCK_BAYES_DELETE . ': '; $delete .= $serendipity['baseURL'] . 'index.php?/plugin/learnAction&action=delete&category=spam&id=' . $eventData['comment_id'] . '&entry_id='. $eventData['entry_id'] . '&path=' . $eventData['path']; $eventData['action_more']['delete'] = $delete; if (!empty($eventData['moderate_comment']) && $eventData['moderate_comment']) { $approve = PLUGIN_EVENT_SPAMBLOCK_BAYES_APPROVE . ': '; $approve .= $serendipity['baseURL'] . 'index.php?/plugin/learnAction&action=approve&category=ham&id=' . $eventData['comment_id'] . '&entry_id='. $eventData['entry_id'] . '&path=' . $eventData['path']; $eventData['action_more']['approve'] = $approve; } return true; break; case 'backend_comments_top': $path = $this->path = $this->get_config('path', $serendipity['serendipityHTTPPath'] . 'plugins/serendipity_event_spamblock_bayes/'); if (!empty($path) && $path != 'default' && $path != 'none' && $path != 'empty') { $path_defined = true; $imgpath = $path . 'img/'; } else { $path_defined = false; $imgpath = $serendipity['baseURL'] . 'index.php?/plugin/'; } echo " "; return true; break; case 'backend_sidebar_entries': if (!serendipity_checkPermission('adminComments')) { break; } if ($serendipity['version'][0] == 1) { if ($this->get_config('menu', true)) { echo ''; } } else { } return true; break; case 'backend_sidebar_admin_appearance': if (!serendipity_checkPermission('adminComments')) { break; } if ($serendipity['version'][0] == 1) { } else { if ($this->get_config('menu', true)) { echo '
  • ' . PLUGIN_EVENT_SPAMBLOCK_BAYES_NAME . '
  • '; } } return true; break; case 'backend_sidebar_entries_event_display_spamblock_bayes': if (!serendipity_checkPermission('adminComments')) { break; } $path = $this->path = $this->get_config('path', $serendipity['serendipityHTTPPath'] . 'plugins/serendipity_event_spamblock_bayes/'); if (!empty($path) && $path != 'default' && $path != 'none' && $path != 'empty') { $path_defined = true; $imgpath = $path . 'img/'; } else { $path_defined = false; $imgpath = $serendipity['baseURL'] . 'index.php?/plugin/'; } global $serendipity; if (isset($serendipity['GET']['message'])) { if ($serendipity['version'][0] == 1) { echo '

    '.(function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['message']) : htmlspecialchars($serendipity['GET']['message'], ENT_COMPAT, LANG_CHARSET)).'

    '; } else { echo ' ' . (function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['message']) : htmlspecialchars($serendipity['GET']['message'], ENT_COMPAT, LANG_CHARSET)) . ''; } } if (isset($serendipity['GET']['success'])) { if ($serendipity['version'][0] == 1) { echo '

    '.(function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['success']) : htmlspecialchars($serendipity['GET']['success'], ENT_COMPAT, LANG_CHARSET)).'

    '; } else { echo ' ' . (function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['success']) : htmlspecialchars($serendipity['GET']['success'], ENT_COMPAT, LANG_CHARSET)) . ''; } } if (isset($serendipity['GET']['error'])) { if ($serendipity['version'][0] == 1) { echo '

    '.(function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['error']) : htmlspecialchars($serendipity['GET']['error'], ENT_COMPAT, LANG_CHARSET)).'

    '; } else { echo ' ' . (function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['error']) : htmlspecialchars($serendipity['GET']['error'], ENT_COMPAT, LANG_CHARSET)) . ''; } } $this->get = $serendipity['GET']; $this->displayMenu($serendipity['GET']['subpage']); return true; break; case 'xmlrpc_comment_spam': $entry_id = $addData['id']; $comment_id = $addData['cid']; if($this->get_config('method', 'moderate') == 'custom') { $spamBarrier = min(array( $this->get_config('moderateBarrier', 70) / 100, $this->get_config('blockBarrier', 90) / 100 )); } else { $spamBarrier = 0.7; } //spam shall not get through the filter twice - so make sure, it really is marked as spam $loop = 0; while ($this->startClassify($eventData) < $spamBarrier && $loop < 5) { $this->startLearn($eventData, 'spam'); //prevent infinite loop $loop++; } if ($this->get_config('recycler', true)) { $this->recycleComment($comment_id, $entry_id); } serendipity_deleteComment($comment_id, $entry_id); return true; break; case 'xmlrpc_comment_ham': $this->startLearn($eventData, 'ham'); $comment_id = $addData['cid']; $entry_id = $addData['id']; //moderated ham-comments should be instantly approved, that's why they need an id: serendipity_approveComment($comment_id, $entry_id); return true; break; default : return false; break; } } else { return false; } } function getRequest($url) { if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); curl_close ($ch); } else { $options = array('http' => array( 'method' => 'GET' )); $context = stream_context_create($options); $response = file_get_contents($url, false, $context); } return $response; } #Show the whole additional configuration, specifiy subpage for a specific tab function displayMenu($subpage=0) { global $serendipity; $css = file_get_contents(dirname(__FILE__). '/admin/serendipity_event_spamblock_bayes.css'); #add javascript for usability if ($serendipity['capabilities']['jquery']) { $jquery_needed = false; } else { $jquery_needed = true; } echo $this->smarty_show('bayesNavigation.tpl', array('css' => $css, 'jquery_needed' => $jquery_needed, 'path' => $this->path, 'subpage' => $subpage, 's9ybackend' => $GLOBALS['s9ybackend'] )); switch($subpage) { case '1': $this->showRecyclerMenu($this->get['commentpage']); break; case '2': $this->showDBMenu($this->get['commentpage']); break; case '3': $this->showLearnMenu(); break; case '4': $this->showAnalysisMenu($this->get['commentpage']); break; case '5': $this->showImportMenu(); break; default: break; } } /* Render a smarty-template * $template: path to the template-file * $data: map with the variables to assign * */ function smarty_show($template, $data = null) { global $serendipity; if (!is_object($serendipity['smarty'])) { serendipity_smarty_init(); } $serendipity['smarty']->assign($data); echo $this->parseTemplate($template); } function showLearnMenu() { echo $this->smarty_show('bayesLearnmenu.tpl', array('s9ybackend' => $GLOBALS['s9ybackend'])); } function showDBMenu($commentpage) { global $serendipity; $data = array(); $sql = "SELECT token, ham, spam, type FROM {$serendipity['dbPrefix']}spamblock_bayes ORDER BY spam" . serendipity_db_limit_sql(sprintf("%d,%d", $commentpage*20, 20)); try { $bayesTable = serendipity_db_query($sql, false, "assoc"); } catch (Exception $e) { $bayesTable = array(); } try { $sql ="SELECT COUNT(token) FROM {$serendipity['dbPrefix']}spamblock_bayes"; $amount = serendipity_db_query($sql, true, "num"); $amount = $amount[0]; } catch (Exception $e) { $amount = 0; } $data['pages'] = ceil($amount / 20); $data['bayesTable'] = $bayesTable; if (! isset($commentpage)) { $commentpage = 0; } $data['curpage'] = $commentpage; foreach($this->type as $type) { $data[$type.'_ham'] = $this->get_config("{$type}_ham", 0); $data[$type.'_spam'] = $this->get_config("{$type}_spam", 0); } $data['path'] = $this->path; $data['s9ybackend'] = $GLOBALS['s9ybackend']; echo $this->smarty_show('bayesDBmenu.tpl', $data); } function showRecyclerMenu($commentpage) { $comments = $this->getAllRecyclerComments($commentpage); if (is_array($comments[0])) { for ($i=0; $i < count($comments); $i++) { $comment = $comments[$i]; $types = array_keys($this->type); $ratings = array(); $comment['rating'] = $this->startClassify($comment) * 100; $comment['article_link'] = serendipity_archiveURL($comment['entry_id'], 'comments', 'serendipityHTTPPath', true); $comment['article_title'] = $this->getEntryTitle($comment['entry_id']); $comments[$i] = $comment; } } else { $comments = array(); } echo $this->smarty_show('bayesRecyclermenu.tpl', array('comments' => $comments, 'types' => array_values($this->type), 'commentpage' => $commentpage, 'path' => $this->path, 's9ybackend' => $GLOBALS['s9ybackend'] )); } function getEntryTitle($id) { global $serendipity; $sql = "SELECT title FROM {$serendipity['dbPrefix']}entries WHERE id = '$id'"; $title = serendipity_db_query($sql, true, "assoc"); $title = $title['title']; return $title; } function showAnalysisMenu($commentpage=0) { if (isset($this->get['comments'])) { //comments already were selected $comment_ids = array_keys($this->get['comments']); $this->showAnalysis($comment_ids); } else { $comments = $this->getAllComments($commentpage); if (!is_array($comments[0])) { $comments = array(); } echo $this->smarty_show('bayesAnalysismenu.tpl', array( 'comments' => $comments, 'commentpage' => $commentpage, 'path' => $this->path, 's9ybackend' => $GLOBALS['s9ybackend'] )); } } function showImportMenu() { global $serendipity; echo $this->smarty_show('bayesImportmenu.tpl', array( 'trojaRegistered' => $this->get_config('troja_registered', false) == true, 's9ybackend' => $GLOBALS['s9ybackend'] )); } function showAnalysis($comment_id) { $comments = $this->getComment($comment_id); for ($i=0; $i < count($comments); $i++) { $comment = $comments[$i]; $types = array_keys($this->type); $ratings = array(); foreach($types as $type) { $rating = $this->classify($comment[$this->type[$type]], $this->type[$type]); if (is_numeric($rating)) { $ratings[$this->type[$type]] = $rating * 100; } else { $ratings[$this->type[$type]] = '-'; } } $comment['rating'] = $this->startClassify($comment) * 100; $comment['ratings'] = $ratings; $comments[$i] = $comment; } echo $this->smarty_show('bayesAnalysis.tpl', array('comments' => $comments, 'types' => array_values($this->type), 's9ybackend' => $GLOBALS['s9ybackend'] )); } #For email-notification. Learn a spam or ham and delete or approve. function learnAction($id, $category, $action, $entry_id) { global $serendipity; $comment = $this->getComment($id); if (is_array ($comment)) { $comment = $comment['0']; } $this->startLearn($comment, $category); # This generates a new Token if this function is called via e-mail comment token # this should be a function in the core. It will be replaced if the core-modification is live. if ($serendipity['useCommentTokens']) { $token = md5(uniqid(rand(),1)); $path = $path . "_token_" . $token; //Delete any comment tokens older than 1 week. serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}options WHERE okey LIKE 'comment_%' AND name < " . (time() - 604800) ); // Issue new comment moderation hash serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}options (name, value, okey) VALUES ('" . time() . "', '" . $token . "', 'comment_" . $id ."')"); } if ($action == 'delete') { serendipity_deleteComment($id, $entry_id, 'comment', $token); } else if ($action == 'approve') { serendipity_approveComment($id, $entry_id, 'comment', $token); } } #id: array of ids or a single id function getComment($id) { global $serendipity; if(is_array($id)) { $sql = "SELECT id, body, entry_id, author, email, url, ip, referer FROM {$serendipity['dbPrefix']}comments WHERE " . serendipity_db_in_sql ( 'id', $id ); } else { $sql = "SELECT id, body, entry_id, author, email, url, ip, referer FROM {$serendipity['dbPrefix']}comments WHERE id = " . (int)$id; } $comments = serendipity_db_query($sql, false, 'assoc'); return $comments; } #id: array of ids or a single id function getRecyclerComment($id) { global $serendipity; if(is_array($id)) { $sql = "SELECT id, body, entry_id, author, email, url, ip, referer FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE " . serendipity_db_in_sql ( 'id', $id ); } else { $sql = "SELECT id, body, entry_id, author, email, url, ip, referer FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE id = " . (int)$id; } $comments = serendipity_db_query($sql, false, 'assoc'); return $comments; } # Get all comments, or, when $page was given, give 20 comments of # that page function getAllComments($page=false) { global $serendipity; if ($page === false) { $sql = "SELECT * FROM {$serendipity['dbPrefix']}comments ORDER BY id DESC"; } else { $first = $page * 20; $amount = 21; $sql = "SELECT * FROM {$serendipity['dbPrefix']}comments ORDER BY id DESC" . serendipity_db_limit_sql(sprintf("%d,%d", $first, $amount)); } $comments = serendipity_db_query($sql, false, 'assoc'); return $comments; } function getAllRecyclerComments($page=false) { global $serendipity; if ($page === false) { $sql = "SELECT * FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler ORDER BY id DESC"; } else { $first = $page * 20; $amount = 21; $sql = "SELECT * FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler ORDER BY id DESC" . serendipity_db_limit_sql(sprintf("%d,%d", $first, $amount)); } $comments = serendipity_db_query($sql, false, 'assoc'); return $comments; } function block(&$eventData, &$addData) { global $serendipity; if ($this->get_config('recycler', true)) { $delete = $this->get_config('recyclerdelete', ''); $rating = preg_replace('/\..*/', '', $this->lastRating * 100); if (empty($delete) || $rating < $delete) { $this->throwInRecycler($eventData, $addData); } } $logfile = $this->logfile = $this->get_config('logfile', $serendipity['serendipityPath'] . 'spamblock.log'); $this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_BAYES_REASON, $addData); $eventData = array ('allow_comments' => false); $serendipity ['messagestack'] ['comments'] [] = PLUGIN_EVENT_SPAMBLOCK_BAYES_ERROR; } function moderate(&$eventData, &$addData) { global $serendipity; $logfile = $this->logfile = $this->get_config('logfile', $serendipity['serendipityPath'] . 'spamblock.log'); $this->log($logfile, $eventData['id'], 'MODERATE', PLUGIN_EVENT_SPAMBLOCK_BAYES_REASON, $addData); $eventData['moderate_comments'] = true; $serendipity['csuccess'] = 'moderate'; $serendipity['moderate_reason'] = sprintf(PLUGIN_EVENT_SPAMBLOCK_BAYES_MODERATE); } //Empty the Recycler function emptyRecycler() { global $serendipity; $sql = "DELETE FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler"; return serendipity_db_query($sql); } //Get the blocked comment and store it in the recycler-table //Used when the comment is from a current happening event function throwInRecycler(&$ca, &$commentInfo) { global $serendipity; #code copied from serendipity_insertComment. Changed: $id and $status $id = (int)$ca['id']; $type = $commentInfo['type']; if (isset($commentInfo['subscribe'])) { if (!isset($serendipity['allowSubscriptionsOptIn']) || $serendipity['allowSubscriptionsOptIn']) { $subscribe = 'false'; } else { $subscribe = 'true'; } } else { $subscribe = 'false'; } //'approved' cause only relevant after recovery $dbstatus = 'approved'; $title = serendipity_db_escape_string($ca['title']); $comments = $commentInfo['comment']; $ip = serendipity_db_escape_string(isset($commentInfo['ip']) ? $commentInfo['ip'] : $_SERVER['REMOTE_ADDR']); $commentsFixed = serendipity_db_escape_string($commentInfo['comment']); $name = serendipity_db_escape_string($commentInfo['name']); $url = serendipity_db_escape_string($commentInfo['url']); $email = serendipity_db_escape_string($commentInfo['email']); $parentid = (isset($commentInfo['parent_id']) && is_numeric($commentInfo['parent_id'])) ? $commentInfo['parent_id'] : 0; $status = serendipity_db_escape_string(isset($commentInfo['status']) ? $commentInfo['status'] : (serendipity_db_bool($ca['moderate_comments']) ? 'pending' : 'approved')); $t = serendipity_db_escape_string(isset($commentInfo['time']) ? $commentInfo['time'] : time()); $referer = substr((isset($_SESSION['HTTP_REFERER']) ? serendipity_db_escape_string($_SESSION['HTTP_REFERER']) : ''), 0, 200); $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes_recycler (entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer) VALUES ('$id', '$parentid', '$ip', '$name', '$email', '$url', '$commentsFixed', '$type', '$t', '$title', '$subscribe', '$dbstatus', '$referer')"; serendipity_db_query($sql); } function recycleComment($id, $entry_id) { global $serendipity; $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes_recycler (entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer) SELECT entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer FROM {$serendipity['dbPrefix']}comments WHERE id = '$id' AND entry_id = '$entry_id';"; serendipity_db_query($sql); } function restoreComments($ids) { global $serendipity; if (is_array($ids)) { $sql = "INSERT INTO {$serendipity['dbPrefix']}comments (entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer) SELECT entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE " . serendipity_db_in_sql ( 'id', $ids ); } else { $sql = "INSERT INTO {$serendipity['dbPrefix']}comments (entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer) SELECT entry_id, parent_id, ip, author, email, url, body, type, timestamp, title, subscribed, status, referer FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE id = " . (int)$ids; } $result = serendipity_db_query($sql); $this->deleteFromRecycler($ids); } function deleteFromRecycler($ids) { global $serendipity; if (is_array($ids)) { $sql = "DELETE FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE " . serendipity_db_in_sql ( 'id', $ids ); } else { $sql = "DELETE FROM {$serendipity['dbPrefix']}spamblock_bayes_recycler WHERE id = " . (int)$ids; } return serendipity_db_query($sql); } /** * Export the database spamblack_bayes into a csv-file * */ function exportDatabase() { global $serendipity; #try to reduce memory usage by not selecting the whole table, #but splitting it in chunks of 10000 $sql = "SELECT COUNT(*) FROM {$serendipity['dbPrefix']}spamblock_bayes"; $amount = serendipity_db_query($sql); $amount = $amount[0][0]; $runs = 0; $csvfile = $serendipity ['serendipityPath'] . 'templates_c/spamblock_bayes.csv'; $fp = @fopen($csvfile , 'w'); while ($amount > ($start = $runs * 10000)) { $sql = "SELECT token, ham, spam, type FROM {$serendipity['dbPrefix']}spamblock_bayes LIMIT $start, 10000"; $database = serendipity_db_query($sql); #The array $database now contains all results twice. There's #probably a nicer way to remove them for ($i=0;$i < count($database); $i++) { for ($j=0;$j < 4; $j++) { unset($database[$i][$j]); } } foreach ($database as $fields) { fputcsv($fp, $fields); } $runs++; } fclose($fp); } function fetchDatabase($host, $key) { global $serendipity; $data = array('key' => $key); $url = $host . 'index.php?/plugin/bayesExportDatabase'; if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); $result = curl_exec ($ch); curl_close ($ch); } else { // this method should work, but in my test, this code //never transmitted the post-fields properly $options = array('http' => array( 'method' => 'POST', 'content' => http_build_query($data) )); $context = stream_context_create($options); $result = file_get_contents($url, false, $context); } if( $this->validCvs($result)) { #write obtained csv to $file $csvfile = $serendipity ['serendipityPath'] . 'templates_c/spamblock_bayes.csv'; file_put_contents($csvfile, $result); $spamDB = $this->getCsvDatabase($csvfile); $this->importDatabase($spamDB); } } #check if the fetched page really was a spamblock-file #param1: $content Content of the cvs #return: true or false function validCvs($content) { $lines = explode("\n", $content); $number_lines = count($lines) -1; return preg_match_all("/.*,[0-9]*,[0-9]*,.*/", $content, $matches) == $number_lines; } function importDatabase($importDatabase) { global $serendipity; set_time_limit(0); serendipity_db_begin_transaction(); if ($this->get_config('dbversion', 2) == 3 && ($serendipity['dbType'] == 'mysql' || $serendipity['dbType'] == 'mysqli')) { #now there is a primary key we can use foreach ($importDatabase as $importToken) { $token = $importToken[0]; $ham = $importToken[1]; $spam = $importToken[2]; $type = $importToken[3]; $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, ham, spam, type) VALUES ('$token', $ham, $spam, '$type') ON DUPLICATE KEY UPDATE ham = ham + VALUES(ham), spam = spam + VALUES(spam);"; serendipity_db_query($sql); $result = mysql_error(); if ($result != "") { serendipity_db_end_transaction(false); return $result; } if ($ham > 0) { $this->set_config("{$type}_ham", $this->get_config("{$type}_ham", 0) + 1); } if ($spam > 0) { $this->set_config("{$type}_spam", $this->get_config("{$type}_spam", 0) + 1); } } } elseif ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'sqlite3oo' || $serendipity['dbType'] == 'pdo-sqlite') { foreach ($importDatabase as $importToken) { $token = $importToken[0]; $ham = $importToken[1]; $spam = $importToken[2]; $type = $importToken[3]; $sql = "INSERT OR IGNORE INTO {$serendipity['dbPrefix']}spamblock_bayes (token, ham, spam, type) VALUES ('$token', 0, 0, '$type');"; serendipity_db_query($sql); $sql = "UPDATE {$serendipity['dbPrefix']}spamblock_bayes SET ham = ham + $ham, spam = spam + $spam WHERE token = '$token' AND type = '$type'"; serendipity_db_query($sql); if ($ham > 0) { $this->set_config("{$type}_ham", $this->get_config("{$type}_ham", 0) + 1); } if ($spam > 0) { $this->set_config("{$type}_spam", $this->get_config("{$type}_spam", 0) + 1); } } } else { foreach ($importDatabase as $importToken) { $token = $importToken[0]; $ham = $importToken[1]; $spam = $importToken[2]; $type = $importToken[3]; $sql = "SELECT token FROM {$serendipity['dbPrefix']}spamblock_bayes WHERE token = '$token' AND type = '$type'"; $tester = serendipity_db_query($sql); if (empty($tester[0])) { $sql = "INSERT INTO {$serendipity['dbPrefix']}spamblock_bayes (token, ham, spam, type) VALUES('$token', $ham, $spam, '$type')"; } else { $sql = "UPDATE {$serendipity['dbPrefix']}spamblock_bayes SET ham = ham + $ham, spam = spam + $spam WHERE token = '$token' AND type = '$type'"; } serendipity_db_query($sql); #NOTE: We do this wrongly, but as good as possible (really?). # The config is supposed to store the amount of # ham/spam-comments, not a guess of that. if ($ham > 0) { $this->set_config("{$type}_ham", $this->get_config("{$type}_ham", 0) + 1); } if ($spam > 0) { $this->set_config("{$type}_spam", $this->get_config("{$type}_spam", 0) + 1); } } } serendipity_db_end_transaction(true); return true; } function getCsvDatabase($csvfile) { if (($handle = fopen($csvfile, "r")) !== FALSE) { $i = 0; while (($lineArray = fgetcsv($handle, 4000)) !== FALSE) { for ($j=0; $jdebug_fp = @fopen ( $serendipity ['serendipityPath'] . 'templates_c/spamblock_bayes.log', 'a' ); if (! $this->debug_fp) { return false; } if (empty ( $msg )) { fwrite ( $this->debug_fp, "failure \n" ); } else { fwrite ( $this->debug_fp, print_r ( $msg, true ) ); } fclose ( $this->debug_fp ); } function is_goodtoken($rpath, $cid) { $tokenparse = explode("_",$rpath); // check that we got a 32 char tokeni if (is_array($tokenparse)) { if (strlen($tokenparse[2]) == 32) { $ret=serendipity_checkCommentToken($tokenparse[2], (int)$cid); return $ret; } else { return false; } } else { return false; } } function log($logfile, $id, $switch, $reason, $addData) { global $serendipity; $method = $this->get_config('logtype'); switch($method) { case 'file': if (empty($logfile)) { return; } if (strpos($logfile, '%') !== false) { $logfile = strftime($logfile); } $fp = @fopen($logfile, 'a+'); if (!is_resource($fp)) { return; } fwrite($fp, sprintf( '[%s] - [%s: %s] - [#%s, Name "%s", E-Mail "%s", URL "%s", User-Agent "%s", IP %s] - [%s]' . "\n", date('Y-m-d H:i:s', serendipity_serverOffsetHour()), $switch, $reason, $id, str_replace("\n", ' ', $addData['name']), str_replace("\n", ' ', $addData['email']), str_replace("\n", ' ', $addData['url']), str_replace("\n", ' ', $_SERVER['HTTP_USER_AGENT']), $_SERVER['REMOTE_ADDR'], str_replace("\n", ' ', $addData['comment']) )); fclose($fp); break; case 'none': return; break; case 'db': default: $q = sprintf("INSERT INTO {$serendipity['dbPrefix']}spamblocklog (timestamp, type, reason, entry_id, author, email, url, useragent, ip, referer, body) VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", serendipity_serverOffsetHour(), serendipity_db_escape_string($switch), serendipity_db_escape_string($reason), serendipity_db_escape_string($id), serendipity_db_escape_string($addData['name']), serendipity_db_escape_string($addData['email']), serendipity_db_escape_string($addData['url']), substr(serendipity_db_escape_string($_SERVER['HTTP_USER_AGENT']), 0, 255), serendipity_db_escape_string($_SERVER['REMOTE_ADDR']), substr(serendipity_db_escape_string(isset($_SESSION['HTTP_REFERER']) ? $_SESSION['HTTP_REFERER'] : $_SERVER['HTTP_REFERER']), 0, 255), serendipity_db_escape_string($addData['comment']) ); serendipity_db_schema_import($q); break; } } }