class BotchaRecipebookAbstract in BOTCHA Spam Prevention 6.2
Same name and namespace in other branches
- 7.2 controller/botcha_recipebook.controller.inc \BotchaRecipebookAbstract
@file Controller layer of the BotchaRecipebook objects.
Hierarchy
- class \BotchaRecipebookAbstract
Expanded class hierarchy of BotchaRecipebookAbstract
File
- controller/
botcha_recipebook.controller.inc, line 8 - Controller layer of the BotchaRecipebook objects.
View source
class BotchaRecipebookAbstract {
/**
* Identifier of the recipe book.
*/
public $id;
/**
* A title of the book.
*/
public $title;
/**
* Description of the recipe book.
*/
public $description;
/**
* List of recipes.
* @var BotchaRecipe
*/
protected $recipes = array();
/**
* List of forms.
* @var BotchaForm
*/
protected $forms = array();
public static function getRecipebook($id, $create = TRUE) {
$none = TRUE;
if ($id != 'none') {
$rb = BotchaRecipebookModel::getRecipebook($id);
if ($rb || $create) {
$none = FALSE;
}
}
if ($none) {
$recipebook = new BotchaRecipebookNone($id);
}
else {
$recipebook = new BotchaRecipebook($id);
if ($rb) {
$recipebook
->setTitle($rb->title)
->setDescription($rb->description);
}
}
return $recipebook;
}
protected function __construct($id) {
$this->id = $id;
}
public function isApplicable($form, $form_state) {
$form_id = $form['form_id']['#value'];
$isApplicable = FALSE;
if (!user_access('skip BOTCHA')) {
$isApplicable = TRUE;
}
switch ($form_id) {
case 'user_register':
// Only change the registration form. There is also 'user_register' form
// at /admin/user/user/create path, but we leave it alone.
if (FALSE === strpos($form['#action'], 'user/register')) {
if (!variable_get('botcha_allow_on_admin_pages', FALSE)) {
$isApplicable = FALSE;
}
}
break;
}
return $isApplicable;
}
/* @todo Remove it.
protected function addAdminLinks(&$form) {
$form_id = $form['form_id']['#value'];
if (variable_get('botcha_administration_mode', FALSE)
&& user_access('administer BOTCHA settings')
&& (arg(0) != 'admin'
|| variable_get('botcha_allow_on_admin_pages', FALSE)
|| ($form_id == 'user_register'))) {
// Add BOTCHA administration tools.
$botcha_element = $this->createAdminLinksFieldset($form_id);
// Get placement in form and insert in form.
// @todo BotchaRecipebook isApplicable Make away with a dependency from botcha.inc.
$botcha_placement = _botcha_get_botcha_placement($form_id, $form);
_botcha_insert_botcha_element($form, $botcha_placement, $botcha_element);
}
}
protected function createAdminLinksFieldset($form_id) {
// For administrators: show BOTCHA info and offer link to configure it.
return array(
'#type' => 'fieldset',
'#title' => t('BOTCHA'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
// @todo Abstract it.
//'#attributes' => array('class' => array('botcha-admin-links')),
'#attributes' => array('class' => 'botcha-admin-links'),
);
}
*
*/
public function save() {
// Clean session to fetch new values.
Botcha::clean();
}
public function delete() {
Botcha::unsetRecipebook($this);
// Delete recipe book from DB.
BotchaRecipebookModel::delete($this);
// Clean session to fetch new values.
Botcha::clean();
}
function setTitle($title) {
$this->title = $title;
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
function getTitle() {
return $this->title;
}
function setDescription($description) {
$this->description = $description;
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
function getDescription() {
return $this->description;
}
function setRecipe($recipe_id) {
$this->recipes[$recipe_id] = $recipe_id;
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
function unsetRecipe($recipe_id) {
unset($this->recipes[$recipe_id]);
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
/**
* @todo BotchaRecipebook getRecipes Description.
* @return array
*/
function getRecipes() {
if (!isset($this->recipes)) {
$rs = array_keys(BotchaModel::getRecipebooksRecipes(array(
'mode' => 'recipe',
'recipebooks' => $this->id,
)));
foreach ($rs as $recipe_id) {
$this
->setRecipe($recipe_id);
}
}
$recipes = array();
foreach ($this->recipes as $recipe_id) {
$recipes[$recipe_id] = Botcha::getRecipe($recipe_id, FALSE);
}
return $recipes;
}
function setForm($form_id) {
$this->forms[$form_id] = $form_id;
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
function unsetForm($form_id) {
unset($this->forms[$form_id]);
// Save changed state.
Botcha::setRecipebook($this);
return $this;
}
/**
* @todo BotchaRecipebook getForms Description.
* @return BotchaForm
*/
function getForms() {
if (!isset($this->forms)) {
$fs = array_keys(BotchaModel::getRecipebooksForms(array(
'mode' => 'form',
'recipebooks' => $this->id,
)));
foreach ($fs as $form_id) {
$this
->setForm($form_id);
}
}
$forms = array();
foreach ($this->forms as $form_id) {
$forms[$form_id] = Botcha::getForm($form_id, FALSE);
}
return $forms;
}
/**
* Get the list of recipes by status of spam checking.
*
* @param string $status
* @return array
*/
function getRecipesByStatus($status) {
$recipes_list = array();
foreach ($this
->getRecipes() as $recipe_id => $recipe) {
if ($recipe
->getStatus() == $status) {
$recipes_list[$recipe_id] = $recipe;
}
}
return $recipes_list;
}
/**
* Handle form depending on the result of spam check.
*
* @param string $result
* This parameter is string and not boolean to have a chance to easily implement
* new results of spam check (such as 'postponed', 'suspected' or other).
* @param array $form
* @param array $form_state
*/
function handle($result, $form, $form_state) {
$recipes_success = $this
->getRecipesByStatus('success');
$recipes_success_count = count($recipes_success);
$recipes_spam = $this
->getRecipesByStatus('spam');
$recipes_spam_count = count($recipes_spam);
// !!~ @todo Recipebook handle Reduce code duplication.
switch ($result) {
case 'success':
variable_set('botcha_form_passed_counter', $recipes_success_count);
// Show good submissions in log.
if (BOTCHA_LOGLEVEL >= 3) {
watchdog(BOTCHA_LOG, '%form_id post approved by BOTCHA.!more', array(
'%form_id' => $form['form_id']['#value'],
'!more' => '' . (BOTCHA_LOGLEVEL >= 3 ? ' Checked ' . count($this->recipes) . ' botchas (' . join(', ', array_keys($this->recipes)) . ').' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'POST=<pre>' . print_r(_botcha_filter_form_values_log($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_form_values_log($_GET), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'SERVER=<pre>' . print_r($_SERVER, 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . ' form=<pre>' . print_r(_botcha_filter_form_log($form), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . ' values=<pre>' . print_r(_botcha_filter_form_values_log($form_state['values']), 1) . '</pre>' : ''),
), WATCHDOG_INFO);
}
$rules_event_name = 'botcha_form_approved';
break;
case 'spam':
default:
variable_set('botcha_form_blocked_counter', $recipes_spam_count);
// Prepare a list of failed recipes.
foreach ($recipes_spam as $recipe_spam) {
$recipe_spam_ids[$recipe_spam->id] = $recipe_spam->id;
}
// Just using the first failed recipe to reject form submission.
$recipe_spam = reset($recipes_spam);
form_set_error($recipe_spam->error_field, $recipe_spam->error_text);
// Show blocked submissions in log.
if (BOTCHA_LOGLEVEL >= 1) {
watchdog(BOTCHA_LOG, '%form_id post blocked by BOTCHA: submission looks like from a spambot.!more', array(
'%form_id' => $form['form_id']['#value'],
'!more' => '' . (BOTCHA_LOGLEVEL >= 2 ? '<br /><br />' . ' Failed ' . $recipes_spam_count . ' of ' . count($this->recipes) . ' recipes [' . implode(', ', $recipe_spam_ids) . '] from "' . $this->id . '" recipe book.' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'POST=<pre>' . print_r(_botcha_filter_form_values_log($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_form_values_log($_GET), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'SERVER=<pre>' . print_r($_SERVER, 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . ' values=<pre>' . print_r(_botcha_filter_form_values_log($form_state['values']), 1) . '</pre>' : ''),
), WATCHDOG_WARNING);
}
$rules_event_name = 'botcha_form_rejected';
break;
}
// Invoke rules event.
if (module_exists('rules')) {
$arguments = array(
// 'form' => &$form,
// 'form_state' => &$form_state,
'form_id' => $form['form_id']['#value'],
'total_recipes' => count($this->recipes),
'passed_recipes' => $recipes_success_count,
'passed_recipes_names' => join(', ', array_keys($recipes_success)),
// !!~ @todo Add last recipe name.
//'last_recipe_name' => $recipe->name,
// !!~ @todo Add a reason of fail to rules event invokation.
//'fail' => $fail,
'fail' => 'FAIL',
'failed_field' => 'mail',
);
// !!? Do we need per recipe rules event invoking?
rules_invoke_event($rules_event_name, $arguments);
}
// Clean $_SESSION.
Botcha::clean();
}
protected function getRecipeSecret($value) {
return md5($value . BOTCHA_SECRET);
}
/**
* Spam check.
* Currently the logic is as follows: if we could find a recipe that failed
* spam check - then we consider this form submission as spam and decline it.
*
* @param array $form
* @param array $form_state
* @return boolean
*/
function isSpam($form, $form_state) {
$isSpam = FALSE;
// We are going to store changes of the recipes states.
$recipes = $this
->getRecipes();
foreach ($recipes as $recipe) {
$recipe
->setStatus($recipe
->isSpam($form, $form_state) ? 'spam' : 'success');
// Do per recipe handling right here. Global handling will be done later.
$recipe
->handle($recipe
->getStatus(), $form, $form_state);
// One is enough to block the form.
$isSpam = $isSpam || $recipe
->getStatus() == 'spam';
}
return $isSpam;
}
protected function getCsss() {
$csss = array();
foreach ($this
->getRecipes() as $recipe) {
if ($css = $recipe
->getCss()) {
$csss[] = $css;
}
}
return $csss;
}
protected function getJss() {
$jss = array();
foreach ($this
->getRecipes() as $recipe) {
if ($recipe instanceof BotchaRecipeUsingJsAbstract) {
$jss[] = $recipe
->getJsValue();
}
}
return $jss;
}
function apply(&$form, &$form_state) {
// @todo Abstract it.
//$form_state['no_cache'] = TRUE;
$form += array(
'#input' => TRUE,
);
// '#input'=1 hacks FAPI to call #process handler on the form
$form['#process'][] = 'botcha_fprocess';
if (!empty($_POST['form_build_id'])) {
$build_id = $_POST['form_build_id'];
$method = 'build_id_submit';
}
else {
$build_id = $form['#build_id'];
$method = 'build_id';
}
$this
->applyForBuildId($form, $form_state, $method, $build_id);
// User_login forms open session in validate hooks instead of submit
// we should be the first to validate - add our hook to the beginning
if (is_array($form['#validate'])) {
// Workaround since array_unshift'ing by reference was deprecated.
// @see http://www.php.net/manual/ru/function.array-unshift.php#40270
array_unshift($form['#validate'], '');
$form['#validate'][0] = '_botcha_form_validate';
}
else {
$form['#validate'] = array(
'_botcha_form_validate',
);
}
$form_state['#botcha'] = $this->id;
// Logging.
$csss = $this
->getCsss();
$jss = $this
->getJss();
if (BOTCHA_LOGLEVEL >= 4) {
watchdog(BOTCHA_LOG, '%form_id form prepared by BOTCHA: added recipes - !botchas!more', array(
'%form_id' => $form['form_id']['#value'],
'!botchas' => join(', ', $this->recipes),
'!more' => '' . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'POST=<pre>' . print_r(_botcha_filter_form_values_log($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_form_values_log($_GET), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'SERVER=<pre>' . print_r($_SERVER, 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'form=<pre>' . print_r(_botcha_filter_form_log($form), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 && count($jss) ? '<br /><br />' . 'JS=<pre>' . join("\n", $jss) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 && count($csss) ? '<br /><br />' . 'CSS=<pre>' . join("\n", $csss) . '</pre>' : ''),
), WATCHDOG_NOTICE);
}
}
protected function applyForBuildId(&$form, &$form_state, $method, $build_id) {
$secret = $this
->getRecipeSecret($build_id);
// We are going to store any changes of the recipes states.
$recipes = $this
->getRecipes();
foreach ($recipes as $recipe) {
// Set necessary parameters.
$recipe
->setSecret($secret)
->setMethod($method);
// Apply recipe to the concrete form.
$recipe
->applyRecipe($form, $form_state);
}
}
}