null);
/**
* Type of question asked in the Captcha. This is either 'math' or 'custom'
* @var string
*/
var $captchaQuestionType = null;
/**
* Whether to use RegExp matching for the hidden Captcha
* @var boolean
*/
var $useRegularExpressions = false;
/**
* Constructor. Initialize class variables from configuration
* @return void
*/
function __construct($instance) {
$this->instance = $instance;
$this->answerRetrievalMethod = $this->get_config('answer_retrieval_method', 'default');
$this->captchaQuestionType = $this->get_config('question_type', 'math');
$this->useHoneyPot = $this->get_config('do_honeypot', true);
$this->hiddenCaptchaHandle = $this->get_config('do_hiddencaptcha', PLUGIN_EVENT_SPAMBLOCK_SWTCH_MODERATE);
$this->useRegularExpressions = $this->get_config('use_regexp', false);
}
/**
* Declare Serendipity backend properties.
*
* @param serendipity_property_bag $propbag
*/
function introspect(&$propbag)
{
global $serendipity;
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_TITLE);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_DESC);
$propbag->add('stackable', false);
$propbag->add('author', 'Grischa Brockhaus, Janek Bevendorff');
$propbag->add('requirements', array(
'serendipity' => '0.8',
'smarty' => '2.6.7',
'php' => '5.2.0'
));
$propbag->add('version', PLUGIN_SPAMBLOCK_BEE_VERSION); // setup via version.inc.php
$propbag->add('event_hooks', array(
'frontend_comment' => true,
'frontend_saveComment' => true,
'frontend_footer' => true,
'css' => true,
'external_plugin' => true
));
$propbag->add('groups', array('ANTISPAM'));
$propbag->add('legal', array(
'services' => array(
),
'frontend' => array(
'Anti-Spam measurements by this plugin can transfer user data and metadata (??? plugin description missing ???)',
'All user data and metadata (IP address, comment fields) can be logged to database or file'
),
'backend' => array(
),
'cookies' => array(
),
'stores_user_input' => true,
'stores_ip' => true,
'uses_ip' => true,
'transmits_user_input' => true
));
$configuration = array('header_desc','do_honeypot', 'do_hiddencaptcha' );
// Only do that, if spamblock is not installed
if (!class_exists('serendipity_event_spamblock')) {
$configuration = array_merge($configuration, array('entrytitle', 'samebody', 'required_fields'));
}
$configuration = array_merge($configuration, array('spamlogtype', 'spamlogfile', 'plugin_path'));
$configuration = array_merge($configuration, array(
'advanced_cc_desc', 'answer_retrieval_method', 'question_type',
'questions', 'answers', 'use_regexp'
));
$propbag->add('configuration', $configuration );
$propbag->add('config_groups', array(
PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SECTION_LOGGING => array(
'spamlogtype', 'spamlogfile', 'plugin_path'
),
PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SECTION_ADVANCED => array(
'advanced_cc_desc', 'answer_retrieval_method', 'question_type',
'questions', 'answers', 'use_regexp'
)
)
);
}
/**
* Set plug-in title.
*
* @param string $title
*/
function generate_content(&$title) {
$title = PLUGIN_EVENT_SPAMBLOCK_BEE_TITLE;
}
/**
* Generate backend configuration fields
*
* @param string $name field name
* @param serendipity_property_bag $propbag properties
* @return bool
*/
function introspect_config_item($name, &$propbag)
{
global $serendipity;
$rejectType = array(
PLUGIN_EVENT_SPAMBLOCK_SWTCH_OFF => PLUGIN_EVENT_SPAMBLOCK_BEE_RESULT_OFF,
PLUGIN_EVENT_SPAMBLOCK_SWTCH_MODERATE => PLUGIN_EVENT_SPAMBLOCK_BEE_RESULT_MODERATE,
PLUGIN_EVENT_SPAMBLOCK_SWTCH_REJECT => PLUGIN_EVENT_SPAMBLOCK_BEE_RESULT_REJECT,
);
$retrievalMethod = array(
'default' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_RM_DEFAULT,
'json' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_RM_JSON,
'smarty' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_RM_SMARTY,
'smarty_enc' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_RM_SMARTY_ENC
);
$questionType = array(
'math' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QT_MATH,
'custom' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QT_CUSTOM
);
switch($name) {
case 'header_desc':
$propbag->add('type', 'content');
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_BEE_EXTRA_DESC .
'' );
break;
case 'do_honeypot':
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_HONEYPOT);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_HONEYPOT_DESC);
$propbag->add('default', true);
break;
case 'do_hiddencaptcha':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_HCAPTCHA);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_HCAPTCHA_DESC);
$propbag->add('select_values', $rejectType);
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_SWTCH_MODERATE);
break;
case 'required_fields':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_REQUIRED_FIELDS);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_REQUIRED_FIELDS_DESC);
$propbag->add('default', '');
break;
case 'entrytitle':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_FILTER_TITLE);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_FILTER_TITLE_DESC);
$propbag->add('select_values', $rejectType);
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_SWTCH_REJECT);
break;
case 'samebody':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_FILTER_SAMEBODY);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_FILTER_SAMEBODY_DESC);
$propbag->add('select_values', $rejectType);
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_SWTCH_REJECT);
break;
case 'spamlogtype':
$logtypevalues = array (
'none' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGTYPE_NONE,
'file' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGTYPE_FILE,
'db' => PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGTYPE_DATABASE,
);
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGTYPE);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGTYPE_DESC);
$propbag->add('select_values', $logtypevalues);
$propbag->add('default', 'none');
break;
case 'spamlogfile':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGFILE);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_SPAM_LOGFILE_DESC);
$propbag->add('default', $serendipity['serendipityPath'] . 'spamblock.log');
break;
case 'plugin_path':
$propbag->add('type', 'string');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_PATH);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_PATH_DESC);
$propbag->add('default', $serendipity['serendipityHTTPPath'] . 'plugins/serendipity_event_spamblock_bee/');
break;
case 'advanced_cc_desc':
$propbag->add('type', 'content');
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_DESC);
break;
case 'answer_retrieval_method':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_ANSWER_RETRIEVAL);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_ANSWER_RETRIEVAL_DESC);
$propbag->add('select_values', $retrievalMethod);
$propbag->add('default', 'default');
break;
case 'question_type':
$propbag->add('type', 'select');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QUESTION_TYPE);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QUESTION_TYPE_DESC);
$propbag->add('select_values', $questionType);
$propbag->add('default', 'math');
break;
case 'questions':
$propbag->add('type', 'text');
$propbag->add('rows', 8);
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QUESTIONS);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_QUESTIONS_DESC);
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_DEFAULT_QUESTIONS);
break;
case 'answers':
$propbag->add('type', 'text');
$propbag->add('rows', 8);
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_ANSWERS);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_ANSWERS_DESC);
$propbag->add('default', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_DEFAULT_ANSWERS);
break;
case 'use_regexp':
$propbag->add('type', 'boolean');
$propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_USE_REGEXP);
$propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BEE_CONFIG_ADV_USE_REGEXP_DESC);
$propbag->add('default', false);
break;
default:
return false;
}
return true;
}
/**
* Hook for Serendipity events, initialize plug-in features
*
* @param string $event
* @param serendipity_property_bag $bag
* @param array $eventData
* @param array $addData
* @return bool
*/
function event_hook($event, &$bag, &$eventData, $addData = null) {
global $serendipity;
$hooks = &$bag->get('event_hooks');
if (isset($hooks[$event])) {
switch($event) {
case 'external_plugin':
switch($eventData) {
case 'spamblockbee.png':
header('Content-Type: image/png');
echo file_get_contents(dirname(__FILE__). '/spamblockbee.png');
break;
case 'spamblockbeecaptcha':
echo $this->produceCaptchaAnswerJson();
break;
}
break;
case 'frontend_saveComment':
// Check only, if no one else denied it before
if (!is_array ( $eventData ) || serendipity_db_bool ( $eventData ['allow_comments'] )) {
return $this->checkComment($eventData, $addData);
}
return true;
break;
case 'frontend_comment':
$this->printCommentEditExtras($eventData, $addData);
break;
case 'frontend_footer':
// Comment header code only if in single article mode or contactform
// If contact form is installed, display on any page not being the article list
// else display in single article only.
$contactFormInstalled = class_exists('serendipity_event_contactform');
if (($contactFormInstalled && empty($eventData['GET']['page']))
|| (!$contactFormInstalled && !empty($eventData['GET']['id'])))
{
$this->printJsExtras();
}
break;
case 'css':
$this->printCss($eventData, $addData);
break;
default:
return false;
break;
}
return true;
} else {
return false;
}
}
/**
* Check if Honey Pot or Captcha have been filled correctly (or if any
* other indications for spam can be found).
*
* @param array $eventData
* @param array $addData
* @return bool
*/
function checkComment(&$eventData, &$addData) {
global $serendipity;
if ("NORMAL" == $addData['type']) { // only supported for normal comments
// Check for Honey Pot:
$phone = $serendipity['POST']['phone'] ?? '';
if ($this->useHoneyPot && (!empty($phone) || $phone == '0') ) {
if (mb_strlen($phone) > 40) {
$phone = mb_substr($phone, 0, 40) . '..';
}
$this->spamlog($eventData['id'], 'REJECTED', "BEE Honeypot [" . $phone . "]", $addData);
$eventData = array('allow_comments' => false);
return false;
}
// Check hidden Captcha
if (PLUGIN_EVENT_SPAMBLOCK_SWTCH_OFF != $this->hiddenCaptchaHandle) {
$answer = trim(strtolower($serendipity['POST']['beecaptcha'] ?? ''));
$correctAnswer = $this->getCaptchaAnswer();
$correctAnswer['answer'] = strtolower($correctAnswer['answer']);
$isCorrect = false;
// If provided answer is longer than 1000 characters and RegExp matching is on,
// reject comment for security reasons (minimize risk of ReDoS)
if ($this->useRegularExpressions && mb_strlen($answer) > 1000) {
$this->processComment($this->hiddenCaptchaHandle, $eventData, $addData, PLUGIN_EVENT_SPAMBLOCK_BEE_ERROR_HCAPTCHA, "BEE HiddenCaptcha [ Captcha input too long ]");
return false;
}
if ($this->captchaQuestionType == 'custom' && $this->useRegularExpressions) {
// Sanitize regular expression and remove answer part
$pattern = preg_replace('/^\s*\/(.*)\/\s*[imsxeADSUXJu]*\s*$/s', '$1', $correctAnswer['pattern']);
// Try to match pattern with given answer
$match = @preg_match('/' . $pattern . '/si', $answer);
// If pattern contains errors, fall back to basic string comparison
if ($match === false) {
$this->useRegularExpressions = false;
} else {
$isCorrect = ($match === 1);
}
}
if ($this->captchaQuestionType != 'custom' || !$this->useRegularExpressions) {
$isCorrect = ($answer == $correctAnswer['answer']);
}
// Also allow numbers as words
if (!$isCorrect && $this->captchaQuestionType == 'math') {
$number = $this->generateNumberString($correctAnswer['answer']);
$isCorrect = ($answer == $number && $number != 'ERROR');
}
if (!$isCorrect) {
if (mb_strlen($answer) > 40) {
$answer = mb_substr($answer, 0, 40) . '..';
}
$this->processComment($this->hiddenCaptchaHandle, $eventData, $addData, PLUGIN_EVENT_SPAMBLOCK_BEE_ERROR_HCAPTCHA, "BEE HiddenCaptcha [ $correctAnswer[answer] != $answer ]");
return $isCorrect;
}
}
// AntiSpam check, the general spamblock supports, too: Only if spamblock is not installed.
if (!class_exists('serendipity_event_spamblock')) {
// Check for required fields. Don't log but tell the user about the fields.
$required_fields = $this->get_config('required_fields', '');
if (!empty($required_fields)) {
$required_field_list = explode(',', $required_fields);
foreach($required_field_list as $required_field) {
$required_field = trim($required_field);
if (empty($addData[$required_field])) {
$this->reject($eventData, $addData, sprintf(PLUGIN_EVENT_SPAMBLOCK_BEE_REASON_REQUIRED_FIELD, $required_field));
return false;
}
}
}
}
}
// AntiSpam check, the general spamblock supports, too: Only if spamblock is not installed.
if (!class_exists('serendipity_event_spamblock')) {
// Check if entry title is the same as comment body
$spamHandle = $this->get_config('entrytitle', PLUGIN_EVENT_SPAMBLOCK_SWTCH_REJECT);
if (PLUGIN_EVENT_SPAMBLOCK_SWTCH_OFF != $spamHandle) {
// Remove the blog name from the comment which might be in