bayes 1.0: cleanup and code update version

This commit is contained in:
onli 2020-08-09 11:10:41 +02:00
parent 8c917ab315
commit f669be9b2b
26 changed files with 1730 additions and 3149 deletions

View file

@ -1,4 +1,11 @@
0.5.6: fix possible SQL exposure in email field of comment
1.0:
* Update from the forked old b8 version to a current
version of that library
* Code cleanup
* Remove seldomly used functions and legacy code, including
most of the backend UI
* Rewrite javascript used in the backend (for marking spam
and ham comments) to be a minimal block of jQuery code
0.5.5: Translation fixes (German).

View file

@ -1,195 +0,0 @@
#bayesNav {
margin: 0;
padding: 0;
}
#bayesNav a {
display: block;
}
#bayesNav li:last-child a {
padding-right: 0.3em;
}
#bayesNav ul {
list-style-type: none;
margin: 0;
padding: 0;
border: 1px solid;
}
#bayesNav li {
display: inline-block;
margin: 0;
padding: 0.5em;
border-right: 1px solid;
}
#bayesNav h3 {
display: inline;
margin: 0;
padding: 0;
font-size: 1em;
}
#bayesContent {
width: 100%;
}
#bayesControls * {
margin-left: 1em;
margin-right: 1em;
margin-bottom: 1em;
}
#bayesLearnTable {
padding-top: 1em;
margin-bottom: 0.8em;
}
#bayesLearnTable td {
vertical-align: top;
}
#bayesControls {
float: right;
border: 1px solid;
border-top: 0;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
max-width: 21%;
padding-top: 1em;
}
#bayesControls form {
margin: 0;
}
#bayesDatabase {
padding-top: 2em;
margin-left: 2em;
}
#bayesSavedValues {
padding-top: 2em;
}
#bayesDatabaseTable th {
cursor: pointer;
text-decoration: underline;
}
th {
border: 1px solid;
}
caption {
font-weight: bold;
}
#bayesSavedValuesTable td {
text-align: center;
}
#bayesRecyclerTable {
padding-top: 2em;
width: 78%;
table-layout: fixed;
}
#bayesRecyclerTable td {
padding-top: 1em;
overflow: auto;
}
#bayesRecyclerTable th.select {
text-align: center;
width: 2em;
}
#bayesRecyclerTable td.select {
text-align: center;
}
.ratingBox {
border-bottom: 1px solid grey;
}
.commentPart {
margin-left: 5em;
}
.rating {
margin-left: 5em;
font-weight: bold;
}
.commentType {
font-weight: bold;
}
.finalRating {
padding-left: 3.3em;
font-weight: bold;
font-size: 1.5em;
border-bottom: 1px solid grey;
}
#bayesAnalysisList li {
margin: 1em;
}
label {
cursor: pointer;
}
.serendipityIconLinkRight {
left: 90%;
position: relative;
}
#bayesControls label, #bayesControls input {
display: block;
}
input[type="submit"] {
cursor: pointer;
}
.bayesTrojaButtons {
display: inline;
}
fieldset {
display: inline-block;
}
#trojaImport {
margin-left: 1.1em;
}
#bayesDatabaseTablePagination {
font-size: 0.8em;
margin-top: 0.2em;
}
a.curpage {
border: 1px dotted black !important;
}
#bayesDatabaseTablePagination a {
border: 1px solid black;
padding: 1px;
}
#bayesRecycler, #bayesAnalysis {
display: inline-block;
}
#bayesRecycler {
max-width: 80%;
}
.bayesRecyclerSummary {
display: inline;
vertical-align: middle;
}
input {
vertical-align: middle;
}
.bayesRecyclerSummary td{
text-align: center;
}
.bayesRecyclerItem {
padding-top: 1em;
}
.bayesRecyclerList {
margin-left: 3em;
}
.bayesRecyclerList dt {
font-weight: bold;
}
.bayesRecyclerList dt:after {
content: ":";
}
.bayesRecyclerList dt+dd {
margin-left: 2em;
}
.bayesRecyclerTableNavigation {
margin-top: 1em;
}
.bayesAnalysisTableNavigation {
margin-top: 1em;
}
#bayesAnalysisButton {
margin-left: 1em;
}
summary {
cursor: pointer;
}

View file

@ -0,0 +1,406 @@
<?php
/* Copyright (C) 2006-2019 Tobias Leupold <tobias.leupold@gmx.de>
b8 - A statistical ("Bayesian") spam filter written in PHP
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* The b8 spam filter library
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
* @author Oliver Lillie <ollie@buggedcom.co.uk> (original PHP 5 port)
*/
namespace b8;
spl_autoload_register(
function ($class) {
$parts = explode('\\', $class);
if ($parts[1]) {
require_once __DIR__ . DIRECTORY_SEPARATOR . $parts[1] . DIRECTORY_SEPARATOR . $parts[2] . '.php';
}
}
);
class b8
{
const DBVERSION = 3;
const SPAM = 'spam';
const HAM = 'ham';
const LEARN = 'learn';
const UNLEARN = 'unlearn';
const CLASSIFIER_TEXT_MISSING = 'CLASSIFIER_TEXT_MISSING';
const TRAINER_TEXT_MISSING = 'TRAINER_TEXT_MISSING';
const TRAINER_CATEGORY_MISSING = 'TRAINER_CATEGORY_MISSING';
const TRAINER_CATEGORY_FAIL = 'TRAINER_CATEGORY_FAIL';
const INTERNALS_TEXTS = 'b8*texts';
const INTERNALS_DBVERSION = 'b8*dbversion';
const KEY_DB_VERSION = 'dbversion';
const KEY_COUNT_HAM = 'count_ham';
const KEY_COUNT_SPAM = 'count_spam';
const KEY_TEXTS_HAM = 'texts_ham';
const KEY_TEXTS_SPAM = 'texts_spam';
private $config = [ 'lexer' => 'standard',
'degenerator' => 'standard',
'storage' => 'dba',
'use_relevant' => 15,
'min_dev' => 0.2,
'rob_s' => 0.3,
'rob_x' => 0.5 ];
private $storage = null;
private $lexer = null;
private $degenerator = null;
private $token_data = null;
/**
* Constructs b8
*
* @access public
* @param array b8's configuration: [ 'lexer' => string,
'degenerator' => string,
'storage' => string,
'use_relevant' => int,
'min_dev' => float,
'rob_s' => float,
'rob_x' => float ]
* @param array The storage backend's config (depending on the backend used)
* @param array The lexer's config (depending on the lexer used)
* @param array The degenerator's config (depending on the degenerator used)
* @return void
*/
function __construct(array $config = [],
array $config_storage = [],
array $config_lexer = [],
array $config_degenerator = [])
{
// Validate config data
foreach ($config as $name => $value) {
switch ($name) {
case 'min_dev':
case 'rob_s':
case 'rob_x':
$this->config[$name] = (float) $value;
break;
case 'use_relevant':
$this->config[$name] = (int) $value;
break;
case 'lexer':
case 'degenerator':
case 'storage':
$this->config[$name] = (string) $value;
break;
default:
throw new \Exception(b8::class . ": Unknown configuration key: \"$name\"");
}
}
// Setup the degenerator class
$class = '\\b8\\degenerator\\' . $this->config['degenerator'];
$this->degenerator = new $class($config_degenerator);
// Setup the lexer class
$class = '\\b8\\lexer\\' . $this->config['lexer'];
$this->lexer = new $class($config_lexer);
// Setup the storage backend
$class = '\\b8\\storage\\' . $this->config['storage'];
$this->storage = new $class($config_storage, $this->degenerator);
}
/**
* Classifies a text
*
* @access public
* @param string The text to classify
* @return mixed float The rating between 0 (ham) and 1 (spam) or an error code
*/
public function classify(string $text = null)
{
// Let's first see if the user called the function correctly
if ($text === null) {
return \b8\b8::CLASSIFIER_TEXT_MISSING;
}
// Get the internal database variables, containing the number of ham and spam texts so the
// spam probability can be calculated in relation to them
$internals = $this->storage->get_internals();
// Calculate the spaminess of all tokens
// Get all tokens we want to rate
$tokens = $this->lexer->get_tokens($text);
// Check if the lexer failed (if so, $tokens will be a lexer error code, if not, $tokens
// will be an array)
if (! is_array($tokens)) {
return $tokens;
}
// Fetch all available data for the token set from the database
$this->token_data = $this->storage->get(array_keys($tokens));
// Calculate the spaminess and importance for each token (or a degenerated form of it)
$word_count = [];
$rating = [];
$importance = [];
foreach ($tokens as $word => $count) {
$word_count[$word] = $count;
// Although we only call this function only here ... let's do the calculation stuff in a
// function to make this a bit less confusing ;-)
$rating[$word] = $this->get_probability($word, $internals);
$importance[$word] = abs(0.5 - $rating[$word]);
}
// Order by importance
arsort($importance);
reset($importance);
// Get the most interesting tokens (use all if we have less than the given number)
$relevant = [];
for ($i = 0; $i < $this->config['use_relevant']; $i++) {
if ($token = key($importance)) {
// Important tokens remain
// If the token's rating is relevant enough, use it
if (abs(0.5 - $rating[$token]) > $this->config['min_dev']) {
// Tokens that appear more than once also count more than once
for ($x = 0, $l = $word_count[$token]; $x < $l; $x++) {
array_push($relevant, $rating[$token]);
}
}
} else {
// We have less words as we want to use, so we already use what we have and can
// break here
break;
}
next($importance);
}
// Calculate the spaminess of the text (thanks to Mr. Robinson ;-)
// We set both haminess and spaminess to 1 for the first multiplying
$haminess = 1;
$spaminess = 1;
// Consider all relevant ratings
foreach ($relevant as $value) {
$haminess *= (1.0 - $value);
$spaminess *= $value;
}
// If no token was good for calculation, we really don't know how to rate this text, so
// we can return 0.5 without further calculations.
if ($haminess == 1 && $spaminess == 1) {
return 0.5;
}
// Calculate the combined rating
// Get the number of relevant ratings
$n = count($relevant);
// The actual haminess and spaminess
$haminess = 1 - pow($haminess, (1 / $n));
$spaminess = 1 - pow($spaminess, (1 / $n));
// Calculate the combined indicator
$probability = ($haminess - $spaminess) / ($haminess + $spaminess);
// We want a value between 0 and 1, not between -1 and +1, so ...
$probability = (1 + $probability) / 2;
// Alea iacta est
return $probability;
}
/**
* Calculate the spaminess of a single token also considering "degenerated" versions
*
* @access private
* @param string The word to rate
* @param array The "internals" array
* @return float The word's rating
*/
private function get_probability(string $word, array $internals)
{
// Let's see what we have!
if (isset($this->token_data['tokens'][$word])) {
// The token is in the database, so we can use it's data as-is and calculate the
// spaminess of this token directly
return $this->calculate_probability($this->token_data['tokens'][$word], $internals);
}
// The token was not found, so do we at least have similar words?
if (isset($this->token_data['degenerates'][$word])) {
// We found similar words, so calculate the spaminess for each one and choose the most
// important one for the further calculation
// The default rating is 0.5 simply saying nothing
$rating = 0.5;
foreach ($this->token_data['degenerates'][$word] as $degenerate => $count) {
// Calculate the rating of the current degenerated token
$rating_tmp = $this->calculate_probability($count, $internals);
// Is it more important than the rating of another degenerated version?
if(abs(0.5 - $rating_tmp) > abs(0.5 - $rating)) {
$rating = $rating_tmp;
}
}
return $rating;
} else {
// The token is really unknown, so choose the default rating for completely unknown
// tokens. This strips down to the robX parameter so we can cheap out the freaky math
// ;-)
return $this->config['rob_x'];
}
}
/**
* Do the actual spaminess calculation of a single token
*
* @access private
* @param array The token's data [ \b8\b8::KEY_COUNT_HAM => int,
\b8\b8::KEY_COUNT_SPAM => int ]
* @param array The "internals" array
* @return float The rating
*/
private function calculate_probability(array $data, array $internals)
{
// Calculate the basic probability as proposed by Mr. Graham
// But: consider the number of ham and spam texts saved instead of the number of entries
// where the token appeared to calculate a relative spaminess because we count tokens
// appearing multiple times not just once but as often as they appear in the learned texts.
$rel_ham = $data[\b8\b8::KEY_COUNT_HAM];
$rel_spam = $data[\b8\b8::KEY_COUNT_SPAM];
if ($internals[\b8\b8::KEY_TEXTS_HAM] > 0) {
$rel_ham = $data[\b8\b8::KEY_COUNT_HAM] / $internals[\b8\b8::KEY_TEXTS_HAM];
}
if ($internals[\b8\b8::KEY_TEXTS_SPAM] > 0) {
$rel_spam = $data[\b8\b8::KEY_COUNT_SPAM] / $internals[\b8\b8::KEY_TEXTS_SPAM];
}
$rating = $rel_spam / ($rel_ham + $rel_spam);
// Calculate the better probability proposed by Mr. Robinson
$all = $data[\b8\b8::KEY_COUNT_HAM] + $data[\b8\b8::KEY_COUNT_SPAM];
return (($this->config['rob_s'] * $this->config['rob_x']) + ($all * $rating))
/ ($this->config['rob_s'] + $all);
}
/**
* Check the validity of the category of a request
*
* @access private
* @param string The category
* @return void
*/
private function check_category(string $category)
{
return $category === \b8\b8::HAM || $category === \b8\b8::SPAM;
}
/**
* Learn a reference text
*
* @access public
* @param string The text to learn
* @param string Either b8::SPAM or b8::HAM
* @return mixed void or an error code
*/
public function learn(string $text = null, string $category = null)
{
// Let's first see if the user called the function correctly
if ($text === null) {
return \b8\b8::TRAINER_TEXT_MISSING;
}
if ($category === null) {
return \b8\b8::TRAINER_CATEGORY_MISSING;
}
return $this->process_text($text, $category, \b8\b8::LEARN);
}
/**
* Unlearn a reference text
*
* @access public
* @param string The text to unlearn
* @param string Either b8::SPAM or b8::HAM
* @return mixed void or an error code
*/
public function unlearn(string $text = null, string $category = null)
{
// Let's first see if the user called the function correctly
if ($text === null) {
return \b8\b8::TRAINER_TEXT_MISSING;
}
if ($category === null) {
return \b8\b8::TRAINER_CATEGORY_MISSING;
}
return $this->process_text($text, $category, \b8\b8::UNLEARN);
}
/**
* Does the actual interaction with the storage backend for learning or unlearning texts
*
* @access private
* @param string The text to process
* @param string Either b8::SPAM or b8::HAM
* @param string Either b8::LEARN or b8::UNLEARN
* @return mixed void or an error code
*/
private function process_text(string $text, string $category, string $action)
{
// Look if the request is okay
if (! $this->check_category($category)) {
return \b8\b8::TRAINER_CATEGORY_FAIL;
}
// Get all tokens from $text
$tokens = $this->lexer->get_tokens($text);
// Check if the lexer failed (if so, $tokens will be a lexer error code, if not, $tokens
// will be an array)
if (! is_array($tokens)) {
return $tokens;
}
// Pass the tokens and what to do with it to the storage backend
return $this->storage->process_text($tokens, $category, $action);
}
}

