bayes 1.0: cleanup and code update version
This commit is contained in:
parent
8c917ab315
commit
f669be9b2b
|
@ -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).
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
406
serendipity_event_spamblock_bayes/b8/b8.php
Normal file
406
serendipity_event_spamblock_bayes/b8/b8.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
176
serendipity_event_spamblock_bayes/b8/degenerator/standard.php
Normal file
176
serendipity_event_spamblock_bayes/b8/degenerator/standard.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
267
serendipity_event_spamblock_bayes/b8/lexer/standard.php
Normal file
267
serendipity_event_spamblock_bayes/b8/lexer/standard.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
105
serendipity_event_spamblock_bayes/b8/storage/dba.php
Normal file
105
serendipity_event_spamblock_bayes/b8/storage/dba.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
110
serendipity_event_spamblock_bayes/b8/storage/mysql.php
Normal file
110
serendipity_event_spamblock_bayes/b8/storage/mysql.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
108
serendipity_event_spamblock_bayes/b8/storage/sqlite.php
Normal file
108
serendipity_event_spamblock_bayes/b8/storage/sqlite.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
316
serendipity_event_spamblock_bayes/b8/storage/storage_base.php
Normal file
316
serendipity_event_spamblock_bayes/b8/storage/storage_base.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=4&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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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&serendipity[adminAction]=spamblock_bayes&serendipity[subpage]=1&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>
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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)})}})()})
|
|
@ -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 = ' ';
|
||||
for (var i = 0; i < this.options.lines - 1; i++) {
|
||||
s += "<br /> ";
|
||||
}
|
||||
|
||||
node.innerHTML = s;
|
||||
|
||||
var w = node.offsetWidth;
|
||||
var h = node.offsetHeight;
|
||||
|
||||
node.innerHTML = ' '; // anything non-empty
|
||||
|
||||
return [w, h];
|
||||
}
|
||||
});
|
||||
|
||||
$.fn.excerpt = function(options) {
|
||||
return $(this).each(function() {
|
||||
new Excerpt(this, options);
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -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 |
|
@ -1,6 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyUQQGNlUmGwwPWz11ZGndNyrv
|
||||
xxg/V7ldOlUSw3kNuhhAbNk+DT05soHsEXPKQHudE9P2WrHv8UtgdQBiQkbpzNGY
|
||||
vFePrT3gvvm/YfMYOkfhphBm/6ZHdg0l5HN+YRQ0+5oaZD7s3tLRROnucAY5qSeb
|
||||
j0fKR1EfeOIO/r4tTQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
|
@ -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;
|
||||
}
|
|
@ -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)');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue