Validation problems with multiple checkboxes (HABTM) on CakePHP form - cakephp

Short version
I have some HABTM checkboxes on a form. Validation is working correctly (at least one checkbox needs to be checked for validation to pass) but the CakePHP error message divs aren't being generated as they should be.
Long Version
I have a from which allows users to fill in their name and email address and then choose from a list of brochures (checkboxes) they'd like to receive.
The form looks like this:
<?php
echo $this->Form->create('Request',array('action' => 'index'));
echo $this->Form->input('id');
echo $this->Form->input('name');
echo $this->Form->input('email');
echo $this->Form->input('Brochure',array(
'label' => __('Information Required:',true),
'type' => 'select',
'multiple' => 'checkbox',
'options' => $list,
'selected' => $this->Html->value('Brochure.Brochure'),
));
echo $this->Form->submit('Submit');
echo $this->Form->end();
?>
In my controller, $list is set as like this:
$this->Request->Brochure->find('list',array('fields'=>array('id','name')));
After reading the 2nd answer (posted by user448164) in HABTM form validation in CakePHP on Stack Overflow, I set my Request model up like this:
<?php
class Request extends AppModel {
var $name = 'Request';
function beforeValidate() {
foreach($this->hasAndBelongsToMany as $k=>$v) {
if(isset($this->data[$k][$k]))
{
$this->data[$this->alias][$k] = $this->data[$k][$k];
}
}
}
var $validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' => 'Please enter your full name'
),
'email' => array(
'rule' => 'email',
'message' => 'Please enter a valid email address'
),
'Brochure' => array(
'rule' => array('multiple', array('min' => 1)),
'message' => 'Please select 1'
),
);
?>
This actually works 99% well. If none of the checkboxes are checked, validation fails as it should do. However, the only problem is that Cake isn't setting the "error" class on the <div>, nor is it creating the <div class="error-message">Please select 1</div> as it should.
For name and email, there is no problem - the error divs are being created properly.
So, to clarify, validation is working for my HABTM checkboxes. The only problem is that the error divs aren't being generated.

I'm posting this here as this is actually a much better question than the related question you found.
I was banging my head against a wall trying to handle the same problem of getting the validation error to show up in the page. I'm using CakePHP v1.2 and I hit a similar problem although I have actually split out the HABTM into the individual tables i.e. Request->BrochuesRequest->Brochure. This is because I can't have it deleting and re-adding the joining table rows at will.
Firstly I think the accepted answer from your linked question assumes that you are doing a save / saveAll when the beforeValidate call is triggered, however I was doing it through a validates call. The difference is that you need to call the Request->set method first. It was an article by Jonathan Snook on Multiple Validation Sets pointed me to that issue.
The second issue is actually getting the error message to appear was down to the $field value you use when calling invalidate. For ages I was including the model as well as the field assuming that this was how it matched the invalidate call to the input, i.e. you have $form->input('BrochuresRequest.brochures_id') so you need $this->BrochuresRequest->invalidate('BrochuresRequest.brochures_id').
However that is wrong you just want $this->BrochuresRequest->invalidate('brochures_id').
<?php
// requests/add view
echo $form->input('BrochuresRequest.brochures_id', array('multiple' => true));
// requests_controller
function add() {
if (!empty($this->data)) {
$this->Request->create();
// critical set to have $this->data
// for beforeValidate when calling validates
$this->Request->set($this->data);
if ($this->Request->validates()) {
$this->Request->saveAll($this->data);
}
}
}
// request model
function beforeValidate() {
if (count($this->data['BrochuresRequest']['brochures_id']) < 1) {
$this->invalidate('non_existent_field'); // fake validation error on Project
// must be brochures_id and not BrochuresRequest.brochures_id
$this->BrochuresRequest->invalidate('brochures_id', 'Please select 1');
return false;
}
return true;
}
?>
A few of the other things that I picked up on the way through:
You don't need a separate $form->error in the view
I couldn't for the life of me get the 'multiple' validation rule to work in the model
The accepted answer checks for an isset but I believe that this isn't required and masked the problem of there being no $this->data being passed through.
The beforeValidate should return false if you want it to prevent any save action.