View file

@ -0,0 +1,176 @@
<?php
/* Copyright (C) 2006-2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* A helper class to derive simplified tokens
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
*/
namespace b8\degenerator;
class standard
{
public $config = [ 'multibyte' => true,
'encoding' => 'UTF-8' ];
public $degenerates = [];
/**
* Constructs the degenerator.
*
* @access public
* @param array $config The configuration: [ 'multibyte' => bool,
'encoding' => string ]
* @return void
*/
public function __construct(array $config)
{
// Validate config data
foreach ($config as $name => $value) {
switch($name) {
case 'multibyte':
$this->config[$name] = (bool) $value;
break;
case 'encoding':
$this->config[$name] = (string) $value;
break;
default:
throw new \Exception(standard::class . ": Unknown configuration key: "
. "\"$name\"");
}
}
}
/**
* Generates a list of "degenerated" words for a list of words.
*
* @access public
* @param array $words The words to degenerate
* @return array An array containing an array of degenerated tokens for each token
*/
public function degenerate(array $words)
{
$degenerates = [];
foreach ($words as $word) {
$degenerates[$word] = $this->degenerate_word($word);
}
return $degenerates;
}
/**
* Remove duplicates from a list of degenerates of a word.
*
* @access private
* @param string $word The word
* @param array $list The list to process
* @return array The list without duplicates
*/
private function delete_duplicates(string $word, array $list)
{
$list_processed = [];
// Check each upper/lower version
foreach ($list as $alt_word) {
if ($alt_word != $word) {
array_push($list_processed, $alt_word);
}
}
return $list_processed;
}
/**
* Builds a list of "degenerated" versions of a word.
*
* @access private
* @param string $word The word
* @return array An array of degenerated words
*/
private function degenerate_word(string $word)
{
// Check for any stored words so the process doesn't have to repeat
if (isset($this->degenerates[$word]) === true) {
return $this->degenerates[$word];
}
// Create different versions of upper and lower case
if ($this->config['multibyte'] === false) {
// The standard upper/lower versions
$lower = strtolower($word);
$upper = strtoupper($word);
$first = substr($upper, 0, 1) . substr($lower, 1, strlen($word));
} elseif ($this->config['multibyte'] === true) {
// The multibyte upper/lower versions
$lower = mb_strtolower($word, $this->config['encoding']);
$upper = mb_strtoupper($word, $this->config['encoding']);
$first = mb_substr($upper, 0, 1, $this->config['encoding'])
. mb_substr($lower, 1, mb_strlen($word), $this->config['encoding']);
}
// Add the versions
$upper_lower = [];
array_push($upper_lower, $lower);
array_push($upper_lower, $upper);
array_push($upper_lower, $first);
// Delete duplicate upper/lower versions
$degenerate = $this->delete_duplicates($word, $upper_lower);
// Append the original word
array_push($degenerate, $word);
// Degenerate all versions
foreach ($degenerate as $alt_word) {
// Look for stuff like !!! and ???
if (preg_match('/[!?]$/', $alt_word) > 0) {
// Add versions with different !s and ?s
if (preg_match('/[!?]{2,}$/', $alt_word) > 0) {
$tmp = preg_replace('/([!?])+$/', '$1', $alt_word);
array_push($degenerate, $tmp);
}
$tmp = preg_replace('/([!?])+$/', '', $alt_word);
array_push($degenerate, $tmp);
}
// Look for "..." at the end of the word
$alt_word_int = $alt_word;
while (preg_match('/[\.]$/', $alt_word_int) > 0) {
$alt_word_int = substr($alt_word_int, 0, strlen($alt_word_int) - 1);
array_push($degenerate, $alt_word_int);
}
}
// Some degenerates are the same as the original word. These don't have to be fetched, so we
// create a new array with only new tokens
$degenerate = $this->delete_duplicates($word, $degenerate);
// Store the list of degenerates for the token to prevent unnecessary re-processing
$this->degenerates[$word] = $degenerate;
return $degenerate;
}
}

View file

