You are here

class BotchaRecipebookAbstract in BOTCHA Spam Prevention 7.2

Same name and namespace in other branches
  1. 6.2 controller/botcha_recipebook.controller.inc \BotchaRecipebookAbstract

@file Controller layer of the BotchaRecipebook objects.

Hierarchy

Expanded class hierarchy of BotchaRecipebookAbstract

File

controller/botcha_recipebook.controller.inc, line 7
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/config/people/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 = 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 = 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_value($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_value($_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_value($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_value($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_value($_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_value($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 handle Add last recipe name.

        //'last_recipe_name' => $recipe->name,

        // !!~ @todo Recipebook handle 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_by_args($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;
    $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) {
    $form_state['no_cache'] = TRUE;
    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_value($_POST), 1) . '</pre>' : '') . (BOTCHA_LOGLEVEL >= 5 ? '<br /><br />' . 'GET=<pre>' . print_r(_botcha_filter_value($_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);
    $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);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
BotchaRecipebookAbstract::$description public property Description of the recipe book.
BotchaRecipebookAbstract::$forms protected property List of forms.
BotchaRecipebookAbstract::$id public property Identifier of the recipe book.
BotchaRecipebookAbstract::$recipes protected property List of recipes.
BotchaRecipebookAbstract::$title public property A title of the book.
BotchaRecipebookAbstract::apply function
BotchaRecipebookAbstract::applyForBuildId protected function
BotchaRecipebookAbstract::delete public function
BotchaRecipebookAbstract::getCsss protected function
BotchaRecipebookAbstract::getDescription function
BotchaRecipebookAbstract::getForms function @todo BotchaRecipebook getForms Description.
BotchaRecipebookAbstract::getJss protected function
BotchaRecipebookAbstract::getRecipebook public static function
BotchaRecipebookAbstract::getRecipes function @todo BotchaRecipebook getRecipes Description.
BotchaRecipebookAbstract::getRecipesByStatus function Get the list of recipes by status of spam checking.
BotchaRecipebookAbstract::getRecipeSecret protected function
BotchaRecipebookAbstract::getTitle function
BotchaRecipebookAbstract::handle function Handle form depending on the result of spam check.
BotchaRecipebookAbstract::isApplicable public function
BotchaRecipebookAbstract::isSpam function 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.
BotchaRecipebookAbstract::save public function 2
BotchaRecipebookAbstract::setDescription function
BotchaRecipebookAbstract::setForm function
BotchaRecipebookAbstract::setRecipe function
BotchaRecipebookAbstract::setTitle function
BotchaRecipebookAbstract::unsetForm function
BotchaRecipebookAbstract::unsetRecipe function
BotchaRecipebookAbstract::__construct protected function 1