Related

how to give CAKEPHP validation?

If condition is true it should show an error message "already exits" or else a message "successful" should be displayed.
Is it possible to add a validation like this to the model part:
$name = $_POST["name"];
$validation_sql = "SELECT COUNT(*) > 0 FROM college WHERE status='2' AND name='$name'";
You can use hasAny() as the solution:
$conditions = array(
'status'=>'2',
'name'=>$name
);
if ($this->XXXXXXX->hasAny($conditions)){
//do something
}
hasAny will return true if found else false.
NOTE: hasAny is not available in version 3.x
You can add server validation in model like:
public $validate = array(
'name' => array(
'rule' => array('isUnique', array('name'), false),
'message' => 'This name has already been used.'
)
);
It is not recommended to use $_POST in CakePHP at all, rather use the Request Object in the controller to access the data given by a POST request:
$this->request->data['College']['name'];
This information can then be passed to the model where it is validated.
If the post request has been created by the CakePHP form helper you don't need to access it - you can directly pass the data to the save method of the model instance (see CakePHP Handbook - Saving your data).
if ($this->College->save($this->request->data)) {
// handle the success (Normally success flash)
}
debug($this->College->validationErrors); //Normally error flash - if FormHelper is used the error messages are automatically shown beside the input elements
The validations can be added with the Bake Console or manually by adding validation rules to the College Model code:
public $validate = array(
'name' => array(
'rule' => 'isUnique',
'message' => 'This username has already been taken.'
)
);

Validating non-model select box CakePHP

In the following from a have a select box which is not related to any database fields:
echo $this->Form->create('Event');
echo $this->Form->input('customer_id', array('label' => __('Customer')));
echo $this->Form->input('service_id', array('label'=>__('Service')));
echo $this->Form->select('user_id', $users, array('multiple'=>true));
echo $this->Form->end(__('Save'));
In the Event model I have a validation rule which is as follows:
var $validate = array(
'user_id'=>array(
'notempty'=> array(
'rule'=>'notEmpty',
'message'=> 'Vælg en medarbejder'
)
))
THe problem is that this validation rule is never called. What may the problem be?
Here is the dump of data after from submition:
array(
'customer_id' => '107',
'service_id' => '195',
'user_id' => '',
...
)
The rule has just been tested on edit and it works perfectly, together with the remaining rules. The problem is that none of the rules are fired on create.
You need to validates manually. Try this in your Controller.
$this->Event->set($this->request->data);
if ($this->Event->validates()) {
//Save your data by calling $this->Event->save($this->request->data);
} else {
// didn’t validate logic
$errors = $this->ModelName->validationErrors;
}
Validation doesn't apply on select(), hidden(), radio() etc. You should always go through Form::input() like:
$this->Form->input(
'user_id',
array(
'type'=>'select',
'options'=>$users,
'multiple'=>true, ....));
Then the validation will be called as expected ;)

Cakephp 2.3.9 custom message in model validation not working