@ -0,0 +1,267 @@
<?php
/* Copyright (C) 2006-2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* A helper class to disassemble a text to tokens
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
* @author Oliver Lillie <ollie@buggedcom.co.uk> (original PHP 5 port)
*/
namespace b8\lexer;
class standard
{
const LEXER_TEXT_NOT_STRING = 'LEXER_TEXT_NOT_STRING';
const LEXER_TEXT_EMPTY = 'LEXER_TEXT_EMPTY';
const LEXER_NO_TOKENS = 'b8*no_tokens';
private $config = [ 'min_size' => 3,
'max_size' => 30,
'get_uris' => true,
'get_html' => true,
'get_bbcode' => false,
'allow_numbers' => false ];
private $tokens = null;
private $processed_text = null;
// The regular expressions we use to split the text to tokens
private $regexp = [ 'raw_split' => '/[\s,\.\/"\:;\|<>\-_\[\]{}\+=\)\(\*\&\^%]+/',
'ip' => '/([A-Za-z0-9\_\-\.]+)/',
'uris' => '/([A-Za-z0-9\_\-]*\.[A-Za-z0-9\_\-\.]+)/',
'html' => '/(<.+?>)/',
'bbcode' => '/(\[.+?\])/',
'tagname' => '/(.+?)\s/',
'numbers' => '/^[0-9]+$/' ];
/**
* Constructs the lexer.
*
* @access public
* @param array $config The configuration: [ 'min_size' => int,
* 'max_size' => int,
* 'get_uris' => bool,
* 'get_html' => bool,
* 'get_bbcode' => bool,
* 'allow_numbers' => bool ]
* @return void
*/
function __construct(array $config)
{
// Validate config data
foreach ($config as $name=>$value) {
switch ($name) {
case 'min_size':
case 'max_size':
$this->config[$name] = (int) $value;
break;
case 'allow_numbers':
case 'get_uris':
case 'get_html':
case 'get_bbcode':
$this->config[$name] = (bool) $value;
break;
default:
throw new \Exception(standard::class . ": Unknown configuration key: "
. "\"$name\"");
}
}
}
/**
* Splits a text to tokens.
*
* @access public
* @param string $text The text to disassemble
* @return mixed Returns a list of tokens or an error code
*/
public function get_tokens(string $text)
{
// Check if we actually have a string ...
if (is_string($text) === false) {
return self::LEXER_TEXT_NOT_STRING;
}
// ... and if it's empty
if (empty($text) === true) {
return self::LEXER_TEXT_EMPTY;
}
// Re-convert the text to the original characters coded in UTF-8, as they have been coded in
// html entities during the post process
$this->processed_text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
// Reset the token list
$this->tokens = array();
if ($this->config['get_uris'] === true) {
// Get URIs
$this->get_uris($this->processed_text);
}
if ($this->config['get_html'] === true) {
// Get HTML
$this->get_markup($this->processed_text, $this->regexp['html']);
}
if ($this->config['get_bbcode'] === true) {
// Get BBCode
$this->get_markup($this->processed_text, $this->regexp['bbcode']);
}
// We always want to do a raw split of the (remaining) text, so:
$this->raw_split($this->processed_text);
// Be sure not to return an empty array
if (count($this->tokens) == 0) {
$this->tokens[self::LEXER_NO_TOKENS] = 1;
}
// Return a list of all found tokens
return $this->tokens;
}
/**
* Validates a token.
*
* @access private
* @param string $token The token string
* @return bool Returns true if the token is valid, otherwise returns false.
*/
private function is_valid(string $token)
{
// Just to be sure that the token's name won't collide with b8's internal variables
if (substr($token, 0, 3) == 'b8*') {
return false;
}
// Validate the size of the token
$len = strlen($token);
if ($len < $this->config['min_size'] || $len > $this->config['max_size']) {
return false;
}
// We may want to exclude pure numbers
if ($this->config['allow_numbers'] === false
&& preg_match($this->regexp['numbers'], $token) > 0) {
return false;
}
// Token is okay
return true;
}
/**
* Checks the validity of a token and adds it to the token list if it's valid.
*
* @access private
* @param string $token
* @param string $word_to_remove Word to remove from the processed string
* @return void
*/
private function add_token(string $token, string $word_to_remove = null)
{
// Check the validity of the token
if (! $this->is_valid($token)) {
return;
}
// Add it to the list or increase it's counter
if (! isset($this->tokens[$token])) {
$this->tokens[$token] = 1;
} else {
$this->tokens[$token] += 1;
}
// If requested, remove the word or it's original version from the text
if ($word_to_remove !== null) {
$this->processed_text = str_replace($word_to_remove, '', $this->processed_text);
}
}
/**
* Gets URIs.
*
* @access private
* @param string $text
* @return void
*/
private function get_uris(string $text)
{
// Find URIs
preg_match_all($this->regexp['uris'], $text, $raw_tokens);
foreach ($raw_tokens[1] as $word) {
// Remove a possible trailing dot
$word = rtrim($word, '.');
// Try to add the found tokens to the list
$this->add_token($word, $word);
// Also process the parts of the found URIs
$this->raw_split($word);
}
}
/**
* Gets HTML or BBCode markup, depending on the regexp used.
*
* @access private
* @param string $text
* @param string $regexp
* @return void
*/
private function get_markup(string $text, string $regexp)
{
// Search for the markup
preg_match_all($regexp, $text, $raw_tokens);
foreach ($raw_tokens[1] as $word) {
$actual_word = $word;
// If the tag has parameters, just use the tag itself
if (strpos($word, ' ') !== false) {
preg_match($this->regexp['tagname'], $word, $match);
$actual_word = $match[1];
$word = "$actual_word..." . substr($word, -1);
}
// Try to add the found tokens to the list
$this->add_token($word, $actual_word);
}
}
/**
* Does a raw split.
*
* @access private
* @param string $text
* @return void
*/
private function raw_split(string $text)
{
foreach (preg_split($this->regexp['raw_split'], $text) as $word) {
// Check the word and add it to the token list if it's valid
$this->add_token($word);
}
}
}

View file

@ -0,0 +1,105 @@
<?php
/* Copyright (C) 2006-2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
namespace b8\storage;
/**
* A Berkeley DB (DBA) storage backend
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
*/
class dba extends storage_base
{
private $db = null;
protected function setup_backend(array $config)
{
if (! isset($config['resource'])
|| gettype($config['resource']) !== 'resource'
|| get_resource_type($config['resource']) !== 'dba') {
throw new \Exception(dba::class . ": No valid DBA resource passed");
}
$this->db = $config['resource'];
}
protected function fetch_token_data(array $tokens)
{
$data = [];
foreach ($tokens as $token) {
// Try to the raw data in the format "count_ham count_spam"
$count = dba_fetch($token, $this->db);
if ($count !== false) {
// Split the data by space characters
$split_data = explode(' ', $count);
// As an internal variable may have just one single value, we have to check for this
$count_ham = isset($split_data[0]) ? (int) $split_data[0] : null;
$count_spam = isset($split_data[1]) ? (int) $split_data[1] : null;
// Append the parsed data
$data[$token] = [ \b8\b8::KEY_COUNT_HAM => $count_ham,
\b8\b8::KEY_COUNT_SPAM => $count_spam ];
}
}
return $data;
}
private function assemble_count_value(array $count)
{
// Assemble the count data string
$count_value = $count[\b8\b8::KEY_COUNT_HAM] . ' ' . $count[\b8\b8::KEY_COUNT_SPAM];
// Remove whitespace from data of the internal variables
return(rtrim($count_value));
}
protected function add_token(string $token, array $count)
{
return dba_insert($token, $this->assemble_count_value($count), $this->db);
}
protected function update_token(string $token, array $count)
{
return dba_replace($token, $this->assemble_count_value($count), $this->db);
}
protected function delete_token(string $token)
{
return dba_delete($token, $this->db);
}
protected function start_transaction()
{
return;
}
protected function finish_transaction()
{
return;
}
}

View file

@ -0,0 +1,110 @@
<?php
/* Copyright (C) 2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
namespace b8\storage;
/**
* A MySQL storage backend
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
*/
class mysql extends storage_base
{
private $mysql = null;
private $table = null;
protected function setup_backend(array $config)
{
if (! isset($config['resource'])
|| get_class($config['resource']) !== 'mysqli') {
throw new \Exception(mysql::class . ": No valid mysqli object passed");
}
$this->mysql = $config['resource'];
if (! isset($config['table'])) {
throw new \Exception(mysql::class . ": No b8 wordlist table name passed");
}
$this->table = $config['table'];
}
protected function fetch_token_data(array $tokens)
{
$data = [];
$escaped = [];
foreach ($tokens as $token) {
$escaped[] = $this->mysql->real_escape_string($token);
}
$result = $this->mysql->query('SELECT token, count_ham, count_spam'
. ' FROM ' . $this->table
. ' WHERE token IN '
. "('" . implode("','", $escaped) . "')");
while ($row = $result->fetch_row()) {
$data[$row[0]] = [ \b8\b8::KEY_COUNT_HAM => $row[1],
\b8\b8::KEY_COUNT_SPAM => $row[2] ];
}
$result->free_result();
return $data;
}
protected function add_token(string $token, array $count)
{
$query = $this->mysql->prepare('INSERT INTO ' . $this->table
. '(token, count_ham, count_spam) VALUES(?, ?, ?)');
$query->bind_param('sii', $token, $count[\b8\b8::KEY_COUNT_HAM],
$count[\b8\b8::KEY_COUNT_SPAM]);
$query->execute();
}
protected function update_token(string $token, array $count)
{
$query = $this->mysql->prepare('UPDATE ' . $this->table
. ' SET count_ham = ?, count_spam = ? WHERE token = ?');
$query->bind_param('iis', $count[\b8\b8::KEY_COUNT_HAM], $count[\b8\b8::KEY_COUNT_SPAM],
$token);
$query->execute();
}
protected function delete_token(string $token)
{
$query = $this->mysql->prepare('DELETE FROM ' . $this->table . ' WHERE token = ?');
$query->bind_param('s', $token);
$query->execute();
}
protected function start_transaction()
{
$this->mysql->begin_transaction();
}
protected function finish_transaction()
{
$this->mysql->commit();
}
}

View file

@ -0,0 +1,108 @@
<?php
/* Copyright (C) 2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
namespace b8\storage;
use PDO;
/**
* A sqlite storage backend
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
*/
class sqlite extends storage_base
{
private $sqlite = null;
private $table = null;
protected function setup_backend(array $config)
{
$this->sqlite = $config['resource'];
if (! isset($config['table'])) {
$config['table'] = 'b8_wordlist';
}
$this->table = $config['table'];
}
protected function fetch_token_data(array $tokens)
{
$data = [];
$escaped = [];
foreach ($tokens as $token) {
$escaped[] = $this->sqlite->quote($token);
}
$result = $this->sqlite->query('SELECT token, count_ham, count_spam'
. ' FROM ' . $this->table
. ' WHERE token IN '
. "(" . implode(",", $escaped) . ")");
while ($row = $result->fetch()) {
$data[$row[0]] = [ \b8\b8::KEY_COUNT_HAM => $row[1],
\b8\b8::KEY_COUNT_SPAM => $row[2] ];
}
return $data;
}
protected function add_token(string $token, array $count)
{
$query = $this->sqlite->prepare('INSERT INTO ' . $this->table
. '(token, count_ham, count_spam) VALUES(?, ?, ?)');
$query->bindParam(1, $token, PDO::PARAM_STR);
$query->bindParam(2, $count[\b8\b8::KEY_COUNT_HAM], PDO::PARAM_INT);
$query->bindParam(3, $count[\b8\b8::KEY_COUNT_SPAM], PDO::PARAM_INT);
$query->execute();
}
protected function update_token(string $token, array $count)
{
$query = $this->sqlite->prepare('UPDATE ' . $this->table
. ' SET count_ham = ?, count_spam = ? WHERE token = ?');
$query->bindParam(1, $count[\b8\b8::KEY_COUNT_HAM], PDO::PARAM_INT);
$query->bindParam(2, $count[\b8\b8::KEY_COUNT_SPAM], PDO::PARAM_INT);
$query->bindParam(3, $token, PDO::PARAM_STR);
$query->execute();
}
protected function delete_token(string $token)
{
$query = $this->sqlite->prepare('DELETE FROM ' . $this->table . ' WHERE token = ?');
$query->bindParam(1, $token, PDO::PARAM_STR);
$query->execute();
}
protected function start_transaction()
{
$this->sqlite->beginTransaction();
}
protected function finish_transaction()
{
$this->sqlite->commit();
}
}

View file

