diff -u b/botcha.module b/botcha.module --- b/botcha.module +++ b/botcha.module @@ -256,7 +256,7 @@ * BOTCHA administration links for site administrators if this option is enabled. */ function botcha_form_alter(&$form, &$form_state, $form_id) { - // Get singleton recipe book and apply all applicable recipes to the form. + // Get a recipe book and apply all applicable recipes to the form. $recipebook = Botcha::getForm($form['form_id']['#value'], FALSE)->getRecipebook(); if ($recipebook->isApplicable($form, $form_state)) { $recipebook->apply($form, $form_state); @@ -295,7 +295,7 @@ function _botcha_form_validate($form, &$form_state) { // FIXME: where does this empty value come from ? happens with comments unset($form_state['values']['']); - // Fetch singleton recipe book and handle spam protection. + // Fetch a recipe book and handle spam protection. $recipebook = Botcha::getForm($form['form_id']['#value'], FALSE)->getRecipebook(); if ($recipebook->isApplicable($form, $form_state)) { if ($recipebook->isSpam($form, $form_state)) { diff -u b/botcha.test b/botcha.test --- b/botcha.test +++ b/botcha.test @@ -550,7 +550,6 @@ // These ones for testing Recipebook UI. case 'addRecipebook': $this->debug("Entered %method %case", array('%method' => __METHOD__, '%case' => $form)); - // @todo Port this to D7. $this->assertTrue( $recipebook = Botcha::getRecipebook($edit['id'], FALSE), "Recipe book {$edit['id']} should exist", @@ -816,7 +815,6 @@ 'editRecipebook', 'deleteRecipebook', ); - // @todo Port to D7. // Parameters that need to be passed through methods. $parameters = array(); foreach ($forms as $form) { @@ -836,7 +834,6 @@ class BotchaTestCase extends BotchaBaseWebTestCase { function setUp() { parent::setUp(); - // @todo Port to D7. // For some reason we don't find this form after installation - but we should. // So fix it manually. Botcha::getForm('comment_node_page_form', TRUE)->save(); @@ -959,7 +956,6 @@ function setUp() { parent::setUp(); // Bind only one recipe to test recipe book. - // @todo Port to D7 also. Botcha::getRecipebook('test') ->setRecipe('honeypot') ->save(); @@ -992,7 +988,6 @@ function setUp() { parent::setUp(); // Bind only one recipe to test recipe book. - // @todo Port to D7 also. Botcha::getRecipebook('test') ->setRecipe('honeypot2') ->save(); @@ -1025,7 +1020,6 @@ function setUp() { parent::setUp(); // Bind only one recipe to test recipe book. - // @todo Port to D7 also. Botcha::getRecipebook('test') ->setRecipe('obscure_url') ->save(); @@ -1058,7 +1052,6 @@ function setUp() { parent::setUp(); // Bind only one recipe to test recipe book. - // @todo Port to D7 also. Botcha::getRecipebook('test') ->setRecipe('no_resubmit') ->save(); @@ -1150,7 +1143,6 @@ function setUp() { parent::setUp(); // Bind only one recipe to test recipe book. - // @todo Port to D7 also. Botcha::getRecipebook('test') ->setRecipe('timegate') ->save(); diff -u b/controller/botcha.controller.inc b/controller/botcha.controller.inc --- b/controller/botcha.controller.inc +++ b/controller/botcha.controller.inc @@ -19,10 +19,15 @@ * Singleton realization of botcha application. */ class Botcha { - + /* @todo Remove it. protected static $recipebooks = array(); protected static $recipes = array(); protected static $forms = array(); + * + */ + const BOTCHA_SESSION_FORMS = 'botcha_forms'; + const BOTCHA_SESSION_RECIPEBOOKS = 'botcha_recipebooks'; + const BOTCHA_SESSION_RECIPES = 'botcha_recipes'; // Protect from any way of creation: initialize, cloning and unserialize. private function __construct() { /* ... @return Singleton */ } @@ -35,44 +40,63 @@ * @return BotchaForm */ public static function getForms($reset = FALSE) { + $forms_session = &$_SESSION[self::BOTCHA_SESSION_FORMS]; + if (empty($forms_session) || $reset) { + $fs = array_keys(BotchaFormModel::getForms()); + foreach ($fs as $form_id) { + Botcha::setForm(Botcha::getForm($form_id, FALSE)); + } + } $forms = array(); - $fs = array_keys(BotchaFormModel::getForms()); - foreach ($fs as $form_id) { - $forms[$form_id] = Botcha::getForm($form_id); + foreach ($forms_session as $form_id => $form_session) { + $forms[$form_id] = unserialize($form_session); } return $forms; } /** * Gets a form from cache. If it does not exists in cache - gets from - * database. If it does not exists there also - returns NULL. + * database. If it does not exists there also - returns new form or + * BotchaFormNone depending on input parameter. * @param string $form_id + * @param boolean $create + * Determines whether we should initialize new recipe book or not. * @return BotchaForm */ public static function getForm($form_id, $create = TRUE) { - $form = BotchaForm::getForm($form_id, $create); - if (!($form instanceof BotchaFormNone)) { - // Set relationships for this concrete form. - foreach (array_keys(BotchaModel::getFormsRecipebooks(array( + // We are using $_SESSION to store the state of the spam checking, because + // HTTP is stateless by design. + $form_session = &$_SESSION[self::BOTCHA_SESSION_FORMS][$form_id]; + if (empty($form_session)) { + $form = BotchaForm::getForm($form_id, $create); + if (!($form instanceof BotchaFormNone)) { + // Set relationships for this concrete form. + $rbs = array_keys(BotchaModel::getFormsRecipebooks(array( 'mode' => 'recipebook', 'forms' => $form_id, - ))) as $rbid) { - $form = $form->setRecipebook($rbid); + ))); + foreach ($rbs as $rbid) { + $form->setRecipebook($rbid); + } } + Botcha::setForm($form); } + $form = unserialize($form_session); return $form; } /** - * Sets form to the cache. + * Sets form to the $_SESSION. * @param BotchaForm $form */ public static function setForm($form) { - self::$forms[$form->id] = $form; + //self::$forms[$form->id] = $form; + $_SESSION[self::BOTCHA_SESSION_FORMS][$form->id] = serialize($form); } public static function unsetForm($form) { - unset(self::$forms[$form->id]); + //unset(self::$forms[$form->id]); + unset($_SESSION[self::BOTCHA_SESSION_FORMS][$form->id]); } /** @@ -81,69 +105,73 @@ * @return BotchaRecipebook */ public static function getRecipebooks($reset = FALSE) { - // Get the value from the cache. // @todo Botcha getRecipebooks Find a better way to check whether we need to reset a list of all recipe books or not. // The problem is in the case when we have one recipe book cached - and it // makes unusable our cache since it does not have all recipe books. - //if (empty(self::$recipebooks) || $reset) { - $recipebooks = array(); + $recipebooks_session = &$_SESSION[self::BOTCHA_SESSION_RECIPEBOOKS]; + if (empty($recipebooks_session) || $reset) { $rbs = array_keys(BotchaRecipebookModel::getRecipebooks()); foreach ($rbs as $rbid) { - // Save recipe book to our cache. - //self::$recipebooks[$rbid] = Botcha::getRecipebook($rbid); - $recipebooks[$rbid] = Botcha::getRecipebook($rbid); + Botcha::setRecipebook(Botcha::getRecipebook($rbid, FALSE)); } - //self::$recipebooks = &drupal_static('botcha_recipebooks', self::$recipebooks); - //} - //return self::$recipebooks; - return $recipebooks; + } + $recipebooks = array(); + foreach ($recipebooks_session as $rbid => $recipebook_session) { + $recipebooks[$rbid] = unserialize($recipebook_session); + } + return $recipebooks; } /** * Gets a recipe book from cache. If it does not exists in cache - gets from - * database. If it does not exists there also - returns new recipe book or NULL - * depending on input parameter. + * database. If it does not exists there also - returns new recipe book or + * BotchaRecipebookNone depending on input parameter. * @param string $id * Machine name of the recipe book to look for. * @param boolean $create * Determines whether we should initialize new recipe book or not. - * @return BotchaRecipebook + * @return BotchaRecipebookAbstract */ public static function getRecipebook($id = 'default', $create = TRUE) { - // Process constructing of recipe book object. - // Get main parameters of recipe book. - $recipebook = BotchaRecipebook::getRecipebook($id, $create); - if (!($recipebook instanceof BotchaRecipebookNone)) { - // Get recipe book relationships. - // Get recipe book - form relationships. - // @todo Port to D7. - foreach (array_keys(BotchaModel::getRecipebooksForms(array( - 'mode' => 'form', - 'recipebooks' => $id, - ))) as $form_id) { - $recipebook->setForm($form_id); - } - // Get recipe book - recipe relationships. - foreach (array_keys(BotchaModel::getRecipebooksRecipes(array( - 'mode' => 'recipe', - 'recipebooks' => $id, - ))) as $recipe_id) { - $recipebook->setRecipe($recipe_id); + $recipebook_session = &$_SESSION[self::BOTCHA_SESSION_RECIPEBOOKS][$id]; + if (empty($recipebook_session)) { + $recipebook = BotchaRecipebook::getRecipebook($id, $create); + if (!($recipebook instanceof BotchaRecipebookNone)) { + // Set relationships for this concrete recipe book. + $fs = array_keys(BotchaModel::getRecipebooksForms(array( + 'mode' => 'form', + 'recipebooks' => $id, + ))); + foreach ($fs as $form_id) { + $recipebook->setForm($form_id); + } + // Get recipe book - recipe relationships. + $rs = array_keys(BotchaModel::getRecipebooksRecipes(array( + 'mode' => 'recipe', + 'recipebooks' => $id, + ))); + foreach ($rs as $recipe_id) { + $recipebook->setRecipe($recipe_id); + } } + Botcha::setRecipebook($recipebook); } + $recipebook = unserialize($recipebook_session); return $recipebook; } /** - * Sets recipe book to the cache. - * @param BotchaRecipebook $recipebook + * Sets recipe book to the $_SESSION. + * @param BotchaRecipebookAbstract $recipebook */ public static function setRecipebook($recipebook) { - self::$recipebooks[$recipebook->id] = $recipebook; + //self::$recipebooks[$recipebook->id] = $recipebook; + $_SESSION[self::BOTCHA_SESSION_RECIPEBOOKS][$recipebook->id] = serialize($recipebook); } public static function unsetRecipebook($recipebook) { - unset(self::$recipebooks[$recipebook->id]); + //unset(self::$recipebooks[$recipebook->id]); + unset($_SESSION[self::BOTCHA_SESSION_RECIPEBOOKS][$recipebook->id]); } /** @@ -152,10 +180,16 @@ * @return array */ public static function getRecipes($reset = FALSE) { + $recipes_session = &$_SESSION[self::BOTCHA_SESSION_RECIPES]; + if (empty($recipes_session) || $reset) { + $rs = array_keys(BotchaRecipeModel::getRecipes()); + foreach ($rs as $recipe_id) { + Botcha::setRecipe(BotchaRecipe::getRecipe($recipe_id)); + } + } $recipes = array(); - $rs = array_keys(BotchaRecipeModel::getRecipes()); - foreach ($rs as $recipe_id) { - $recipes[$recipe_id] = Botcha::getRecipe($recipe_id); + foreach ($recipes_session as $recipe_id => $recipe_session) { + $recipes[$recipe_id] = unserialize($recipe_session); } return $recipes; } @@ -166,16 +200,45 @@ * @param string $id - * Machine name of the recipe book to look for. + * Machine name of the recipe to look for. + * @param boolean $create + * Determines whether we should initialize new recipe book or not. * @return BotchaRecipe */ - public static function getRecipe($id) { - $recipe = BotchaRecipe::getRecipe($id); - // Set relationships for this concrete recipe. - foreach (array_keys(BotchaModel::getRecipesRecipebooks(array( + public static function getRecipe($id, $create = TRUE) { + $recipe_session = &$_SESSION[self::BOTCHA_SESSION_RECIPES][$id]; + if (empty($recipe_session)) { + $recipe = BotchaRecipe::getRecipe($id, $create); + // @todo ?Do we need it? + //if (!($recipe1 instanceof BotchaRecipeNone)) { + // Set relationships for this concrete recipe. + $rbs = array_keys(BotchaModel::getRecipesRecipebooks(array( 'mode' => 'recipebook', 'recipes' => $id, - ))) as $rbid) { - $recipe->setRecipebook($rbid); + ))); + foreach ($rbs as $rbid) { + $recipe->setRecipebook($rbid); + } + //} + Botcha::setRecipe($recipe); } + $recipe = unserialize($recipe_session); return $recipe; } + + /** + * Sets recipe to the $_SESSION. + * @param BotchaRecipe $recipe + */ + public static function setRecipe($recipe) { + $_SESSION[self::BOTCHA_SESSION_RECIPES][$recipe->id] = serialize($recipe); + } + + public static function unsetRecipe($recipe) { + unset($_SESSION[self::BOTCHA_SESSION_RECIPES][$recipe->id]); + } + + public static function clean() { + unset($_SESSION[self::BOTCHA_SESSION_FORMS]); + unset($_SESSION[self::BOTCHA_SESSION_RECIPEBOOKS]); + unset($_SESSION[self::BOTCHA_SESSION_RECIPES]); + } } diff -u b/controller/botcha_form.controller.inc b/controller/botcha_form.controller.inc --- b/controller/botcha_form.controller.inc +++ b/controller/botcha_form.controller.inc @@ -48,11 +48,15 @@ public function setRecipebook($rbid) { $this->recipebook = $rbid; + // Save changed state. + Botcha::setForm($this); return $this; } function unsetRecipebook() { unset($this->recipebook); + // Save changed state. + Botcha::setForm($this); return $this; } @@ -61,7 +65,16 @@ * @return BotchaRecipebook */ public function getRecipebook() { - return Botcha::getRecipebook(!empty($this->recipebook) ? $this->recipebook : 'none', FALSE); + //return Botcha::getRecipebook(!empty($this->recipebook) ? $this->recipebook : 'none', FALSE); + if (empty($this->recipebook)) { + $rbs = array_keys(BotchaModel::getRecipebooksForms(array( + 'mode' => 'recipebook', + 'forms' => $this->id, + ))); + // In fact there is not more than 1 item. + $this->recipebook = !empty($rbs) ? current($rbs) : 'none'; + } + return Botcha::getRecipebook($this->recipebook, FALSE); } public function save() { diff -u b/controller/botcha_recipe.controller.inc b/controller/botcha_recipe.controller.inc --- b/controller/botcha_recipe.controller.inc +++ b/controller/botcha_recipe.controller.inc @@ -28,6 +28,16 @@ protected $settings = array(); /** + * Secret. + */ + protected $secret; + + /** + * Method of recipe genration. + */ + protected $method; + + /** * CSS to add to the page. */ protected $css; @@ -53,6 +63,11 @@ protected $recipebooks = array(); + /** + * Status of the spam check. + */ + public $status = 'none'; + public static function getRecipe($id) { $r = BotchaRecipeModel::getRecipe($id); $classname = $r->classname; @@ -64,6 +79,8 @@ public function setRecipebook($rbid) { $this->recipebooks[$rbid] = $rbid; + // Save changed state. + Botcha::setRecipe($this); return $this; } @@ -73,6 +90,8 @@ function setTitle($title) { $this->title = $title; + // Save changed state. + Botcha::setRecipe($this); return $this; } @@ -82,6 +101,8 @@ function setDescription($description) { $this->description = $description; + // Save changed state. + Botcha::setRecipe($this); return $this; } @@ -91,6 +112,8 @@ function setSecret($secret) { $this->secret = $secret; + // Save changed state. + Botcha::setRecipe($this); return $this; } @@ -100,6 +123,8 @@ function setMethod($method) { $this->method = $method; + // Save changed state. + Botcha::setRecipe($this); return $this; } @@ -107,6 +132,17 @@ return $this->method; } + function setStatus($status) { + $this->status = $status; + // Save changed state. + Botcha::setRecipe($this); + return $this; + } + + function getStatus() { + return $this->status; + } + /** * Magic method __construct. */ @@ -270,6 +306,8 @@ // 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. @@ -287,8 +325,7 @@ } } else { - unset($field_properties['!valid_token']); - $form[$field_name] = $field_properties; + //unset($field_properties['!valid_token']); } } } @@ -593,6 +630,7 @@ ); $js_tok1 = reset($js['chops']); $js_tok2 = next($js['chops']); +/* @todo Remove it. return <<generateFormElements() as $field_name => $formElement) { - if (isset($formElement['!valid_token']) && isset($form_state['botcha_submit_values'][$field_name]) - && $form_state['botcha_submit_values'][$field_name] !== $formElement['!valid_token']) { + foreach ($this->generateFormElements() as $field_name => $form_element) { + if (!empty($form_element['!valid_token']) && !empty($form_state['botcha_submit_values'][$field_name]) + && $form_state['botcha_submit_values'][$field_name] !== $form_element['!valid_token']) { $isSpam = TRUE; break; } @@ -659,6 +707,7 @@ ); $js_tok1 = reset($js['chops']); $selector = "input.{$fields[0]['class']}"; +/* @todo Remove it. return <<title = $title; + // Save changed state. + Botcha::setRecipebook($this); return $this; } @@ -145,6 +143,8 @@ function setDescription($description) { $this->description = $description; + // Save changed state. + Botcha::setRecipebook($this); return $this; } @@ -154,11 +154,15 @@ 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; } @@ -167,29 +171,30 @@ * @return array */ function getRecipes() { - $recipes = array(); if (empty($this->recipes)) { - // @todo Port this to D7. $this->recipes = array_keys(BotchaModel::getRecipebooksRecipes(array( 'mode' => 'recipe', 'recipebooks' => $this->id, ))); } + $recipes = array(); foreach ($this->recipes as $recipe_id) { - $recipes[$recipe_id] = Botcha::getRecipe($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; } - - // @todo Port to D7. function unsetForm($form_id) { unset($this->forms[$form_id]); + // Save changed state. + Botcha::setRecipebook($this); return $this; } @@ -198,16 +203,15 @@ * @return BotchaForm */ function getForms() { - $forms = array(); if (empty($this->forms)) { - // @todo Port this to D7. $this->forms = array_keys(BotchaModel::getRecipebooksForms(array( 'mode' => 'form', 'recipebooks' => $this->id, ))); } + $forms = array(); foreach ($this->forms as $form_id) { - $forms[$form_id] = Botcha::getForm($form_id); + $forms[$form_id] = Botcha::getForm($form_id, FALSE); } return $forms; } @@ -220,9 +224,9 @@ */ function getRecipesByStatus($status) { $recipes_list = array(); - foreach ($this->result as $recipe_id => $recipe_status) { - if ($recipe_status == $status) { - $recipes_list[$recipe_id] = Botcha::getRecipe($recipe_id); + foreach ($this->getRecipes() as $recipe_id => $recipe) { + if ($recipe->getStatus() == $status) { + $recipes_list[$recipe_id] = $recipe; } } return $recipes_list; @@ -317,6 +321,8 @@ // !!? Do we need per recipe rules event invoking? rules_invoke_event($rules_event_name, $arguments); } + // Clean $_SESSION. + Botcha::clean(); } protected function getRecipeSecret($value) { @@ -334,14 +340,14 @@ */ function isSpam($form, $form_state) { $isSpam = FALSE; - foreach ($this->getRecipes() as $recipe) { - $status = ($recipe->isSpam($form, $form_state)) ? 'spam' : 'success'; + // 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($status, $form, $form_state); - // Remember the result of this check. - $this->result[$recipe->id] = $status; + $recipe->handle($recipe->getStatus(), $form, $form_state); // One is enough to block the form. - $isSpam = $isSpam || ($status == 'spam'); + $isSpam = $isSpam || ($recipe->getStatus() == 'spam'); } return $isSpam; } @@ -371,9 +377,11 @@ //$form_state['no_cache'] = TRUE; $form += array('#input' => TRUE); // '#input'=1 hacks FAPI to call #process handler on the form $form['#process'][] = 'botcha_fprocess'; + /* @todo Remove it. $recipe_way = array(); // !!~ @todo Reduce code duplication. // @see BotchaRecipe.applyRecipe() + // @todo Move this to submit handler. $build_id_submit = isset($_POST['form_build_id']) ? $_POST['form_build_id'] : FALSE; $build_id = $form['#build_id']; if ($build_id_submit) { @@ -383,6 +391,17 @@ foreach ($recipe_way as $key => $value) { $this->applyForBuildId($form, $form_state, $key, $value); } + * + */ + 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'])) { @@ -417,10 +436,11 @@ } } - protected function applyForBuildId(&$form, &$form_state, $key, $value) { - $secret = $this->getRecipeSecret($value); - $method = $key; - foreach ($this->getRecipes() as $recipe) { + 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); diff -u b/model/botcha.model.inc b/model/botcha.model.inc --- b/model/botcha.model.inc +++ b/model/botcha.model.inc @@ -30,7 +30,6 @@ } public static function getRecipebooksForms($parameters = array()) { - // @todo Port to D7. $key = ''; switch ($parameters['mode']) { case 'form': @@ -63,7 +62,6 @@ } public static function getRecipebooksRecipes($parameters = array()) { - // @todo Port to D7. $key = ''; switch ($parameters['mode']) { case 'recipe':