I am developing simple guestbook module for one of my customers and as a spam prevention I decided to use an array of simple questions. For this I have to pick random question from the array and set the ID of this question (array element ID) in form so I can check the answer in submit handler. The problem is that the form is regenerated before the submit handler is evaluated so the random value changes.
Shortly: I am getting other value of $form_state['values']['queston_id'] in submit handler function that it is in form. Why is that and how can I change this?
Thanks a lot!
This is my module:
function gb_menu() {
$items = array();
$items['gb'] = array(
'title' => t('Guestbook'),
'description' => t('Guestbook page'),
'page callback' => 'gb_guestbook',
//'page arguments' => array('gb_guestbook'),
'access arguments' => array('view guestbook'),
);
return $items;
}
function gb_guestbook() {
dpm('generating page');
$page = NULL;
$page .= drupal_render(drupal_get_form('gb_guestbook_form'));
$page .= 'list of guestbook messages here';
return $page;
}
function gb_guestbook_form($form, &$form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#description' => t('Please, enter your name.'),
);
$form['message'] = array(
'#type' => 'textarea',
'#cols' => 5,
'#title' => t('Message'),
'#description' => t('Please, enter your message.'),
);
$questions = gb_get_question();
$question_id = rand(0, count($questions)-1);
$question = $questions[$question_id];
$form['question_id'] = array(
'#type' => 'hidden',
'#value' => $question_id,
);
$form['spam'] = array(
'#title' => $question['question'],
'#description' => t('Please, answer the simple question so we know that you are a human.'),
'#type' => 'textfield',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
function gb_guestbook_form_submit($form, &$form_state) {
// spam check
$values = $form_state['values'];
$questions = gb_get_question();
$answers = $questions[$values['question_id']]['answers'];
if (!in_array(strtolower($values['spam']), $answers)) {
drupal_set_message(t('You did not reply the answer correctly. Are you sure it was not typo?'), 'error');
}
else {
drupal_set_message(t('Thanks for the contribution!'), 'status');
// processing input
}
}
function gb_get_question() {
return array(
array (
'question' => t('What is the capital of Slovakia?'),
'answers' => array('bratislava'),
),
array (
'question' => t('What is the name of this band?'),
'answers'=> array('divozel'),
),
array (
'question' => t('What is the biggest town in east part of Slovakia?'),
'answers' => array('košice','kosice'),
),
);
}
You should probably do this check in gb_guestbook_form_VALIDATE instead.
I'd do this in a validation hook. Also another issue in the function gb_guestbook_form_submit
You get the questions but not the answers array and then you are trying to compare them. This could also be the source of your issue, you capture questions but not answers and then compare to the empty array of answers.
Wrap your random question generator in a conditional statement that checks if your question has been defined in the form yet.
$question_id = NULL;
$questions = gb_get_question();
if (!isset($form['question_id'])) {
$question_id = rand(0, count($questions)-1);
}
else {
$question_id = $form['question_id']['#value'];
}
$question = $questions[$question_id];
Not sure why my other answer isn't working for you, but here's another approach. The idea would be to store the question id in $form_state['storage'].
First, correct your form declaration so it passes in $form_state as a reference.
function gb_guestbook_form($form, &$form_state) {
Now check the $form_state['storage'] array for your question id, and set it if you haven't already, before you set the form value question_id.
$questions = gb_get_question();
if (!isset($form_state['storage']['question_id'])) {
$form_state['storage']['question_id'] = array_rand($questions);
}
$form['question_id'] = array(
'#type' => 'hidden',
'#value' => $form_state['storage']['question_id'],
);
Your problem (and mine) is describe here:
http://www.sparklepod.com/myblog/drupal-form-same-form-for-build-and-validation/
In short. On submit the form is rebuild before the validation code is run. For me the solutions in the blog is not working for my Drupal 7 site.
What I have done is added the following line to the forum build:
$form_state['cache'] = true;
That will cache the form and the on _submit I will set it to false.
Related
I am newbie in drupal and i am developing a custom module.Actually i am listing all drupal menu's and their children using fieldset and checkboxes against each menu in a loop.
Here what is i have done so far.
function hide_menu_item_configuration_form($node, &$form_state){
$form = array();
$form['markup'] = array(
'#type'=>'markup',
'#markup'=>t('<p>Select a menu.</p>'),
);
$menus = menu_get_menus($all = TRUE);
foreach ($menus as $key => $value) {
$form['menus'][$value] = array(
'#type'=>'fieldset',
'#title'=>t($value),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$menu_items = menu_load_links($key);
foreach ($menu_items as $key => $values) {
$form['menus'][$value][$values['link_title']] = array(
'#type' => 'checkbox',
'#title' => t($values['link_title']),
'#' => t($values['link_title']),
'#default_value' => 1
);
}
}
$form['config_submit'] = array(
'#type'=>'submit',
'#value'=>'Save configuration',
);
return $form;
}
function hide_menu_item_configuration_form_submit(&$form, &$form_state) {
//here i see values
dsm($form_state);
}
Now what the problem is that on form submit,i am getting boolean value against each menu link like 0 or 1 .In this case,i can't gues which link was checked or not.Because of 0 or 1 which doesn't indicate anything.But i want to get menu machine name and link machine name.
I am stuck here.
Can you please guys help or suggest me some other way to complete this thing?
Please help.
pls changes your code first build the options array using the foreach loop you are running
$options = array();
$menu_items = menu_load_links($key);
foreach ($menu_items as $key => $values) {
$options[$values['link_title']] = $values['link_title'];
}
$form['name']=array(
'#type'=>'checkbox',
'#title'=>'any title',
'#description' => 'any Description',
'#options' => $options,
);
I'm trying to learn to write drupal modules and am working on writing a simple math module http://nodeone.se/en/the-math-question-module
I've got the GUI down fine but seem to be having issues with the actual math. I never seem to get the correct answer because I think it loads a new set of questions before it checks the current answer.
Here's what I have so far:
<?php
/**
* #file
* Tests users on their math skills through a series of question and answers
*/
/**
* Implements hook_menu().
*/
function math_question_menu() {
// add new navigation menu item
$items['math_question'] = array(
'title' => 'Math questions',
'description' => 'Test you math skills with these questions.',
'page callback' => 'drupal_get_form',
'page arguments' => array('math_question_page'),
'access callback' => 'user_access',
'access arguments' => array('administer_site_configuration'),
);
return $items;
}
$val1 = rand(1,10);
$val2 = rand(1,10);
variable_set('val1', $val1);
variable_set('val2', $val2);
/**
* Builds the form for configuring Math Questions.
*/
function math_question_page() {
global $user;
$num1 = variable_get('val1');
$num2 = variable_get('val2');
$total = $num1 + $num2;
variable_set('total',$total);
$form['math_question'] = array(
'#type' => 'item',
'#markup' => 'What is ' . $num1 . ' + ' . $num2 . ', ' .
check_plain($user->name) . '?',
);
$form['answer'] = array(
'#type' => 'textfield',
'#title' => t('Answer'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Check my answer'),
);
return $form;
}
function math_question_page_validate($form, $form_state) {
if (empty($form['answer']['#value'])) {
form_error($form['answer'], t('This field is required.'));
}
if ($form['answer']['#value'] ==
variable_get('total')) {
$msg = t('Good job!');
$type = 'status';
}
else {
$msg = t('Try again...');
$type = 'error';
}
drupal_set_message(check_plain($msg), $type);
}
modified code:
.
.
/**
* Builds the form for configuring Math Questions.
*/
function math_question_page() {
global $user;
$val1 = rand(1,10);
$val2 = rand(1,10);
$total = $val1 + $val2;
$form['math_question'] = array(
'#type' => 'item',
'#markup' => 'What is ' . $val1 . ' + ' . $val2 . ', ' .
check_plain($user->name) . '?',
);
$form['answer'] = array(
'#type' => 'textfield',
'#title' => t('Answer'),
);
$form['total'] = array(
'#type' => 'hidden',
'#value' => $total,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Check my answer'),
);
return $form;
}
function math_question_page_validate($form, $form_state) {
if (empty($form['answer']['#value'])) {
form_error($form['answer'], t('This field is required.'));
}
if ($form['answer']['#value'] ==
$form['total']['#value']) {
$msg = t('Good job!');
$type = 'status';
}
else {
$msg = t('Try again...');
$type = 'error';
}
drupal_set_message(check_plain($msg), $type);
}
In the validation function math_question_page_validate(), you should access the values from $form_state variable, not from $form.
eg:
if (!$form_state['values']['answer'] == $form_state['values']['total']) {
form_set_error('answer', $error);
}
You can also store the answer in $form_state variable to pass it to the validation function (no need to store the value in $form). if you store it as $form_state['total'], you can access it in the same way (given below).
if (!$form_state['total'] == $form_state['values']['answer']) {form_set_error('answer', $error);}
Thank you very much papirrin for teaching this.
I suggest to use variable_get() to keep the correct answer safe. The random value you keep in the form will not survive the form regeneration.
My working code here.
/**
* Builds the form for configuring Math Questions.
*/
function math_question_page($form, &$form_state) {
global $user;
$val1 = rand(1,10);
$val2 = rand(1,10);
$total = $val1 + $val2;
// Store the correct answer safely away from this form
if (!variable_get('math_question_answer',FALSE)) {
variable_set('math_question_answer', $total);
}
$form['math_question'] = array(
'#type' => 'item',
'#markup' => 'What is ' . $val1 . ' + ' . $val2 . ', ' .
check_plain($user->name) . '?',
);
$form['answer'] = array(
'#type' => 'textfield',
'#title' => t('Answer'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Check my answer'),
);
$form_state['redirect'] = FALSE;
return $form;
}
function math_question_page_submit($form, &$form_state) {
if (empty($form['answer']['#value'])) {
form_set_error('answer', t('This field is required.'));
}
else {
if ($form_state['values']['answer'] == variable_get('math_question_answer')) {
$msg = t('Good job!');
$type = 'status';
}
else {
$msg = t('Try again...');
$type = 'error';
}
drupal_set_message(check_plain($msg), $type);
}
variable_del('math_question_answer');
}
Drupal redirects after form submission so your form fields (altogether with your form) are recreated before the validation is called. See details about that in another question.
Your form is created with math_question_page, which does not take any form_state arguments. If you had form_state, then you could detect that math_question_page has not been submitted yet and just keep the state of the last call (last random values).
In order to call math_question_page with a form_state argument, you can use drupal_build_form instead of drupal_get_form in your menu.
Instead of storing the $total in the global variable 'total', try storing it as a hidden field on your form (unless you are worried about your users cheating). You can write:
'#type' => 'hidden,
to create a hidden 'total' field and then check for that value in validate so prevent total to be overwritten.
I am learning to write drupal modules too (Started the tutorial series 2 weeks ago).
I tried to do this same module , and i think that the implementation which saves the random values in the database is NOT correct. The first thing asked in the exercise is this :
Any user visiting the /question path will get a simple add-two-numbers question, such as "What is 4 + 9?".
So acces arguments for the page should be 'access arguments' => array('access content') not 'access arguments' => array('administer_site_configuration').
This comes with the following issue : If there are 2 (or more) users browsing at the same moment this page , the latest user overwrites the values in the database , so the other users won't be able to complete the "question" because his answer will not be equal to the answer in the database. Saving values to database is NOT a option in this case...
Meantime I am trying to make it work without the database , but i have the same situation as this question's starter , the form overwrites the values when i submit it (so , error thrown because the answer is wrong ).
If i'll manage to complete it i'll post the code here , or if someone else managed to complete it without variable_set() and variable_get() , i ask them to post the code here :)
I am trying to build my own node reference custom field - I know several projects out there already do this, I am building this in order to learn... :)
My problem is the autocomplete path, it's not being triggered, I have checked the noderefcreate project and implemented my solution based on that project. But still; nothing is being triggered when I check firebug.
Here is my code:
function nodereference_field_widget_info() {
return array(
'nodereference_nodereference_form' => array(
'label' => t('Node Reference Form'),
'field types' => array('nodereference'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
'settings' => array(
'autocomplete_match' => 'contains',
'autocomplete_path' => 'nodereference/autocomplete',
),
),
);
}
function nodereference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($instance['widget']['type'] == 'nodereference_nodereference_form') {
$widget = $instance['widget'];
$settings = $widget['settings'];
$element += array(
'#type' => 'textfield',
'#default_value' => isset($items[$delta]['nid']) ? $items[$delta]['nid'] : NULL,
'#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'],
);
}
return array('nid' => $element);
}
You need to define method from where auto compete will take suggestions.
It can be done like this:
/**
* implements hook_menu
*/
function your_module_name_menu() {
$items['master_place/autocomplete'] = array(
'page callback' => '_your_module_name_autocomplete',
'access arguments' => array('access example autocomplete'),
'type' => MENU_CALLBACK
);
return $items;
}
/**
* Auto complete method implementation
* #param $string
*/
function _your_module_name_autocomplete($string) {
$matches = array();
// Some fantasy DB table which holds cities
$query = db_select('target_db_table', 'c');
// Select rows that match the string
$return = $query
->fields('c', array('target_column'))
->condition('c.target_column', '%' . db_like($string) . '%', 'LIKE')
->range(0, 10)
->execute();
// add matches to $matches
foreach ($return as $row) {
$matches[$row->city] = check_plain($row->city);
}
// return for JS
drupal_json_output($matches);
}
What I am trying to do is display a table with checkboxes on the press of a button by ajax. The table should be initially hidden and get populated on the fly by a function call.
If initially I load $options1 with some dummy values , then after ajax call it throws in an error saying-
Notice: Undefined index: red in theme_tableselect() (line 3285 of
D:\wamp\www\drupal7\includes\form.inc).
where 'red' is the index of a dummy row value and #options don't get populated with the new values. What is the way to get this working ?
Here is the code for the form-
$form['mltag_new']['tag'] = array(
'#type' => 'button',
'#value' => t("Suggest Tags"),
'#ajax' => array(
'callback' => 'mltag_suggest_tags_ajax',
'wrapper' => 'mltag_suggest_tags_table_div',
'effect' => 'slide',
),
);
$options1 = array(); //initial dummy values
$options1['red']['tag'] = "A red row";
$options1['red']['chi'] = "A red row";
$form['mltag_new']['myselector'] = array (
'#type' => 'tableselect',
'#title' => 'My Selector',
'#header' => $header,
'#options' => $options1,
'#prefix' => '<div id="mltag_suggest_tags_table_div">',
'#suffix' => '</div>',
);
return $form;
and the Ajax callback looks something like this-
function mltag_suggest_tags_ajax($form, $form_state) {
//$content has some content
//pass the content to a function
include_once 'includes/content_tag.inc';
$tags = mltag_content_tag($content, variable_get('algo_type'), 20);
if (empty($tags)) {
$output .= t('Content is insufficient to generate Tags using this algorithm. <br>Please choose other algorithm from Settings Page.');
$form['mltag_new']['sample_text']['#markup'] = $output;
return $form['mltag_new']['sample_text'];
}
else {
$algo = variable_get('algo_type');
if ($algo == 1) {
$header = array(
'tag' => t('Tag'),
'frequency' => t('Frequency'),
);
$options = array();
foreach ($tags as $key => $value) {
$options[$key] = array(
'tag' => $key,
'frequency' => $value,
);
}
}
elseif ($algo == 2) {
$header = array(
'tag' => t('Tag'),
'chi' => t('Chi Square Value'),
);
$options = array();
foreach ($tags as $key => $value) {
$options[$key] = array(
'tag' => $key,
'chi' => $value,
);
}
}
$form['mltag_new']['myselector']['#header'] = $header;
$form['mltag_new']['myselector']['#options'] = $options;
return $form['mltag_new']['myselector'];
}
}
I replied to your post on Drupal.org about how I'm working on a somewhat similar problem. Try adding
$form['mltag_new']['myselector'] =
form_process_tableselect($form['mltag_new']['myselector']);
just before your return. Hopefully that helps you more than it did me. Beware that the #options just get rendered when the block reloads from the ajax, but the original $form object doesn't seem to be aware.
I know that this is a few years later, but I found this while searching for my own solution:
The tableselect module creates checkboxes in the $ form that have to be removed. in the example above, they would be in $form['mltag_new']['myselector'] with keys equal to the original $option1 in your original code. If you unset those, then call
$form['mltag_new']['myselector'] = form_process_tableselect($form['mltag_new']['myselector']);
before your return, it will eliminate the dummy rows.
I want to create an auto-complete form in my custom module that will be loaded in a block. Drupal doesn't seem to be loading the necessary Javascript libraries to work properly. How do I know what needs to be loaded and how/where do I tell Drupal to load these libraries?
hook_block_view:
function my_module_block_view($delta = '') {
//The $delta parameter tells us which block is being reqested.
switch ($delta) {
case 'my_module_my_block':
$block['subject'] = t('Block Subject');
$block['content'] = drupal_get_form('my_module_my_form');
break;
}
return $block;
}
Form code:
function my_module_my_form($form, &$form_state) {
$form = array();
$form['term'] = array(
'#type' => 'textfield',
'#autocomplete_path' => 'my-module-autocomplete'
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Add',
);
return $form;
}
The form loads, the field is there, but auto-complete isn't working :(
If I call the my-module-autocomplete path I do get a valid response back when compared with a Content Type edit form. The ajax spinner in the input field never appears so the ajax isn't being called. Realistically all I want is the autocomplete field...the submit will be handled manually.
It's probably because you're reseting $form to an empty array at the beginning of the function. In Drupal 7 there's a bunch of stuff added to that element before it's passed through to your form function (that's why $form is passed to your function whereas in Drupal 6 it wasn't).
Just remove $form = array(); and it should work, other than that your code looks perfect.
the following should work;
function mymodule_block_info() {
$blocks['mymodule'] = array(
// The name that will appear in the block list.
'info' => t('My Module'),
// Default setting.
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
function mymodule_block_view($delta = ''){
switch($delta){
case 'mymodule':
if(user_access('access content')){ //good idea to check user perms here
$block['subject'] = t('My Module');
$block['content'] = 'Hi :)';
$block['content'] = drupal_get_form('mymodule_form');
return $block;
}
break;
}
}
function mydmodule_menu() {
$items['module/autocomplete'] = array(
'page callback' => 'mymodule_autocomplete',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK
);
return $items;
}
function mymodule_form($form, &$form_state) {
$form['greenentry'] = array(
'#type' => 'textfield',
'#title' => t('Enter'),
'#autocomplete_path' => 'mymodule/autocomplete',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
function mymodule_autocomplete($string) {
$matches = array();
// Some fantasy DB table which holds cities
$query = db_select('cities', 'c');
// Select rows that match the string
$return = $query
->fields('c', array('city'))
->condition('c.city', '%' . db_like($string) . '%', 'LIKE')
->range(0, 10)
->execute();
// add matches to $matches
foreach ($return as $row) {
$matches[$row->url] = check_plain($row->url);
}
// return for JS
drupal_json_output($matches);
}
this code is so pretty to add an auto-complete filed in block. But i just found a tiny notice here. if someone get an error
An ajax error occurred. http result code 200
then just add
exit();
after the line
drupal_json_output($matches);
hence fix the issue.