@ -0,0 +1,316 @@
<?php
/* Copyright (C) 2006-2019 Tobias Leupold <tobias.leupold@gmx.de>
This file is part of the b8 package
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation in version 2.1 of the License.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* Abstract base class for storage backends
*
* @license LGPL 2.1
* @package b8
* @author Tobias Leupold <tobias.leupold@gmx.de>
*/
namespace b8\storage;
abstract class storage_base
{
protected $degenerator = null;
/**
* Sets up the backend
*
* @access public
* @param array The configuration for the respective backend
*/
abstract protected function setup_backend(array $config);
/**
* Does the actual interaction with the database when fetching data
*
* @access protected
* @param array $tokens List of token names to fetch
* @return mixed Returns an array of the returned data in the format array(token => data)
or an empty array if there was no data.
*/
abstract protected function fetch_token_data(array $tokens);
/**
* Stores a new token to the database
*
* @access protected
* @param string $token The token's name
* @param array $count The ham and spam counters [ \b8\b8::KEY_COUNT_HAM => int,
\b8\b8::KEY_COUNT_SPAM => int ]
* @return bool true on success or false on failure
*/
abstract protected function add_token(string $token, array $count);
/**
* Updates an existing token
*
* @access protected
* @param string $token The token's name
* @param array $count The ham and spam counters [ \b8\b8::KEY_COUNT_HAM => int,
\b8\b8::KEY_COUNT_SPAM => int ]
* @return bool true on success or false on failure
*/
abstract protected function update_token(string $token, array $count);
/**
* Removes a token from the database
*
* @access protected
* @param string $token The token's name
* @return bool true on success or false on failure
*/
abstract protected function delete_token(string $token);
/**
* Starts a transaction (if the underlying database supports/needs this)
*
* @access protected
* @return void
*/
abstract protected function start_transaction();
/**
* Finishes a transaction (if the underlying database supports/needs this)
*
* @access protected
* @return void
*/
abstract protected function finish_transaction();
/**
* Passes the degenerator to the instance and calls the backend setup
*
* @access public
* @param array The respective backen's configuration
* @param object The degenerator to use
* @return void
*/
public function __construct(array $config, object $degenerator)
{
$this->degenerator = $degenerator;
$this->setup_backend($config);
$internals = $this->get_internals();
if (! isset($internals[\b8\b8::KEY_DB_VERSION])
|| $internals[\b8\b8::KEY_DB_VERSION] !== \b8\b8::DBVERSION) {
throw new \Exception(storage_base::class . ': The connected database is not a b8 v'
. \b8\b8::DBVERSION . ' database.');
}
}
/**
* Get the database's internal variables.
*
* @access public
* @return array Returns an array of all internals.
*/
public function get_internals()
{
$internals = $this->fetch_token_data([ \b8\b8::INTERNALS_TEXTS,
\b8\b8::INTERNALS_DBVERSION ]);
// Just in case this is called by check_database() and it's not yet clear if we actually
// have a b8 database
$texts_ham = null;
$texts_spam = null;
$dbversion = null;
if(isset($internals[\b8\b8::INTERNALS_TEXTS][\b8\b8::KEY_COUNT_HAM])) {
$texts_ham = (int) $internals[\b8\b8::INTERNALS_TEXTS][\b8\b8::KEY_COUNT_HAM];
}
if(isset($internals[\b8\b8::INTERNALS_TEXTS][\b8\b8::KEY_COUNT_SPAM])) {
$texts_spam = (int) $internals[\b8\b8::INTERNALS_TEXTS][\b8\b8::KEY_COUNT_SPAM];
}
if(isset($internals[\b8\b8::INTERNALS_DBVERSION][\b8\b8::KEY_COUNT_HAM])) {
$dbversion = (int) $internals[\b8\b8::INTERNALS_DBVERSION][\b8\b8::KEY_COUNT_HAM];
}
return [ \b8\b8::KEY_TEXTS_HAM => $texts_ham,
\b8\b8::KEY_TEXTS_SPAM => $texts_spam,
\b8\b8::KEY_DB_VERSION => $dbversion ];
}
/**
* Get all data about a list of tokens from the database.
*
* @access public
* @param array The tokens list
* @return mixed Returns False on failure, otherwise returns array of returned data
in the format [ 'tokens' => [ token => count ],
'degenerates' => [ token => [ degenerate => count ] ] ].
*/
public function get(array $tokens)
{
// First we see what we have in the database
$token_data = $this->fetch_token_data($tokens);
// Check if we have to degenerate some tokens
$missing_tokens = array();
foreach ($tokens as $token) {
if (! isset($token_data[$token])) {
$missing_tokens[] = $token;
}
}
if (count($missing_tokens) > 0) {
// We have to degenerate some tokens
$degenerates_list = [];
// Generate a list of degenerated tokens for the missing tokens ...
$degenerates = $this->degenerator->degenerate($missing_tokens);
// ... and look them up
foreach ($degenerates as $token => $token_degenerates) {
$degenerates_list = array_merge($degenerates_list, $token_degenerates);
}
$token_data = array_merge($token_data, $this->fetch_token_data($degenerates_list));
}
// Here, we have all available data in $token_data.
$return_data_tokens = [];
$return_data_degenerates = [];
foreach ($tokens as $token) {
if (isset($token_data[$token])) {
// The token was found in the database
$return_data_tokens[$token] = $token_data[$token];
} else {
// The token was not found, so we look if we can return data for degenerated tokens
foreach ($this->degenerator->degenerates[$token] as $degenerate) {
if (isset($token_data[$degenerate])) {
// A degenertaed version of the token way found in the database
$return_data_degenerates[$token][$degenerate] = $token_data[$degenerate];
}
}
}
}
// Now, all token data directly found in the database is in $return_data_tokens and all
// data for degenerated versions is in $return_data_degenerates, so
return [ 'tokens' => $return_data_tokens,
'degenerates' => $return_data_degenerates ];
}
/**
* Stores or deletes a list of tokens from the given category.
*
* @access public
* @param array The tokens list
* @param string Either \b8\b8::HAM or \b8\b8::SPAM
* @param string Either \b8\b8::LEARN or \b8\b8::UNLEARN
* @return void
*/
public function process_text(array $tokens, string $category, string $action)
{
// No matter what we do, we first have to check what data we have.
// First get the internals, including the ham texts and spam texts counter
$internals = $this->get_internals();
// Then, fetch all data for all tokens we have
$token_data = $this->fetch_token_data(array_keys($tokens));
$this->start_transaction();
// Process all tokens to learn/unlearn
foreach ($tokens as $token => $count) {
if (isset($token_data[$token])) {
// We already have this token, so update it's data
// Get the existing data
$count_ham = $token_data[$token][\b8\b8::KEY_COUNT_HAM];
$count_spam = $token_data[$token][\b8\b8::KEY_COUNT_SPAM];
// Increase or decrease the right counter
if ($action === \b8\b8::LEARN) {
if ($category === \b8\b8::HAM) {
$count_ham += $count;
} elseif ($category === \b8\b8::SPAM) {
$count_spam += $count;
}
} elseif ($action == \b8\b8::UNLEARN) {
if ($category === \b8\b8::HAM) {
$count_ham -= $count;
} elseif ($category === \b8\b8::SPAM) {
$count_spam -= $count;
}
}
// We don't want to have negative values
if ($count_ham < 0) {
$count_ham = 0;
}
if ($count_spam < 0) {
$count_spam = 0;
}
// Now let's see if we have to update or delete the token
if ($count_ham != 0 or $count_spam != 0) {
$this->update_token($token, [ \b8\b8::KEY_COUNT_HAM => $count_ham,
\b8\b8::KEY_COUNT_SPAM => $count_spam ]);
} else {
$this->delete_token($token);
}
} else {
// We don't have the token. If we unlearn a text, we can't delete it as we don't
// have it anyway, so just do something if we learn a text
if ($action === \b8\b8::LEARN) {
if ($category === \b8\b8::HAM) {
$this->add_token($token, [ \b8\b8::KEY_COUNT_HAM => $count,
\b8\b8::KEY_COUNT_SPAM => 0 ]);
} elseif ($category === \b8\b8::SPAM) {
$this->add_token($token, [ \b8\b8::KEY_COUNT_HAM => 0,
\b8\b8::KEY_COUNT_SPAM => $count ]);
}
}
}
}
// Now, all token have been processed, so let's update the right text
if ($action === \b8\b8::LEARN) {
if ($category === \b8\b8::HAM) {
$internals[\b8\b8::KEY_TEXTS_HAM]++;
} elseif ($category === \b8\b8::SPAM) {
$internals[\b8\b8::KEY_TEXTS_SPAM]++;
}
} elseif ($action === \b8\b8::UNLEARN) {
if ($category === \b8\b8::HAM) {
if ($internals[\b8\b8::KEY_TEXTS_HAM] > 0) {
$internals[\b8\b8::KEY_TEXTS_HAM]--;
}
} elseif ($category === \b8\b8::SPAM) {
if ($internals[\b8\b8::KEY_TEXTS_SPAM] > 0) {
$internals[\b8\b8::KEY_TEXTS_SPAM]--;
}
}
}
$this->update_token(\b8\b8::INTERNALS_TEXTS,
[ \b8\b8::KEY_COUNT_HAM => $internals[\b8\b8::KEY_TEXTS_HAM],
\b8\b8::KEY_COUNT_SPAM => $internals[\b8\b8::KEY_TEXTS_SPAM] ]);
$this->finish_transaction();
}
}

View file

@ -1,27 +0,0 @@
<div id="bayesContent">
{foreach from=$comments item=comment}
<h3>{$CONST.COMMENT} #{$comment.id}</h3>
<ul class="plainList bayesAnalysis">
{foreach from=$types item=type}
<li class="ratingBox">
<div class="commentType">{$type}</div>
<div class="commentPart">{$comment.$type|escape:"html"}</div>
<div class="rating">
{if $comment.ratings.$type != "-"}
{$comment.ratings.$type|regex_replace:"/\..*/":""}%
{else}
{$comment.ratings.$type}
{/if}
</div>
</li>
{/foreach}
</ul>
<div class="finalRating">{$comment.rating|regex_replace:"/\..*/":""}%</div>
{/foreach}
<script src="{$path}jquery.excerpt.js" type="text/javascript"></script>
<script>shortenAll("commentPart", 1)
colorize()</script>
</div>

View file