I am doing model validation in my admin panel login so there is only two fields username and password. Validation is working but custom message which I have written in my model is not shown.
Model
public $validate = array(
'username' => array(
'required' => array(
'rule' => array('notEmpty'),
'message' => 'Please Enter Your Username'
)
),
'password' => array(
'required' => array(
'rule' => array ('notEmpty'),
'message' => 'Please Enter Your Password'
)
)
);
Controller
function login(){
$this->layout = 'admin_login';
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
}
$this->Session->setFlash(__('Invalid username or password, try again'));
}
}
View
echo $this->Form->create('Admin',array('autocomplete'=>"off"));
echo '<div style="width:294px;float:left;position:relative;">';
echo $this->Form->input('username' , array('label' => '', 'placeholder' =>'Enter your username','div' => false));
echo $this->Form->input('password' , array('label' => '', 'value' =>'', 'div' => false,'placeholder'=>'Enter Your Password'));
echo '</div>';
echo '<div style="padding-left:0px;">';
echo $this->Form->end(__('Login' ,true));
I have already tried a few things like which is mentioned in this link, but it's not working for me.
CakePHP : Validation message not displaying
That looks like a message from the browser and not CakePHP.
CakePHP now adds a required attribute which modern browsers can use to trigger an error.
You can do one of three things here:
One: Set up your form to leave validation to the server:
$this->Form->create(array('novalidate'=>true));
Two: Set a custom validation message in the browser: http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#dom-cva-setcustomvalidity
Three: tolerate it
You get that message because the "username" field is flagged as "required". Maybe you've not defined it in the Form->input() function, but the "required" flag has been automatically added from the Model (due to your validation rules). As timstermatic said, it's a browser validation message caused by the required attribute.
To solve this issue (and show the CakePHP validation message) you've to force for avoiding the addition of the "required" flag on your field:
$this->Form->input('username', array('required' => FALSE));
This will override the Model automatic additions. Happy coding ;)
*Edited => It's important to clarify that the inline override removes only the required flag on the field: you'll take advantage of the Model validation anyway (just because if an empty field is sent to the server, it will not pass the validation rule you entered.
keep this code it will bypass the html5 validation and add your custom validations
view
echo $this->form->create('Post',array('action'=>'add'));
echo $this->form->input('title');
echo $this->form->input('body');
echo $this->form->submit('Save Post',array('formnovalidate'=>true));
echo $this->form->end();//Creates ending form tag
Model
var $validate=array(
'title'=>array(
'title_must_not_be_empty'=>array('rule'=>'notEmpty','message'=>'Please enter a title),
'title_must_be_unique'=>array('rule'=>'isUnique','message'=>'Title name already exists')
),
'body'=>array(
'body_must_not_be_empty'=>array(
'rule'=>'notEmpty',
'message'=>'Please enter body'
)
)
);
This will work just the way you want

Using HtmlHelper on Model to insert links in returned errors

I'm working with CakePHP and trying to understand the best ways to make my application consistent and logical.
Now I'm trying to working with Model data validation and handling validation errors in the view, I have a doubt on how should I do if I like to insert some link inside the returned error, for example for a forgotten password.
Is it good to use (if it's possibile) HtmlHelper inside the Model to return consistent links inside my application, or should I think about another way?
<?php
App::import('Helper', 'Html');
class User extends AppModel {
var $name = 'User';
var $validate = array (
'email' => array (
'checkEmail' => array (
'rule' => array('email', true),
'message' => 'Email not valid message.'
),
'checkUnique' => array (
'rule' => 'isUnique',
'message' => 'This email is allready in the db, if you forgot the password, '.(string)$this->Html->link('click here', array('controller' => 'users', 'action' => 'password-recover')).'.'
)
)
// the rest of the code...
This doesn't work because it seems I can't chain the message string with HTML string.
Does exist e smartest way to do that, or should I simply insert the html string without the HtmlHelper?
If you really want HTML in your validation messages CakePHP provides a way to do this, no breaking Cake, no writing a lot of code.
In your $validation just use whatever HTML you would like to have presented to the user.
In your view when you create your FormHelper::input($fieldName, array $options) pass the following array to $options:
$options = array('error' => array(
'attributes' => array('escape' => false)
))
See this page to learn more about the $options['error'] ...options.
Alternatively, if you want all inputs with no HTML escaping you can pass $options['inputDefaults'] when you create the form.
this is a difficult topic because
you might need to break MVC
validation is as in your case usually in $validate and cannot contain dynamic stuff
for 1)
you can also use Router::url() with manual HTML
you can use BBcode or pseudo-markup and translate this into real links in the view/element of the flashmessage
for 2)
use __construct() and $this->validate to use dynamic elements if needed
In PHP, properties of a class (such as $validate) have to be initialized with constant values.
<?php
class User extends AppModel {
public $validate = array(
'email' => array(
'checkUnique' => array(
'rule' => array('isUnique'),
'message' => 'This email address has already been claimed, possibly by you. If this is your email address, use the reset password facility to regain access to your account'
),
),
);
public function beforeValidate($options = array()) {
$this->validate['email']['checkUnique']['message'] = String::insert(
$this->validate['email']['checkUnique']['message'],
array('link' => Router::url(array('action' => 'password-recover')))
);
return true;
}
You are making it hard on yourself. The helpers are not accessible in the model and controller. And for good reason: the M and C shouldn't be concerned with the V.
There are ways to do exactly as you want (but involves considerably more code). Since you ask for the smartest way: What's wrong with just echo the reset password link in the view, after the login form? Just echo 'Forgot your password? '.$this->Html->link('Click here', array('controller' => 'users', 'action' => 'password-recover'));
I don't agree on breaking the MVC logic. I also tried all the array('escape' => false) possible ways (in Form->input, in Form->error and even in the model) and none of them worked with me! (cakephp 2.0)
"Anh Pham" answer is the easiest and simplest way. In addition to that, I returned empty error message from model validation ('errorMessage' => false ; doesn't work in cakePhp 2.0).
Because I wanted to pass a variable to the view to build the link there (MVC), in the controller I check if the field is invalidated:
$invlaidFields = array_keys($this->Model->validationErrors();
if ( in_array('myField', $invalidFields) ){
...
}
In the view, I check if the field was invalidated, I then echo my error message giving it class error-message so it looks the same as the rest error messages.
if ($this->Form->('myFields')) { ... echo '<span class="error-message">error message'. $this->Html->link(...).'</span>'; }
Hope it helps somebody out there.
P.S. It's always a good practice to mention what cakePHP version you are using...
To cakephp2 you can use the following:
//model validation
'company' => array('notempty' => array('rule' => array('notempty'),'message' => "select one company o send email to contact",),)
//front
<?php if ($this->Form->isFieldError('Register.company')): ?>
<span class="text-danger"><?php echo $this->Form->error('Register.company', null, array('escape'=>false)); ?></span>
<?php endif; ?>

Display custom validation messages using CakePHP $validate array

I'm trying to display custom messages like, 'this field should not be empty' or 'name not null' using the $validate array in the model. I have two controllers, main and users.
The index file of the main controller has the login and registration views. The action part of the login and register functions are in the user_controller. If the login and register function validate, they are redirected to the home page of the main controller,else they remain in the index page itself.
I want the validation messages to be displayed in the index page itself. But those messages appear only if there is a separate view file for login and register,i.e, /views/forms/register.ctp and /views/forms/login.ctp exist.
Is there a way to display those validation messages without having a separate view file for those functions? I have given my code below.Someone guide me please.
Model Class:
<?php
class User extends AppModel {
var $name = 'User';
var $components=array('Auth');
var $validate = array(
'name' => array(
'rule' => 'notEmpty',
'message' =>'Name cannot be null.'
),
'password' => array(
'rule' => 'notEmpty'
),
'email_id' => array(
'rule' => 'notEmpty'
)
);
function registerUser($data)
{
if (!empty($data))
{
$this->data['User']['name']=$data['User']['name'];
$this->data['User']['email_id']=$data['User']['email_id'];
$this->data['User']['password']=$data['User']['password'];
$existingUsers= $this->find('all');
foreach($existingUsers as $existingUser):
if($this->data['User']['email_id']==$existingUser['User']['email_id']){
return 0;
}
else{
$this->save($this->data);
$this->data['User']['id']= $this->find('all',array('fields' => array('User.id'),
'order' => 'User.id DESC'
));
$userId=$this->data['User']['id'][0]['User']['id'];
return $userId;
}
endforeach;
}
}
function loginUser($data)
{
$this->data['User']['email_id']=$data['User']['email_id'];
$this->data['User']['password']=$data['User']['password'];
$login=$this->find('all');
foreach($login as $form):
if($this->data['User']['email_id']==$form['User']['email_id'] && $this->data['User']['password']==$form['User']['password'])
{
$this->data['User']['id']= $this->find('all',array('fields' => array('User.id'),
'conditions'=>array('User.email_id'=> $this->data['User']['email_id'],'User.password'=>$this->data['User']['password'])
));
$userId=$this->data['User']['id'][0]['User']['id'];
return $userId;
}
endforeach;
}
}
?>
Controller Class:
<?php
class UsersController extends AppController
{
var $name = 'Users';
var $uses=array('Form','User','Attribute','Result');
var $helpers=array('Html','Ajax','Javascript','Form');
function register()
{
$this->Session->write('userId',$this->User->registerUser($this->data));
$this->User->data=$this->data;
if (!$this->User->validates())
{
$this->Session->setFlash('Please enter valid inputs');
$this->redirect('/main' );
return;
}
if($this->Session->read('userId')==0){
$this->Session->setFlash('You are already a registerd member.Log in your account');
$this->redirect('/main');
}
else{
$this->Session->setFlash('User account created');
$this->redirect('/main/home');
}
}
function login()
{
//$userId=$this->User->loginUser($this->data);
$this->Session->write('userId',$this->User->loginUser($this->data));
$this->User->data=$this->data;
if (!$this->User->validates())
{
$this->Session->setFlash('Please enter valid inputs');
$this->redirect('/main' );
return;
}
if($this->Session->read('userId')>0){
$this->Session->setFlash('Login Successful');
$this->redirect('/main/home');
break;
}
else{
$this->Session->setFlash('Username and password do not match.');
$this->redirect('/main');
}
}
}
?>
View Template:
<!-- File: /views/main/index.ctp-->
<div id="register">
<h3>Register</h3>
<?php
echo $form->create('User',array('action'=>'register'));
echo $form->input('name');
echo $form->input('email_id');
echo $form->input('password');
echo $form->end('Register');
?>
</div>
<div id="login">
<h3>Login</h3>
<?php
echo $form->create('User',array('action'=>'login'));
echo $form->input('email_id');
echo $form->input('password');
echo $form->end('Login');
?>
</div>
I think you're going about it the wrong way. You're doing way too much in the model, and you're also doing almost the same thing in the controller again after the fact. That's not good. Overall, honestly, the code is quite a mess for something so simple.
A huge WTF flag pops up here:
$existingUsers= $this->find('all');
foreach($existingUsers as $existingUser):
if($this->data['User']['email_id']==$existingUser['User']['email_id']){
You're seriously retrieving all users from the database (potentially a hugely expensive task) and then go through them one by one to compare a single field?!
You can simply define a validation rule that says 'email_id' should be unique, and Cake will automatically ask the database if the 'email_id' already exists. http://book.cakephp.org/view/472/isUnique
About your specific problem: You have the same form field twice on the same page, password and email_id fields for the same User model. There's no way for Cake to know which instance of the two fields is supposed to get the error message, they both have the same name. Also, I don't think you want to use validation error messages for the login form, you just want to see if the login was successful or not. Use Session::flash() instead to display an error message for a failed login, it's not field specific.
Take the login and register methods out of your model, they don't belong there. Only specify proper validation rules for the email, name and password fields in the model, they will automatically be checked upon calling $this->User->save() in the controller.
Don't hand-validate anything, unless there's really no way to do it with Cake validation rules (not the case here). If the built-in validation rules don't satisfy what you need to do, you can even make custom rules. http://book.cakephp.org/view/150/Custom-Validation-Rules
PS: Components are not for models. I think you need to learn more about the basics of Cake before continuing: http://book.cakephp.org/view/218/Tutorials-Examples
But, if you want to see your error messages that comes from the validate array you should access the $this->modelName->invalidFields() which will return you the fields that didn't pass the validation and the message that you have setted for them...

Resources