Codeigniter Form validation: Stop validating following rules if a rule fails - codeigniter-3

This is my controller method to process the user input
function do_something_cool()
{
if ($this->form_validation->run() === TRUE)
{
// validation passed process the input and do_somthing_cool
}
// show the view file
$this->load->view('view_file');
Validation Rules are as follow:
<?php
$config = array(
'controller/do_something_cool' => array(
array(
'field' => 'email',
'label' => 'Email',
'rules' => 'trim|required|valid_email|callback_check_email_exists',
)
)
);
My problem:
If the user input is not a valid email, the validation rule is not stopping from executing the next rule, that is callback function in this case. Therefore, even if the email is not valid, I am getting the error message for check_email_exists() callback.
Is there any option in CI to stop checking the other rules if a rule failed?

From system/libraries/Form_validation.php's _prepare_rules() method,
"Callbacks" are given the highest priority (always called), followed
by 'required' (called if callbacks didn't fail), and then every next
rule depends on the previous one passing.
That means, the input will be validated against the callbacks first. So we will have to check the input within the callback function itself.
For the above case, I have modified my callback function as follows
function check_email_exists($email)
{
if ($this->form_validation->valid_email($email) === FALSE)
{
$this->form_validation->set_message('check_email_exists', 'Enter a valid email');
return FALSE;
}
// check if email_exists in the database
// if FALSE, set validation message and return FALSE
// else return TRUE
}

Related

Symfony2 form unchecked checkbox not taken into account, why?

When I send a form with an unchecked checkbox, if the related entity property equals true, then it does not change to false.
The other way round (setting property to true when the form is sent with a checked checkbox) works fine, as well as all the forms other fields saving.
Here is how I build the form and declare the related property:
// --- Form creation function EntityType::buildForm() ---
$builder->add('secret', 'checkbox', array( 'required' => false ));
// --- Entity related property, Entity.php file ---
/** #ORM\Column(name="secret", type="boolean") */
protected $secret;
EDIT: The issue happens because the form is submitted using a PATCH request.
In Symfony, the Form::submit method is called by a Request Handler with this line:
$form->submit($data, 'PATCH' !== $method);
As a result the Form::submit $clearMissing parameter is set to false in the case of a PATCH request, thus leaving the non-sent fields to their old value.
But I do not know how to solve the problem. If I explicitely pass a JSON {secret: false} to the Symfony framework when the checkbox is not checked, it will interpret it as the "false" string and consider that a true value, thus considering the checkbox checked...
NB. I have exactly the same issue with an array of checkboxes using a choice field type (with multiple and extended to true) linked to a Doctrine Simple Array property: as soon as a given checkbox has been sent once as checked, it is impossible to set back the related property to false with subsequent unchecked submissions.
Non of above-mentioned didn't help me.
So, I am using this...
Explanation
Resolution for this issue when "PATCH" method was used, was to add additional hidden "timestamp" field inside of a form type and to have it next to the checkbox of issue in twig file. This is needed to pass something along with the checkbox, that would definitely change - time-stamp will change.
Next thing was to use PRE_SUBMIT event and to wait for form field to arrive and if it not set, I would set it manually... Works fine, and I don't mind extra code...
FormType
$builder
...
->add('some_checkbox')
->add('time_stamp', 'hidden', ['mapped' => false, 'data' => time()])
...
Twig
{{ form_widget(form.time_stamp) }}
{{ form_widget(form.some_checkbox) }}
PRE_SUBMIT event in builder
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use($options) {
$data = $event->getData();
$form = $event->getForm();
if (!$data) {
return;
}
/* Note that PATCH method is added as an option for "createForm"
* method, inside of your controller
*/
if ($options["method"]=="PATCH" && !isset($data['some_checkbox'])) {
$form->getData()->setSomeCheckbox(false);//adding missing checkbox, since it didn't arrive through submit.. grrr
}
});
The issue happens because the form is submitted using a PATCH request.
This has lead to open this Symfony issue.
As explained, one workaround is to explicitely send a specific reserved value (for instance the string '__false') when the checkbox is unchecked (instead of sending nothing), and replace this value by 'null' using a custom data transformer in the form type:
// MyEntityFormType.php -- buildForm method
$builder->add('mycheckbox', ...);
$builder->get('mycheckbox')
->addViewTransformer(new CallbackTransformer(
function ($normalizedFormat) {
return $normalizedFormat;
},
function ($submittedFormat) {
return ( $submittedFormat === '__false' ) ? null : $submittedFormat;
}
));
The case with the 'choice' field can't be solved the same way. It is actually a bug of Symfony, dealt with in this issue.
What version of Symfony are you using?
There should exist some code dedicated to the situation you're writing about, in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php, in Form::submit():
// Treat false as NULL to support binding false to checkboxes.
// Don't convert NULL to a string here in order to determine later
// whether an empty value has been submitted or whether no value has
// been submitted at all. This is important for processing checkboxes
// and radio buttons with empty values.
if (false === $submittedData) {
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
}
Located at lines 525-534 for me. Could you check this works properly for you?
Another lead would be a custom form subscriber that do not work exactly as intended - by overwriting the provided value.
It's probably because the field isn't required on you schema. you can provide a default value to the checkbox with the following:
$builder->add('secret', 'checkbox', array(
'required' => false,
'empty_data' => false
));
See here or here
This solution works for me.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('isActive', CheckboxType::class, array(
'required' => false
))
$builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $e {
$entity = $e->getData();
$form = $e->getForm();
$isActive = empty($_POST[$form->getName()]['isActive']) ? false : true;
$entity->setIsActive($isActive);
});
}
Another possibility is to add a hidden element and make this with Javascript. It will fail in 0.1 % of the people that use a browser without javascript.
This is a simple example for a multiple checkboxes FormType element:
->add('ranges', ChoiceType::class, array(
'label' => 'Range',
'multiple' => true,
'expanded' => true,
'choices' => array(
null => 'None',
'B1' => 'My range',
)
))
<script>
$(document).ready(function() {
function updateDynRanges(object) {
if (object.prop("checked")) {
document.getElementById('ranges_0').checked = 0;
} else {
document.getElementById('ranges_0').checked = 1;
}
}
// On page onload
$('#ranges_1').change(function() {
updateDynRanges($(this));
});
updateDynRanges($('ranges_1'));
}
</script>
If after testing works you can just add a visibility:false to the second checkbox.
Twig template:
{{ form_label(form.dynamicRanges) }}<br>
{{ form_widget(form.dynamicRanges[1]) }}
<div class="hidden">{{ form_widget(form.ranges[0]) }}</div>
Looks like an ugly workaround, but I just wanted to compete with the other ugly suggested workarounds, in this case mostly updating the twig template.

How to disable filter of a field in Zend Framework 2 in the controller?

Form to add/edit user I get from service manager with already installed filter, which is the test password. But this password is not needed when the user is edited. Can I somehow disabled password field validation in the controller?
In the getServiceConfig function of the module:
// ....
'UserCRUDFilter' => function($sm)
{
return new \Users\Form\UserCRUDFilter();
},
'UserCRUDForm' => function($sm, $param, $param1)
{
$form = new \Users\Form\UserCRUDForm();
$form->setInputFilter($sm->get('UserCRUDFilter'));
return $form;
},
// ....
In the controller I first of all getting a form object from service manager:
$form = $this->getServiceLocator()->get('UserCRUDForm');
Then disable user password validation and requirements, when user is edited and password not specified:
if ($user_id > 0 && $this->request->getPost('password') == '') {
$form->.... // Someway gained access to the filter class and change the password field validation
}
And after this i make a validation:
$form->isValid();
I found it!
// If user is editted - clear password requirement
if ($user_id > 0) {
$form->getInputFilter()->get('password')->setRequired(false);
$form->getInputFilter()->get('confirm_password')->setRequired(false);
}
This lines is disables requirement of input form fields :)
if you like to set all validators by yourself, call inside your form class
$this->setUseInputFilterDefaults(false);
to disable auto element validations/filter added from zend.
if you like to remove filter from elements call in your controller after your form object this
$form->getInputFilter()->remove('InputFilterName');
$form->get('password')->removeValidator('VALIDATOR_NAME'); should do the trick.
Note that you may have to iterate trough the Validatorchain when using Fieldsets.
$inputFilter->add(array(
'name' => 'password',
'required' => true,
'allow_empty' => true,
));
And on ModeleTable: saveModule:
public function saveAlbum(Album $album)
{
$data = array(
'name' => $album->name,
);
if (isset($album->password)){
$data['password'] = $album->password;
}

Yii framework: access rules not working properly

I have problem with access control. I have rule:
array('deny',
'actions'=>array('index'),
'expression'=>'Yii::app()->user->isRegistered()',
'deniedCallback' => array(
$this->render('//site/info',array(
'message'=>'You must activate your account.'
)
),Yii::app()->end()),
),
function:
public function isRegistered()
{
return (Yii::app()->user->isGuest) ? FALSE : $this->level == 1;
}
If I login as admin and i have level 3, isRegistered() return false, but deniedCalback runs.
How to change this to run callback only when expression is true?
You need to specify the callback as callable. The way you wrote it, it will always execute the code you have in that array. You should better write a dedicated method in the controller.
array('deny',
'actions'=>array('index'),
'expression'=>'Yii::app()->user->isRegistered()',
'deniedCallback' => array($this, 'accessDenied'),
),
// ...
public function accessDenied()
{
$this->render('//site/info', array(
'message' => 'You must activate your account.'
));
Yii::app()->end(); // not really neccessary
}

How to use form validation in Drupal 7

I am trying to edit the checkout form in Drupal Commerce, to require a user to enter their email address twice. When they submit their form, Drupal should check to see if the emails match, and call form_set_error() if they don't. For now, I am just trying to attach a custom validation function to the form, which I can't get to work. (My module is called checkout_confirm_email. This module is only for our own use, so I didn't put much effort into the name).
function checkout_confirm_email_form_alter(&$form, &$form_state, $form_id) {
if($form_id == 'commerce_checkout_form_checkout') {
$form['#validate'][] = 'checkout_confirm_email_form_validate';
dprint_r($form['#validate']);
dsm("I printed");
}
}
function checkout_confirm_email_form_validate($form, &$form_state) {
dsm("Never prints...");
}
The dprint_r statment outputs Array ([0] => checkout_confirm_email_form_validate). So the function is part of the form array, but the dsm statement in the validation function never prints.
I've actually been stuck for a while. I've looked up examples, and I can't see what I'm doing wrong. Anyone?
You need to attach the #validate property to the form submit button like this:
$form['submit']['#validate'][] = 'checkout_confirm_email_form_validate'
And it'll work then it's not necessary that my example is identical match to your form tree you should search for the submit button array and apply this example to it
Instead of form_set_error() I would use form_error($form, t('Error message.'));
function checkout_confirm_email_form_alter(&$form, &$form_state, $form_id) {
if($form_id == 'commerce_checkout_form_checkout') {
$form['#validate'][] = 'checkout_confirm_email_form_validate';
dpm($form['#validate']);
dsm("I printed");
}
}
function checkout_confirm_email_form_validate(&$form, &$form_state) {
// Not sure the exact email field
if(empty($form['submitted']['mail']['#value'])){
dsm("Should see me now and return to the form for re-submission.");
form_error($form, t('Username or email address already in use.'));
}
}
You could use any validate function here
https://api.drupal.org/api/drupal/includes!form.inc/7
The listed validations would be
date_validate - Validates the date type to prevent invalid dates
(e.g., February 30, 2006).
element_validate_integer -Form element validation handler for
integer elements.
element_validate_integer_positive - Form element validation handler
for integer elements that must be positive
element_validate_number - Form element validation handler for
number elements.
password_confirm_validate - Validates a password_confirm element.
Ex of usage
$form['my_number_field'] = array(
'#type' => 'textfield',
'#title' => t('Number'),
'#default_value' => 0,
'#size' => 20,
'#maxlength' => 128,
'#required' => TRUE,
'#element_validate' => array('element_validate_number')
);
You can use _form_validate().
function my_form_form_validate($form, &$form_state) {
if ((valid_email_address($form_state['values']['field_candid_email'])) === FALSE) {
form_set_error('field_candid_email', t('The email address is not valid.'));
}
if (!(is_numeric($form_state ['values']['field_candid_montant']))) {
form_set_error('field_candid_montant', t('The field value must be numeric.'));
}
}
I changed this line:
$form['submit']['#validate'][] = 'checkout_confirm_email_form_validate'
to this:
$form['actions']['submit']['#validate'][] = 'checkout_confirm_email_form_validate';
And it's works !
Use the following code:
$form['submit']['#validate'][] = 'checkout_confirm_email_form_validate'

How to add validators on the fly in Symfony2?

I've a password form field (not mapped to User password) to be used in a change password form, along with two other (mapped) fields, first and last.
I've to add validators on the fly: if value for password is blank then no validation should occur. Otherwise a new MinLength and MaxLength validators should be added.
Here is what i've done so far: create the repeated password field, add a CallbackValidator and return if $form->getData() is null.
Then, how can i add validators for minimum and maximum length to $field?
$builder = $this->createFormBuilder($user);
$field = $builder->create('new_password', 'repeated', array(
'type' => 'password',
'first_name' => 'Password',
'second_name' => 'Confirm password',
'required' => false,
'property_path' => false // Not mapped to the entity password
));
// Add a callback validator the the password field
$field->addValidator(new Form\CallbackValidator(function($form) {
$data = $form->getData();
if(is_null($data)) return; // Field is blank
// Here password is provided and match confirm, check min = 3 max = 10
}));
// Add fields to the form
$form = $builder
->add('first', 'text', array('required' => false)) // Mapped
->add('last', 'text', array('required' => false)) // Mapped
->add($field) // Not mapped
->getForm();
Oh well, found a solution myself after a few experiments.
I'm going to leave this question unanswered for a couple of days as one can post a better solution, that would be really really welcome :)
In particular, i found the new FormError part redundat, don't know if there is a better way to add the error to the form. And honestly, don't know why new Form\CallbackValidator works while new CallbackValidator won't.
So, don't forget to add use statements like these:
use Symfony\Component\Form as Form, // Mendatory
Symfony\Component\Form\FormInterface,
Symfony\Component\Validator\Constraints\MinLength,
Symfony\Component\Validator\Constraints\MinLengthValidator;
And the callback is:
$validation = function(FormInterface $form) {
// If $data is null then the field was blank, do nothing more
if(is_null($data = $form->getData())) return;
// Create a new MinLengthValidator
$validator = new MinLengthValidator();
// If $data is invalid against the MinLength constraint add the error
if(!$validator->isValid($data, new MinLength(array('limit' => 3)))) :
$template = $validator->getMessageTemplate(); // Default error msg
$parameters = $validator->getMessageParameters(); // Default parameters
// Add the error to the form (to the field "password")
$form->addError(new Form\FormError($template, $parameters));
endif;
};
Well, and this is the part i can't understand (why i'm forced to prefix with Form), but it's fine:
$builder->get('password')->addValidator(new Form\CallbackValidator($validation));
addValidator was deprecated and completly removed since Symfony 2.3.
You can do that by listening to the POST_SUBMIT event
$builder->addEventListener(FormEvents::POST_SUBMIT, function ($event) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
if ("Your logic here") {
$form->get('new_password')->addError(new FormError());
}
});