@ -1,73 +0,0 @@
<div id="bayesContent">
<div id="bayesAnalysis">
{if $s9ybackend == 1}
<div class="bayesAnalysisTableNavigation">
{else}
<ul class="bayesAnalysisTableNavigation plainList clearfix">
{/if}
{if $commentpage > 0}
{if $s9ybackend == 1}
<a class="serendipityIconLink" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage-1}"><img src="{serendipity_getFile file="admin/img/previous.png"}"/>{$CONST.PREVIOUS}</a>
{else}
<li class="prev"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage-1}" title="{$CONST.PREVIOUS}"><span class="icon-left-dir" aria-hidden="true"></span><span class="visuallyhidden"> {$CONST.PREVIOUS}</span></a></li>
{/if}
{/if}
{if $comments|@count > 20}
{if $s9ybackend == 1}
<a class="serendipityIconLinkRight" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage+1}">{$CONST.NEXT} <img src="{serendipity_getFile file="admin/img/next.png"}"/></a>
{else}
<li class="next"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage+1}" title="{$CONST.NEXT}"><span class="visuallyhidden">{$CONST.NEXT} </span><span class="icon-right-dir" aria-hidden="true"></span></a></li>
{/if}
{/if}
{if $s9ybackend == 1}
</div>
{else}
</ul>
{/if}
<form action="{$serendipityBaseURL}index.php?/plugin/bayesAnalyse" method="post">
<ul id="bayesAnalysisList" class="plainList">
{foreach from=$comments item=comment }
<li>
<input type="checkbox" id="{$comment.id}" name="comments[{$comment.id}]" />
<label for="{$comment.id}">{$comment.id}:</label>
<div class="bayesComments">
{$comment.author|escape:"html"}, {$comment.body|escape:"html"}
</div>
</li>
{/foreach}
<input type="submit" class="serendipityPrettyButton input_button" id="bayesAnalysisButton" value="{$CONST.GO}" />
</ul>
</form>
<script src="{$path}jquery.excerpt.js" type="text/javascript"></script>
<script>shortenAll("bayesComments", 1)</script>
{if $s9ybackend == 1}
<div class="bayesAnalysisTableNavigation">
{else}
<ul class="bayesAnalysisTableNavigation plainList clearfix">
{/if}
{if $commentpage > 0}
{if $s9ybackend == 1}
<a class="serendipityIconLink" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage-1}"><img src="{serendipity_getFile file="admin/img/previous.png"}"/>{$CONST.PREVIOUS}</a>
{else}
<li class="prev"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage-1}" title="{$CONST.PREVIOUS}"><span class="icon-left-dir" aria-hidden="true"></span><span class="visuallyhidden"> {$CONST.PREVIOUS}</span></a></li>
{/if}
{/if}
{if $comments|@count > 20}
{if $s9ybackend == 1}
<a class="serendipityIconLinkRight" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage+1}">{$CONST.NEXT} <img src="{serendipity_getFile file="admin/img/next.png"}"/></a>
{else}
<li class="next"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=4&amp;serendipity[commentpage]={$commentpage+1}" title="{$CONST.NEXT}"><span class="visuallyhidden">{$CONST.NEXT} </span><span class="icon-right-dir" aria-hidden="true"></span></a></li>
{/if}
{/if}
{if $s9ybackend == 1}
</div>
{else}
</ul>
{/if}
</div>
</div>

View file

@ -1,112 +0,0 @@
<div id="bayesContent">
<div id="bayesControls">
<form action="{$serendipityBaseURL}index.php?/plugin/bayesSetupDatabase" method="post">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_CREATEDB}" name="submit"/>
</form>
<form action="{$serendipityBaseURL}index.php?/plugin/bayesLearnFromOld" method="post">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_LEARNOLD}" name="submit"/>
</form>
<form id="bayesDeleteDB" action="{$serendipityBaseURL}index.php?/plugin/bayesDeleteDatabase" method="post">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_ERASEDB}" name="submit"/>
</form>
<form action="{$serendipityBaseURL}index.php?/plugin/bayesExportDatabase" method="post">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_EXPORTDB}" name="submit"/>
</form>
<form action="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=5" method="post">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_IMPORTDB}" name="submit"/>
</form>
</div>
<div id="bayesDatabase">
<table id="bayesDatabaseTable">
<caption>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_DATABASE}</caption>
<thead>
<tr>
<th>token</th>
<th>ham</th>
<th>spam</th>
<th>type</th>
</tr>
</thead>
<tbody>
{foreach from=$bayesTable item=row}
<tr>
{foreach from=$row item=value}
<td>
{$value}
</td>
{/foreach}
</tr>
{/foreach}
</tbody>
</table>
{if $pages > 1}
<div id="bayesDatabaseTablePagination">
{if $curpage > 2}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]=0" title="{$CONST.Page}">1</a>
...
{elseif $curpage > 1}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]=0" title="{$CONST.Page}">1</a>
{/if}
{section name=page start=1 loop=$pages+1}
{if $curpage == $smarty.section.page.index -1}
<a class="curpage" href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]={$smarty.section.page.index-1}" title="{$CONST.Page}">{$smarty.section.page.index}</a>
{/if}
{if $curpage == $smarty.section.page.index -2 || $curpage == $smarty.section.page.index}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]={$smarty.section.page.index-1}" title="{$CONST.Page}">{$smarty.section.page.index}</a>
{/if}
{/section}
{if $curpage < $pages -3}
...
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]={$pages-1}" title="{$CONST.Page}">{$pages}</a>
{elseif $curpage < $pages -2}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2&serendipity[commentpage]={$pages-1}" title="{$CONST.Page}">{$pages}</a>
{/if}
</div>
{/if}
<div id="bayesSavedValues">
<table id="bayesSavedValuesTable">
<caption>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_SAVEDVALUES}</caption>
<thead>
<tr>
<th colspan="2">{$CONST.NAME}</th>
<th colspan="2">{$CONST.HOMEPAGE}</th>
<th colspan="2">{$CONST.EMAIL}</th>
<th colspan="2">{$CONST.IP}</th>
<th colspan="2">{$CONST.REFERER}</th>
<th colspan="2">{$CONST.COMMENT}</th>
</tr>
<tr>
{section name=i loop=6 start=0}
<th>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_HAM}</th>
<th>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_SPAM}</th>
{/section}
</tr>
</thead>
<tbody>
<tr>
<td>{$author_ham}</td>
<td>{$author_spam}</td>
<td>{$url_ham}</td>
<td>{$url_spam}</td>
<td>{$email_ham}</td>
<td>{$email_spam}</td>
<td>{$ip_ham}</td>
<td>{$ip_spam}</td>
<td>{$referer_ham}</td>
<td>{$referer_spam}</td>
<td>{$body_ham}</td>
<td>{$body_spam}</td>
</tr>
</tbody>
</table>
</div>
<script src="{$path}jquery.heatcolor.js" type="text/javascript"></script>
<script src="{$path}jquery.tablesorter.js" type="text/javascript"></script>
<script>$("#bayesDatabaseTable").tablesorter();
sortwithcolor(2);</script>
</div>

View file

@ -1,31 +0,0 @@
<div id="bayesContent">
{if $s9ybackend == 1}<p>{else}<span class="msg_hint"><span class="icon-help-circled" aria-hidden="true"></span> {/if}{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_IMPORT_EXPLANATION}{if $s9ybackend == 1}</p>{else}</span>{/if}
<form enctype="multipart/form-data" action="{$serendipityBaseURL}index.php?/plugin/spamblock_bayes_import" method="post">
{if $s9ybackend != 1}
<div class="form_field">
{/if}
<input name="importcsv" type="file" />
<input class="serendipityPrettyButton input_button" type="submit" value="{$CONST.GO}" />
{if $s9ybackend != 1}
</div>
{/if}
</form>
<h3>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_TROJA}</h3>
{if $s9ybackend == 1}<p>{else}<span class="msg_hint"><span class="icon-help-circled" aria-hidden="true"></span> {/if}{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_TROJA_EXPLANATION}{if $s9ybackend == 1}</p>{else}</span>{/if}
<form{if $s9ybackend != 1} class="bayesTrojaButtons"{/if} action="{$serendipityBaseURL}index.php?/plugin/bayesTrojaRequestDB" method="post">
<input id="trojaImport" class="serendipityPrettyButton input_button" type="submit" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_TROJA_IMPORT}" />
</form>
{if $trojaRegistered}
<form class="bayesTrojaButtons" action="{$serendipityBaseURL}index.php?/plugin/bayesTrojaRemove" method="post">
<input class="serendipityPrettyButton input_button" type="submit" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_TROJA_REMOVE}" />
</form>
{else}
<form class="bayesTrojaButtons" action="{$serendipityBaseURL}index.php?/plugin/bayesTrojaRegister" method="post">
<input class="serendipityPrettyButton input_button" type="submit" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_TROJA_REGISTER}" />
</form>
{/if}
</div>

View file

@ -1,86 +0,0 @@
<div id="bayesContent">
<form id="bayesLearnForm" action="{$serendipityBaseURL}index.php?/plugin/bayesMenuLearn" method="post">
{if $s9ybackend == 1}
<table id="bayesLearnTable">
<tr>
<td><label for="bayesCommentName">{$CONST.NAME}</label></td>
<td><input type="text" id="bayesCommentName" name="author"></input></td>
</tr>
<tr>
<td><label for="bayesCommentUrl">{$CONST.HOMEPAGE}</label></td>
<td><input type="text" id="bayesCommentUrl" name="url"></input></td>
</tr>
<tr>
<td><label for="bayesCommentEmail">{$CONST.EMAIL}</label></td>
<td><input type="text" id="bayesCommentEmail" name="email"></input></td>
</tr>
<tr>
<td><label for="bayesCommentIp">IP</label></td>
<td><input type="text" id="bayesCommentIp" name="ip"></input></td>
</tr>
<tr>
<td><label for="bayesCommentReferrer">{$CONST.REFERER}</label></td>
<td><input type="text" id="bayesCommentReferrer" name="referrer"></input></td>
</tr>
<tr>
<td><label for="bayesCommentBody">{$CONST.COMMENT}</label></td>
<td><textarea rows="10" cols="40" id="bayesCommentBody" name="body"></textarea></td>
</tr>
<tr>
<td></td>
<td><label for="bayesCommentHam">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_HAM}</label>
<input class="direction_ltr input_radio" type="radio" id="bayesCommentHam" name="ham" value="true" checked="" title="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_HAM}">
<label for="bayesCommentSpam">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_SPAM}</label>
<input class="direction_ltr input_radio" type="radio" id="bayesCommentSpam" name="ham" value="false" title="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_SPAM}"></td>
</tr>
</table>
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.SAVE}" name="submit"/>
{else}
<div class="form_field">
<label for="bayesCommentName">{$CONST.NAME}</label>
<input type="text" id="bayesCommentName" name="author">
</div>
<div class="form_field">
<label for="bayesCommentUrl">{$CONST.HOMEPAGE}</label>
<input type="text" id="bayesCommentUrl" name="url">
</div>
<div class="form_field">
<label for="bayesCommentEmail">{$CONST.EMAIL}</label>
<input type="text" id="bayesCommentEmail" name="email">
</div>
<div class="form_field">
<label for="bayesCommentIp">IP</label>
<input type="text" id="bayesCommentIp" name="ip">
</div>
<div class="form_field">
<label for="bayesCommentReferrer">{$CONST.REFERER}</label>
<input type="text" id="bayesCommentReferrer" name="referrer">
</div>
<div class="form_area">
<label for="bayesCommentBody">{$CONST.COMMENT}</label>
<textarea rows="10" id="bayesCommentBody" name="body"></textarea>
</div>
<div class="clearfix">
<div class="form_radio">
<label for="bayesCommentHam">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_HAM}</label>
<input class="direction_ltr" type="radio" id="bayesCommentHam" name="ham" value="true" checked="">
</div>
<div class="form_radio">
<label for="bayesCommentSpam">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_SPAM}</label>
<input class="direction_ltr" type="radio" id="bayesCommentSpam" name="ham" value="false">
</div>
</div>
<div class="form_buttons">
<input type="submit" value="{$CONST.SAVE}" name="submit">
</div>
{/if}
</form>
</div>

