You are here

abstract class BotchaRecipe in BOTCHA Spam Prevention 6.2

Same name and namespace in other branches
  1. 6.3 controller/recipe/botcha.recipe.controller.inc \BotchaRecipe
  2. 7.2 controller/botcha_recipe.controller.inc \BotchaRecipe
  3. 7.3 controller/recipe/botcha.recipe.controller.inc \BotchaRecipe

Abstract class to describe recipe data structure.

Hierarchy

Expanded class hierarchy of BotchaRecipe

File

controller/botcha_recipe.controller.inc, line 11
Controller layer of the BotchaRecipe objects.

View source
abstract class BotchaRecipe {

  /**
   * Identifier of recipe.
   */
  public $id;

  /**
   * Brief description of the recipe.
   * It should contain explanation of how bots would fail with it
   * and what the recipe exactly does.
   */
  protected $description;

  /**
   * Options that received as parameters turned into settings
   * by merging with default values.
   */
  protected $settings = array();

  /**
   * Secret.
   */
  protected $secret;

  /**
   * Method of recipe genration.
   */
  protected $method;

  /**
   * CSS to add to the page.
   */
  protected $css;

  /**
   * Javascript to add to the page.
   */
  protected $js;

  /**
   * @todo Do we really need it? Probably the best way is to provide mail field always - it hides our fields.
   * Name of the field in the form to use in error messages
   * (to mask botcha fields).
   */
  public $error_field;

  /**
   * Text to give users if botcha recipe blocks submission.
   * It should give some help to real human users in cases
   * of disabled Javascript or CSS.
   */
  public $error_text;
  protected $recipebooks = array();