View file

@ -1,32 +0,0 @@
{if $jquery_needed == true}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
{/if}
<style> {$css} </style>
<script src="{$path}serendipity_event_spamblock_bayes.js" type="text/javascript"></script>
{if $s9ybackend != 1}
<h2>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_NAME}</h2>
{/if}
<div id="bayesNav">
<ul>
<li{if $subpage == 1 && $s9ybackend != 1} class="current"{/if}>{if $subpage == 1 && $s9ybackend == 1}<h3>{/if}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_RECYCLER}</a>
{if $subpage == 1 && $s9ybackend == 1}</h3>{/if}
</li>
<li{if $subpage == 2 && $s9ybackend != 1} class="current"{/if}>{if $subpage == 2 && $s9ybackend == 1}<h3>{/if}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=2">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_DATABASE}</a>
{if $subpage == 2 && $s9ybackend == 1}</h3>{/if}
</li>
<li{if $subpage == 3 && $s9ybackend != 1} class="current"{/if}>{if $subpage == 3 && $s9ybackend == 1}<h3>{/if}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=3">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_LEARN}</a>
{if $subpage == 3 && $s9ybackend == 1}</h3>{/if}
</li>
<li{if $subpage == 4 && $s9ybackend != 1} class="current"{/if}>{if $subpage == 4 && $s9ybackend == 1}<h3>{/if}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_ANALYSIS}</a>
{if $subpage == 4 && $s9ybackend == 1}</h3>{/if}
</li>
<li{if $subpage == 5 && $s9ybackend != 1} class="current"{/if}>{if $subpage == 5 && $s9ybackend == 1}<h3>{/if}
<a href="?serendipity[adminModule]=event_display&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=5">{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_MENU_IMPORT}</a>
{if $subpage == 5 && $s9ybackend == 1}</h3>{/if}
</li>
</ul>
</div>

View file

@ -1,121 +1,39 @@
<h2>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_NAME}</h2>
<div id="bayesContent">
<form action="{$serendipityBaseUrl}index.php?/plugin/bayesRecycler" method="post">
<form action="{$serendipityBaseUrl}index.php?/plugin/bayes_recycle" method="post">
<div id="bayesControls">
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_RESTORE}" name="restore"/>
<input type="submit" class="serendipityPrettyButton input_button" value="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_RECYCLER_EMPTY}" name="empty" />
</div>
<div id="bayesRecycler">
{if $s9ybackend == 1}
<div class="bayesRecyclerTableNavigation">
{else}
<ul class="bayesRecyclerTableNavigation plainList clearfix">
{/if}
{if $commentpage > 0}
{if $s9ybackend == 1}
<a class="serendipityIconLink" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage-1}"><img src="{serendipity_getFile file="admin/img/previous.png"}"/>{$CONST.PREVIOUS}</a>
{else}
<li class="prev"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage-1}" title="{$CONST.PREVIOUS}"><span class="icon-left-dir" aria-hidden="true"></span><span class="visuallyhidden"> {$CONST.PREVIOUS}</span></a></li>
{/if}
{/if}
{if ($commentpage+1)*20 < $comments|@count}
{if $s9ybackend == 1}
<a class="serendipityIconLinkRight" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage+1}">{$CONST.NEXT} <img src="{serendipity_getFile file="admin/img/next.png"}"/></a>
{else}
<li class="next"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage+1}" title="{$CONST.NEXT}"><span class="visuallyhidden">{$CONST.NEXT} </span><span class="icon-right-dir" aria-hidden="true"></span></a></li>
{/if}
{/if}
{if $s9ybackend == 1}
</div>
{else}
</ul>
{/if}
{if $s9ybackend != 1}
<ul id="serendipity_comments_list" class="clearfix plainList zebra_list">
{/if}
{foreach from=$comments item=comment}
{if $s9ybackend == 1}
<details class="bayesRecyclerItem">
<summary>
<input type="checkbox" class="bayesRecyclerSelectBox" name="serendipity[selected][{$comment.id}]" />
<input type="hidden" name="serendipity[comments][{$comment.id}]" />
<table class="bayesRecyclerSummary">
<thead>
<tr>
<th>{$CONST.AUTHOR}</th>
<th>{$CONST.COMMENT}</th>
<th>{$CONST.DATE}</th>
<th>{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_RATING}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{$comment.author|truncate:20:"..."|escape:"html"}</td>
<td>{$comment.body|truncate:20:"..."|escape:"html"}</td>
<td>{$comment.timestamp|date_format:"%d.%m.%y, %R"}</td>
<td>{$comment.rating|regex_replace:"/\..*/":""}%</td>
</tr>
</tbody>
</table>
</summary>
<dl class="bayesRecyclerList">
{foreach from=$types item=type}
<dt>{$type}</dt>
<dd>{$comment.$type|escape:"html"}</dd>
{/foreach}
<dt>{$CONST.Article}</dt>
<dd><a href="{$comment.article_link}" target="_blank">{$comment.article_title}</a></dd>
</dl>
</details>
{else}
<ul id="serendipity_comments_list" class="clearfix plainList zebra_list">
{foreach from=$comments item=comment}
<li id="comment_{$comment.id}" class="clearfix {cycle values="odd,even"}">
<input type="hidden" name="serendipity[comments][{$comment.id}]">
<div class="form_check">
<input id="serendipity[selected][{$comment.id}]" type="checkbox" class="bayesRecyclerSelectBox" name="serendipity[selected][{$comment.id}]">
<label for="serendipity[selected][{$comment.id}]" class="visuallyhidden">{$CONST.TOGGLE_SELECT}</label>
</div>
<h4 id="c{$comment.id}">{$comment.author|truncate:20:"..."|escape:"html"} {$CONST.IN_REPLY_TO} <a href="{$comment.article_link}" target="_blank">{$comment.article_title}</a> {$CONST.ON} {$comment.timestamp|date_format:"%d.%m.%y, %R"} <span title="{$CONST.PLUGIN_EVENT_SPAMBLOCK_BAYES_RATING}">{$comment.rating|regex_replace:"/\..*/":""}%</span> <button class="toggle_info button_link" type="button" data-href="#comment_data_{$comment.id}"><span class="icon-info-circled" aria-hidden="true"></span><span class="visuallyhidden"> More</span></button></h4>
<h4 id="c{$comment.id}">{$comment.author|truncate:20:"..."|escape:"html"} {$CONST.IN_REPLY_TO} <a href="{$comment.article_link}" target="_blank">{$comment.article_title}</a> {$CONST.ON} {$comment.timestamp|date_format:"%d.%m.%y, %R"} <button class="toggle_info button_link" type="button" data-href="#comment_data_{$comment.id}"><span class="icon-info-circled" aria-hidden="true"></span><span class="visuallyhidden"> More</span></button></h4>
<div id="comment_data_{$comment.id}" class="additional_info">
<dl class="comment_data clearfix">
{foreach from=$types item=type}
<dt>{$type}</dt>
<dd>{$comment.$type|escape:"html"}</dd>
{/foreach}
<dt>name</dt>
<dd>{$comment.author|escape:"html"}</dd>
<dt>email</dt>
<dd>{$comment.email|escape:"html"}</dd>
<dt>url</dt>
<dd>{$comment.url|escape:"html"}</dd>
<dt>comment</dt>
<dd>{$comment.body|escape:"html"}</dd>
</dl>
</div>
</li>
{/if}
{/foreach}
{if $s9ybackend == 1}
<div class="bayesRecyclerTableNavigation">
{else}
</ul>
<ul class="bayesRecyclerTableNavigation plainList clearfix">
{/if}
{if $commentpage > 0}
{if $s9ybackend == 1}
<a class="serendipityIconLink" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage-1}"><img src="{serendipity_getFile file="admin/img/previous.png"}"/>{$CONST.PREVIOUS}</a>
{else}
<li class="prev"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage-1}" title="{$CONST.PREVIOUS}"><span class="icon-left-dir" aria-hidden="true"></span><span class="visuallyhidden"> {$CONST.PREVIOUS}</span></a></li>
{/if}
{/if}
{if ($commentpage+1)*20 < $comments|@count}
{if $s9ybackend == 1}
<a class="serendipityIconLinkRight" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage+1}">{$CONST.NEXT} <img src="{serendipity_getFile file="admin/img/next.png"}"/></a>
{else}
<li class="next"><a class="button_link" href="?serendipity[adminModule]=event_display&amp;serendipity[adminAction]=spamblock_bayes&amp;serendipity[subpage]=1&amp;serendipity[commentpage]={$commentpage+1}" title="{$CONST.NEXT}"><span class="visuallyhidden">{$CONST.NEXT} </span><span class="icon-right-dir" aria-hidden="true"></span></a></li>
{/if}
{/if}
{if $s9ybackend == 1}
</div>
{else}
</ul>
{/if}
</div>
</form>
{if $s9ybackend == 1}
<script src="{$path}details.polyfill.min.js" type="text/javascript"></script>
{/if}
</div>

View file

@ -1,302 +1,11 @@
var httpRequest;
var lastID;
function ham(id) {
if (window.XMLHttpRequest) { // Mozilla, Safari, Opera, IE7
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE6, IE5
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
var copyId = id;
httpRequest.onreadystatechange = function() {
setMessage(copyId);
}
lastID = id;
// Method, url, Async = true / Sync = false
httpRequest.open('POST', learncommentPath, true);
httpRequest.setRequestHeader('content-Type', 'application/x-www-form-urlencoded; charset='+bayesCharset);
if (id.constructor == Array) {
var length = id.length
for (var i=0;i<length;i++) {
setLoadIndicator(id[i]);
}
id = id.join(';');
} else {
setLoadIndicator(id);
}
httpRequest.send('id='+id+'&category=ham'); // Start request
return false;
function spam(commentID) {
$.post(learncommentPath, {id: commentID, category: 'spam'}).done(function() {
location.reload();
});
}
function spam(id) {
if (window.XMLHttpRequest) { // Mozilla, Safari, Opera, IE7
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE6, IE5
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
//ids will be changed later on if an array, but we want the array
var copyId = id;
httpRequest.onreadystatechange = function() {
deleteComment(copyId);
}
lastID = id;
httpRequest.open('POST', learncommentPath, true);
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset='+bayesCharset);
if (id.constructor == Array) {
var length = id.length
for (var i=0;i<length;i++) {
setLoadIndicator(id[i]);
}
id = id.join(';');
} else {
setLoadIndicator(id);
}
httpRequest.send('id='+id+'&category=spam'); // Start request
return false;
}
function setLoadIndicator(id) {
var imgElement = document.createElement("img");
imgElement.src=bayesLoadIndicator;
document.getElementById('comment_'+id).appendChild(imgElement);
}
function setMessage(ids) {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
getAllRatings();
if (! (ids.constructor == Array)) {
//without the use of id, the new ids would be undefined
var id = ids;
ids = new Array();
//push as workaround, Array(id) produces errors
ids.push(id);
}
var length = ids.length;
for (var i=0;i<length;i++) {
var textElement = document.createElement("p");
textElement.classList.add('bayes_done');
textElement.appendChild(document.createTextNode(bayesDone));
//update ratings to make the effect visible
getAllRatings();
//remove load-indicator:
var comment = document.getElementById('comment_'+ids[i]);
comment.removeChild(comment.lastChild);
comment.appendChild(textElement);
}
}
}
function deleteComment(ids) {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
if (! (ids.constructor == Array)) {
//without the use of id, the new ids would be undefined
var id = ids;
ids = new Array();
//push as workaround, Array(id) produces errors
ids.push(id);
}
var length = ids.length;
for (var i=0;i<length;i++) {
var comment = document.getElementById('comment_'+ids[i]);
//by default, there is a hr below the comment and a message above
var divider = nextObject(comment.parentNode);
var message = previousObject(comment.parentNode);
remove(comment)
remove(message);
remove(divider);
getAllRatings();
}
}
}
//help selecting the various commentelements
function nextObject (n) {
do
n = n.nextSibling;
while (n && n.nodeType != 1);
return n;
}
function previousObject (p) {
do
p = p.previousSibling;
while (p && p.nodeType != 1);
return p;
}
function remove(p) {
p.parentNode.removeChild(p);
}
/*Rating-display*/
function getRating(id) {
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari, Opera, IE7
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE6, IE5
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
//define the callback here to make it possible to have a local httpRequest
//which is needed for the multiple requests we are sending
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
var response = httpRequest.responseText.split(';');
var length = response.length;
for (var i=0; i < length; i=i+2) {
updateRating(response[i+1], response[i]);
}
}
}
httpRequest.open('POST', ratingPath, true);
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset='+bayesCharset);
//fetch all ratings of all ids in one step to be faster
if (id.constructor == Array) {
id = id.join(';');
}
httpRequest.send('id='+id); // Start request
}
function updateRating(id, rating) {
if (typeof id == 'undefined') {
return;
}
var ratingElement = document.getElementById(id+'_rating');
if (ratingElement != null) {
ratingElement.innerHTML = rating;
} else {
var textElement = document.createElement('span');
textElement.setAttribute('class', 'spamblockBayesRating');
ratingElement = document.createElement('span');
ratingElement.setAttribute('id', id+'_rating');
ratingElement.appendChild(document.createTextNode(rating));
textElement.appendChild(ratingElement);
var help = document.createElement('img');
help.setAttribute('src', bayesHelpImage);
textElement.setAttribute('title', bayesHelpTitle);
textElement.appendChild(help);
//id_mail only will exist in s9y 1.6
var ratingLocation = document.getElementById(id+'_mail');
if (ratingLocation == null) {
ratingLocation = document.getElementById('comment_'+id);
}
if (ratingLocation != null) {
ratingLocation.appendChild(textElement);
}
}
}
function removeRatings() {
//reduce number of searched elements via expensive getElementByClass
var form = document.getElementById('formMultiDelete');
var ratings = getElementByClass('spamblockBayesRating', form);
length = ratings.length
for (var i=0; i < length; i++) {
ratings[i].parentNode.removeChild(ratings[i]);
}
}
function getAllRatings() {
var form = document.getElementById('formMultiDelete');
//We need them to fetch the comment-ids
var controls = getElementByClass('spamblockBayesControls', form, 'a');
var length = controls.length;
var ids = new Array()
for (var i=0; i < length; i++) {
if (controls[i].getAttribute('id').indexOf('spam') != -1) {
ids.push(controls[i].getAttribute('id').replace('spam', ''));
}
}
length = ids.length;
//giving 10000s of comments to classify() will probably
//produce a timeout - so split it in smaller pieces
var size = 100;
var blocks = Math.ceil(length / size);
for (i=0; i < blocks; i++) {
getRating(ids.slice(i*size, (i*size)+size));
}
}
//Something like this don't exist in Javascript
function getElementByClass(className, node, tag) {
if (node == null) {
node = document;
}
if (tag == null) {
tag = "*";
}
var allHTMLTags = node.getElementsByTagName(tag);
var classes = new Array();
var length = allHTMLTags.length;
for (var i=0; i < length; i++) {
//multiple classes are in the same string, so search carefully
if (allHTMLTags[i].className.indexOf(className) != -1) {
classes.push(allHTMLTags[i]);
}
}
return classes;
}
function placeSpambutton() {
var groupBayes = document.createElement('fieldset');
var legendBayes = document.createElement('legend');
legendBayes.innerHTML = bayesPlugin;
var buttonSpam = document.createElement('input');
buttonSpam.setAttribute('type', 'button');
buttonSpam.setAttribute('name', 'toogle');
buttonSpam.setAttribute('value', bayesSpambutton);
buttonSpam.setAttribute('class', 'serendipityPrettyButton input_button');
buttonSpam.setAttribute('onclick', 'markAllSpam()');
var buttonHam = document.createElement('input');
buttonHam.setAttribute('type', 'button');
buttonHam.setAttribute('name', 'toogle');
buttonHam.setAttribute('value', bayesHambutton);
buttonHam.setAttribute('class', 'serendipityPrettyButton input_button');
buttonHam.setAttribute('onclick', 'markAllHam()');
groupBayes.appendChild(legendBayes);
groupBayes.appendChild(buttonHam);
groupBayes.appendChild(buttonSpam);
document.querySelector('.invert_selection').parentNode.appendChild(groupBayes);
}
function markAllSpam() {
var ids = getChecked();
var length = ids.length;
spam(ids);
}
function markAllHam() {
var ids = getChecked();
var length = ids.length;
ham(ids);
}
function getChecked() {
var form = document.getElementById('formMultiDelete');
var checkboxes = getElementByClass('input_checkbox', form, 'input');
var length = checkboxes.length;
if (length == 0) {
// the classes changed in 2.0 backend, so we use another selector there
checkboxes = form.querySelectorAll('.multidelete');
length = checkboxes.length;
}
var ids = new Array()
for (var i=0; i < length; i++) {
if (checkboxes[i].checked) {
var id = checkboxes[i].name.split('[')[2];
id = id.substr(0, id.length-1);
ids.push(id);
}
}
return ids;
}
addLoadEvent(placeSpambutton);
function ham(commentID) {
$.post(learncommentPath, {id: commentID, category: 'ham'}).done(function() {
location.reload();
});
}

View file

@ -1 +0,0 @@
jQuery(function(a){(function(){var b=this;this.hideDetailChildren=function(c){var d=c instanceof jQuery?c[0].childNodes:c.childNodes,e=d.length;a(c).attr("open",!1);if(a.browser.safari==!0)for(var f=0;f<e;f++)if(d[f].nodeType==3&&d[f].textContent!=""){var g=a("<span />");g.text(d[f].textContent).hide(),a(d[f]).after(g),d[f].textContent="",e++}a.each(d,function(d,e){if(a(e)[0].nodeType==1&&e==a(e).parent().find("> summary:first-of-type")[0])a(e).data("processed")!=!0&&(a(e).css({display:"block",cursor:"pointer"}).data("processed",!0).addClass("detailHidden").bind("click",function(){b.toggleDetailChildren(a(this))}),a(c).prepend(a(e)));else if(a(e)[0].nodeType==3&&!e.isElementContentWhitespace&&!!a.browser.safari==!1){var f=a("<span />");f.text(e.textContent).hide(),a(e).after(f),e.textContent=""}else if(a(c).find("> summary").length==0){var g=a("<summary />").text("Details").css({display:"block",cursor:"pointer"}).data("processed",!0).addClass("detailHidden").bind("click",function(){b.toggleDetailChildren(a(this))});a(c).prepend(g)}a(c).find("> :visible:not(summary:first-child)").hide()})},this.showDetailChildren=function(b){a(b).attr("open",!0),a.each(a(b).find("> *"),function(b,c){a(c).show()})},this.toggleDetailChildren=function(a){a.hasClass("detailHidden")?(a.removeClass("detailHidden"),b.showDetailChildren(a.parents("details")[0])):(a.addClass("detailHidden"),b.hideDetailChildren(a.parents("details")[0]))};var c=function(a){var b=a.createElement("details"),c,d,e;return"open"in b?(d=a.body||function(){var b=a.documentElement;return c=!0,b.insertBefore(a.createElement("body"),b.firstElementChild||b.firstChild)}(),b.innerHTML="<summary>a</summary>b",b.style.display="block",d.appendChild(b),e=b.offsetHeight,b.open=!0,e=e!=b.offsetHeight,d.removeChild(b),c&&d.parentNode.removeChild(d),e):!1}(document);if(c==!1){if(a("details").length!==0){var d=a("<style />").text('summary {-webkit-text-size-adjust: none;} details > summary:first-child:before {content: "\u25bc"; font-size:.9em;padding-right:6px;font-family:"Courier New";} details > summary.detailHidden:first-child:before {content: "\u25ba";font-size:.9em;padding-right:6px;font-family:"Courier New";}');a("head").append(d)}a.each(a("details"),function(a,c){b.hideDetailChildren(c)})}})()})

View file