  /**
   * Status of the spam check.
   */
  public $status = 'none';
  public static function getRecipe($id) {
    $r = BotchaRecipeModel::getRecipe($id);
    $classname = $r->classname;
    $recipe = new $classname($id);
    $recipe
      ->setTitle($r->title)
      ->setDescription($r->description);
    return $recipe;
  }
  public function setRecipebook($rbid) {
    $this->recipebooks[$rbid] = $rbid;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  public function save() {
    BotchaRecipeModel::save($this);

    // Clean session to fetch new values.
    Botcha::clean();
  }
  function setTitle($title) {
    $this->title = $title;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  function getTitle() {
    return $this->title;
  }
  function setDescription($description) {
    $this->description = $description;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  function getDescription() {
    return $this->description;
  }
  function setSecret($secret) {
    $this->secret = $secret;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  function getSecret() {
    return $this->secret;
  }
  function setMethod($method) {
    $this->method = $method;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  function getMethod() {
    return $this->method;
  }
  function setStatus($status) {
    $this->status = $status;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }
  function getStatus() {
    return $this->status;
  }
  function getSetting($key, $default = NULL) {
    return !empty($this->settings[$key]) ? $this->settings[$key] : $default;
  }
  function setSetting($key, $value) {
    $this->settings[$key] = $value;

    // Save changed state.
    Botcha::setRecipe($this);
    return $this;
  }

  /**
   * Magic method __construct.
   */
  function __construct($id) {
    $this->id = $id;

    // Get human-readable description about this recipe
    // to clarify its work process.
    $this
      ->getInfo();
  }

  /**
   * Used to get information about the recipe.
   * Must be overridden.
   */
  function getInfo() {
    $this->error_field = 'mail';
    $this->error_text = t('You must be a human, not a spam bot, to submit forms on this website.') . ' ' . t('If you insist that you are a human, please try again.') . ' ' . t('If error persists, contact webmaster using contact link at the bottom of this page and give all the details of this error (your browser, version, OS).');
  }

  /**
   * Used to get default recipe data structure.
   */
  function getDefaultSettings() {
    return array(
      'fields' => $this
        ->getFields(),
      'css' => $this
        ->getCss(),
      'js' => $this
        ->getJs(),
    );
  }

  /**
   * Universal getter.
   * Wrapper getProperty is used to let class methods be used not only in getting
   * default settings. It gives flexibility to make calls to the class methods
   * in any order: the first of them will always calculate the property value
   * and set the setting, while others will just get this already calculated value.
   * It also provides consistency: we are sure that when we get some property,
   * it is set appropriately.
   */
  function getProperty(&$value, $getter_callback, $parameters = NULL) {
    if (empty($value)) {
      $value = $this
        ->{$getter_callback}($parameters);
    }
    return $value;
  }

  /**
   * Spam check.
   *
   * @param type $form
   * @param type $form_state
   */
  function isSpam($form, $form_state) {
  }

  /**
   * Handle form depending on the result of spam check.
   *
   * @param type $result
   * @param type $form
   * @param type $form_state
   */
  function handle($result, $form, $form_state) {

    // @todo handle Implement real logic of handling.
    switch ($result) {
      case 'success':
        break;
      case 'spam':
      default:
        break;
    }
  }
  function getSeed() {
    return md5(get_class($this) . substr($this->secret, 0, -4));
  }
  function getFields() {
    $fields_count = $this
      ->getFieldCount();
    $fields = array();
    for ($i = 0; $i < $fields_count; $i++) {
      $fields[$i] = $this
        ->getField($i);
    }
    return $fields;
  }

  // @todo Replace deltas with machine names.
  function getField($delta) {
    return array(
      'name' => $this
        ->getProperty($this->settings['fields'][$delta]['name'], 'getFieldName', $delta),
      'class' => $this
        ->getProperty($this->settings['fields'][$delta]['class'], 'getFieldClass', $delta),
      'prefix' => $this
        ->getProperty($this->settings['fields'][$delta]['prefix'], 'getFieldPrefix', $delta),
    );
  }

  /**
   * Should be overridden.
   *
   * @return string
   */
  public function getCss() {
  }

  /**
   * Should be overridden.
   *
   * @return array
   */
  public function getJs() {
  }

  // @todo Replace deltas with machine names.
  function getFieldName($delta) {
    return substr($this
      ->getProperty($this->seed, 'getSeed'), 0, 3) . '_name';
  }

  // @todo Replace deltas with machine names.
  function getFieldClass($delta) {

    // 'a' fix for Firefox - it breaks on ".<number>" class in CSS filter!
    return 'a' . substr($this
      ->getProperty($this->seed, 'getSeed'), 1, 4) . '_field';
  }

  // @todo Replace deltas with machine names.
  function getFieldPrefix($delta) {
    return substr($this
      ->getProperty($this->seed, 'getSeed'), 10, mt_rand(3, 6));
  }

  /**
   * Used to get information about the recipe.
   * Must be overridden with calling to parent::generateFormElements.
   * @todo Switch from indexed array to associative.
   */
  function generateFormElements() {
    $css = $this
      ->getProperty($this->settings['css'], 'getCss');
    if (!empty($css)) {

      // @todo Abstract it.

      //drupal_add_css('' . $this->settings['css'] . '', array('type' => 'inline'));
      drupal_set_html_head('<style type="text/css">' . $css . '</style>');
    }
    return array();
  }

  /*
   * Apply recipe modifying form properties.
   */
  public function applyRecipe(&$form, &$form_state) {

    // Add BOTCHA fields to the form.
    $form_elements = $this
      ->generateFormElements();
    foreach ($form_elements as $field_name => $field_properties) {
      unset($field_properties['!valid_token']);
      $form[$field_name] = $field_properties;
      if ($this->method == 'build_id_submit') {

        // Save submitted values in our stash for later use in _validate,
        // as we have to reset them here at _form_alter stage.
        // It won't be possible to reset after validation as there is no
        // reliable mechanism in Form API, i.e. form_set_value() does not
        // change rendered form and form errors disable whole 'rebuild' business.
        if (isset($_POST[$field_name])) {
          $form_state['botcha_submit_values'][$field_name] = $_POST[$field_name];
        }
        if (isset($field_properties['#default_value'])) {

          // Reset our controls to defaults here (as explained above).
          $form[$field_name]['#value'] = $field_properties['#default_value'];
          $form_state['post'][$field_name] = $field_properties['#default_value'];
          $_POST[$field_name] = $field_properties['#default_value'];
        }
      }
      else {

        //unset($field_properties['!valid_token']);
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
BotchaRecipe::$css protected property CSS to add to the page.
BotchaRecipe::$description protected property Brief description of the recipe. It should contain explanation of how bots would fail with it and what the recipe exactly does.
BotchaRecipe::$error_field public property @todo Do we really need it? Probably the best way is to provide mail field always - it hides our fields. Name of the field in the form to use in error messages (to mask botcha fields).
BotchaRecipe::$error_text public property Text to give users if botcha recipe blocks submission. It should give some help to real human users in cases of disabled Javascript or CSS.
BotchaRecipe::$id public property Identifier of recipe.
BotchaRecipe::$js protected property Javascript to add to the page.
BotchaRecipe::$method protected property Method of recipe genration.
BotchaRecipe::$recipebooks protected property
BotchaRecipe::$secret protected property Secret.
BotchaRecipe::$settings protected property Options that received as parameters turned into settings by merging with default values.
BotchaRecipe::$status public property Status of the spam check.
BotchaRecipe::applyRecipe public function 2
BotchaRecipe::generateFormElements function Used to get information about the recipe. Must be overridden with calling to parent::generateFormElements. @todo Switch from indexed array to associative. 2
BotchaRecipe::getCss public function Should be overridden. 1
BotchaRecipe::getDefaultSettings function Used to get default recipe data structure.
BotchaRecipe::getDescription function
BotchaRecipe::getField function 1
BotchaRecipe::getFieldClass function
BotchaRecipe::getFieldName function 3
BotchaRecipe::getFieldPrefix function
BotchaRecipe::getFields function
BotchaRecipe::getInfo function Used to get information about the recipe. Must be overridden. 3
BotchaRecipe::getJs public function Should be overridden. 1
BotchaRecipe::getMethod function
BotchaRecipe::getProperty function Universal getter. Wrapper getProperty is used to let class methods be used not only in getting default settings. It gives flexibility to make calls to the class methods in any order: the first of them will always calculate the property value and set…
BotchaRecipe::getRecipe public static function
BotchaRecipe::getSecret function
BotchaRecipe::getSeed function
BotchaRecipe::getSetting function
BotchaRecipe::getStatus function
BotchaRecipe::getTitle function
BotchaRecipe::handle function Handle form depending on the result of spam check. 1
BotchaRecipe::isSpam function Spam check. 4
BotchaRecipe::save public function
BotchaRecipe::setDescription function
BotchaRecipe::setMethod function
BotchaRecipe::setRecipebook public function
BotchaRecipe::setSecret function
BotchaRecipe::setSetting function
BotchaRecipe::setStatus function
BotchaRecipe::setTitle function
BotchaRecipe::__construct function Magic method __construct.