@ -1,166 +0,0 @@
(function($) {
/*
* Ensures an element's text is cut off at a certain maximum number of lines.
*
* The element must have a nonzero width when empty. (Most commonly a block
* element, such as a <p>, will fit this criterion.) The contained, HTML-free
* text will be truncated to fit the width along with an "end", e.g., '…'.
* Truncation will only occur along whitespace.
*
* Assumptions:
* - The element is empty or contains only a single text node.
*
* Guarantees:
* - The displayed text will never surpass the requested number of lines.
* - If truncation occurs and the end string fits within the width of the
* element, the end string will be displayed.
* - As many words in the element's text will be displayed as possible.
*
* Options:
* end: (default '…') String to append to the end when truncating. May also
* be a DOM node.
* always_end: String or DOM node which must always be appended, whether or
* not we truncate. (This may actually cause truncation which
* would otherwise not occur.)
* lines: (default 1) Number of lines of text to display.
*
* --
* Bodacity JavaScript Utilities
* http://adamhooper.com/bodacity
* Public Domain (no licensing restrictions)
*/
function Excerpt(elem, options) {
this.$elem = $(elem);
this.options = $.extend({
end: '…',
always_end: undefined,
lines: 1
}, options);
this.original_text = this.$elem.text();
if (typeof(this.options.end) != 'string') {
// Assume it's a DOM element or jQuery object
this.$end_node = $(this.options.end);
this.end_string = this.$end_node.text();
} else {
this.end_string = this.options.end;
this.$end_node = $(document.createTextNode(this.end_string));
}
if (this.options.always_end) {
if (typeof(this.options.always_end) != 'string') {
this.$always_end_node = $(this.options.always_end);
this.always_end_string = this.$always_end_node.text();
} else {
this.always_end_string = this.options.always_end;
this.$always_end_node = $(document.createTextNode(this.always_end_string));
}
}
this._attach();
this.refresh();
}
$.extend(Excerpt.prototype, {
_attach: function() {
},
/*
* Resets the element based on its original text, such that it only the
* desired number of lines are shown and there is no overflow.
*/
refresh: function() {
if (!this.$elem[0].firstChild) return; // It's already empty
var wh = this._calculate_desired_width_height();
var w = wh[0];
var h = wh[1];
var s = this.original_text.replace(/\s+/, ' ');
var spaces = []; // Array of indices to space characters
spaces.push(0);
for (var i = 1; i < s.length; i++) {
if (s.charAt(i) == ' ') {
spaces.push(i);
}
}
spaces.push(s.length);
var lbound = 0;
var rbound = spaces.length - 1;
var cur = 0;
var cutoff = 100;
while (lbound < rbound && cutoff) {
cur = Math.floor(lbound + (rbound - lbound) / 2);
if (cur == lbound) cur += 1;
var sub = this._substring(s, spaces[cur]);
if (this._is_string_small_enough(sub, w, h)) {
lbound = cur;
} else {
rbound = cur - 1;
}
cutoff -= 1;
}
this.$elem[0].firstChild.nodeValue = this._substring(s, spaces[lbound], true);
if (s.length != spaces[lbound]) {
this.$elem.append(this.$end_node.clone());
}
if (this.$always_end_node) {
this.$elem.append(this.$always_end_node.clone());
}
},
_substring: function(s, length, exclude_end_string) {
if (length == s.length) return s;
var substr = s.substr(0, length);
if (exclude_end_string) {
return substr;
} else {
return substr + this.end_string + (this.always_end_string || '');
}
},
_is_string_small_enough: function(s, w, h) {
var node = this.$elem[0];
node.firstChild.nodeValue = s;
return node.offsetHeight <= h && node.offsetWidth <= w;
},
/*
* Returns the desired [width, height] in px.
*
* Modifies this.$elem contents as a side-effect.
*/
_calculate_desired_width_height: function() {
var node = this.$elem[0];
var s = '&nbsp;';
for (var i = 0; i < this.options.lines - 1; i++) {
s += "<br />&nbsp;";
}
node.innerHTML = s;
var w = node.offsetWidth;
var h = node.offsetHeight;
node.innerHTML = '&nbsp;'; // anything non-empty
return [w, h];
}
});
$.fn.excerpt = function(options) {
return $(this).each(function() {
new Excerpt(this, options);
});
};
})(jQuery);

View file

@ -1,7 +0,0 @@
/*
HeatColor, by Josh Nathanson
A plugin for jQuery
Complete documentation at http://www.jnathanson.com/blog/client/jquery/heatcolor/index.cfm
*/
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('p.V.10=9(j,k){4 l={M:9(){v p(8)},q:0,t:0,N:0.Z,F:\'E\',y:X};u(k){p.W(l,k)};4 m={K:9(a,c,d){4 e=(a-c)/(d-c);4 f=l.F==\'E\'?0.5*e+1.7*(1-e):e+0.2+5.5*(1-e);4 h=Y;4 i=2*6.A;4 x=f+e*i;x=l.F!=\'E\'?-x:x;4 r=8.w(6.z((6.D(x)+1)*h));4 g=8.w(6.z((6.D(x+6.A/2)+1)*h));4 b=8.w(6.z((6.D(x+6.A)+1)*h));v\'#\'+r+g+b},w:9(a){4 n=6.z(a+l.N*(U-a));4 s=n.T(S);s=s.C==1?\'0\'+s:s;v s},J:9(c){4 d=[];c.I(9(){d.R(j.B(p(8)))});d=d.Q(9(a,b){v a-b});l.t=!l.y?d[d.C-1]:d[0];l.q=!l.y?d[0]:d[d.C-1]}};u(!l.q&&!l.t)m.J(p(8));H u(l.y){4 o=l.q;l.q=l.t;l.t=o}p(8).I(9(){4 a=p(8);4 b=j.B(a);4 c=m.K(b,l.q,l.t);4 d=l.M.B(a);u(d[0].L==1)d.O("P-G",c);H u(d[0].L==3)d.O("G",c)});v(8)}',62,63,'||||var||Math||this|function||||||||||||||||jQuery|minval|||maxval|if|return|process||reverseOrder|floor|PI|apply|length|cos|roygbiv|colorStyle|color|else|each|setMaxAndMin|findcolor|nodeType|elementFunction|lightness|css|background|sort|push|16|toString|256|fn|extend|false|128|75|heatcolor'.split('|'),0,{}))

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

View file

@ -1,6 +0,0 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyUQQGNlUmGwwPWz11ZGndNyrv
xxg/V7ldOlUSw3kNuhhAbNk+DT05soHsEXPKQHudE9P2WrHv8UtgdQBiQkbpzNGY
vFePrT3gvvm/YfMYOkfhphBm/6ZHdg0l5HN+YRQ0+5oaZD7s3tLRROnucAY5qSeb
j0fKR1EfeOIO/r4tTQIDAQAB
-----END PUBLIC KEY-----

View file

@ -1,138 +0,0 @@
#bayesNav {
margin: 0;
padding: 0;
}
#bayesNav a {
display: block;
}
#bayesNav li:last-child a {
padding-right: 0.3em;
}
#bayesNav ul {
list-style-type: none;
margin: 0;
padding: 0;
border: 1px solid;
}
#bayesNav li {
display: inline-block;
margin: 0;
padding: 0.5em;
border-right: 1px solid;
}
#bayesNav h3 {
display: inline;
margin: 0;
padding: 0;
font-size: 1em;
}
#bayesContent {
width: 100%;
}
#bayesControls * {
margin-left: 1em;
margin-right: 1em;
margin-bottom: 1em;
}
#bayesLearnTable {
padding-top: 1em;
margin-bottom: 0.8em;
}
#bayesLearnTable td {
vertical-align: top;
}
#bayesControls {
float: right;
border: 1px solid;
border-top: 0;
-moz-border-radius-bottomleft: 5px;
-webkit-border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-webkit-border-bottom-right-radius: 5px;
max-width: 21%;
padding-top: 1em;
}
#bayesControls form {
margin: 0;
}
#bayesDatabase {
padding-top: 2em;
margin-left: 2em;
}
#bayesSavedValues {
padding-top: 2em;
}
#bayesDatabaseTable th {
cursor: pointer;
text-decoration: underline;
}
th {
border: 1px solid;
}
caption {
font-weight: bold;
}
#bayesSavedValuesTable td {
text-align: center;
}
#bayesRecyclerTable {
padding-top: 2em;
width: 78%;
table-layout: fixed;
}
#bayesRecyclerTable td {
padding-top: 1em;
overflow: auto;
}
#bayesRecyclerTable th.select {
text-align: center;
width: 2em;
}
#bayesRecyclerTable td.select {
text-align: center;
}
.ratingBox {
border-bottom: 1px solid grey;
}
.commentPart {
margin-left: 5em;
}
.rating {
margin-left: 5em;
font-weight: bold;
}
.commentType {
font-weight: bold;
}
.finalRating {
padding-left: 3.3em;
font-weight: bold;
font-size: 1.5em;
border-bottom: 1px solid grey;
}
#bayesAnalysisList li {
margin: 1em;
}
label {
cursor: pointer;
}
.serendipityIconLinkRight {
float: right;
}
#bayesControls label {
display: block;
}
input[type="submit"] {
cursor: pointer;
}
.bayesTrojaButtons {
display: inline;
}
fieldset {
display: inline-block;
}
#trojaImport {
margin-left: 1.1em;
}

View file

@ -1,72 +0,0 @@
function sortwithcolor(column) {
$("#bayesDatabaseTable > tbody > tr").heatcolor(
function() { return $("td:nth-child(" + column + ")", this).text(); },
{ colorStyle: 'greentored' }
);
};
$(document).ready(function() {
var checked = false;
$("#bayesDeleteDB").submit(function(event) {
if (! checked) {
event.preventDefault()
var answer = confirm("Completely delete the database?")
if (answer){
window.location.href = $("#bayesDeleteDB").attr('action');
} else {
}
}
});
});
$("th").click(function() {
$(this).siblings().css("background-color","#cccccc").end().css("background-color","#dd0000");
sortwithcolor( $(this).parent().children().index( this ) + 1 );
});
function shortenAll(textclass, lines) {
$.each( $('.'+textclass), function() {
shorten($(this), lines);
});
}
function shorten($element, lines) {
var o = $element.text();
$r = $('<a href="#" style="padding-left: 5px;">... show</a>');
$element.excerpt({ lines: lines, end: $r});
$element.find('a').click(function(e){
e.preventDefault();
var $cell = $(this).parent();
var $link = $('<a></a>')
.attr('href', '#')
.text('shorten')
.css('padding-left', '5px');
$link.click( function(e) {
e.preventDefault();
$link.remove();
shorten($cell);
});
$cell.text(o);
$cell.append($link);
});
}
function colorize() {
$ratings = $(".ratingBox").children(".rating");
$.each($ratings, function() {
var rating = parseInt($(this).text().replace("%",""));
if (rating > 70) {
$(this).parent().css('background', 'rgba(249, 199, 199, 0.5)');
} else if ( rating > 10) {
$(this).parent().css('background', 'rgba(248, 246, 137, 0.5)');
} else if (rating >= 0){
$(this).parent().css('background', 'rgba(202, 248, 199, 0.5)');
} else {
/*detect those without a rating*/
$(this).parent().css('background', 'rgba(165, 165, 165, 0.5)');
}
});
}