Model behavior trying to validate when it shouldn’t be - cakephp

I’ve created a simple model behavior in my CakePHP application to handle file uploads. In its beforeValidate() method I have the following:
public function beforeValidate(Model $Model, $options = array()) {
$maxFileSize = '1MB';
$Model->validator()->add($this->settings[$Model->alias]['field'], array(
'extension' => array(
'rule' => array('extension', $this->settings[$Model->alias]['allowedExtensions']),
'message' => __('Please supply a valid image'),
'allowEmpty' => ($this->settings[$Model->alias]['required'] === false)
),
'fileSize' => array(
'rule' => array('fileSize', '<=', $maxFileSize),
'message' => __('Image must be less than %s', $maxFileSize)
),
'uploadError' => array(
'rule' => 'uploadError',
'message' => __('Something went wrong with the upload')
)
));
return true;
}
I’m dynamically adding file field-related validation. The first rule is checking the extension is in an allowed array of extensions passed in the behavior settings, and I’ve an allowEmpty key that equates to true because a file upload is not mandatory in this case.
This behavior is attached to an Event class. When editing an event, the extension validation rule kicks in, even though I’ve specified allowEmpty as true. Why is this?
Is it because the data passed in the file field is an array and actually equates to non-empty, therefore the validation is kicking in? If so, how can I combat this? I only want validation to kick in if a file has actually been uploaded.

Is it because the data passed in the file field is an array and actually equates to non-empty, therefore the validation is kicking in?
Yes. If no file is uploaded the array key for the model field will still be present, with a none-empty value. I.e. something like:
//$request->data
array(
'Model' => array(
'field' => array(
...
'size' => 0,
'error' => 4
)
)
);
Coping with optional file uploads
To prevent problems validating a field which is a file upload - a simple option is to check for UPLOAD_ERR_NO_FILE and wipe the relevant model data. In a beforeValidate callback that could look like this:
$field = $this->settings[$Model->alias]['field'];
if (
isset($Model->data[$Model->alias][$field]['error']) &&
$Model->data[$Model->alias][$field]['error'] === UPLOAD_ERR_NO_FILE
) {
unset($Model->data[$Model->alias][$field]);
}
Alternatively add a bail-early chunk of code to all validation rules:
function validateExt($Model, ...) {
$field = $this->settings[$Model->alias]['field'];
if ($Model->data[$Model->alias][$field]['error'] === UPLOAD_ERR_NO_FILE) {
return true;
}
...
}
Such that even if called with an empty file upload the validation rules do not return false negatives. Incidentally, validation rule-order matters, it would make more sense to check for an upload error - before validating the contents of the upload =).

Related

CakePHP 3.x - File validators on upload always failing

I am working on an image upload. I am trying to do the image verification in the model (like it should, right?), however I can't get the validation to work.
I put the following in src/Model/Table/CommentTable:
public function validationDefault(Validator $validator)
{
//...
$validator->add('photofile', 'upload', [
'rule' => ['uploadedFile', ['maxSize' => 1048576, 'optional' => true, 'types' => ['image/*']]],
'message' => 'Not a valid file'
]);
//...
}
For testing my controller looks like (I hard-coded an existing file):
$comment = $this->Comments->newEntity([
'body' => $data['body'],
'bin_id' => $bin_id,
'user_id' => $this->Auth->user('id'),
'photofile' => 'C:/Users/Robert/Downloads/test.jpg'
]);
This file is only a couple of bytes, yet after debugging $comment is shows an error in 'photofile':
'[errors]' => [
'photofile' => [
'upload' => 'Not a valid file'
]
],
So why is the validator always failing? Am I using it correctly?
Well, it's not an uploaded file, so that's generally the expected behavior. Also a string isn't supported as a valid value at all, the value must either be a file upload array, or as of CakePHP 3.4, an object that implements \Psr\Http\Message\UploadedFileInterface.
For file upload arrays, the uploadedFile validation rule provided by CakePHP will utilize is_uploaded_file(), which will fail for files that haven't actually been uploaded, and you cannot emulate them.
As of CakePHP 3.4 you can use UploadedFileInterface objects as already mentioned. CakePHP requires \Zend\Diactoros\UploadedFile which provides an implementation that you could utilize for testing purposes, like:
$file = 'C:/Users/Robert/Downloads/test.jpg';
$comment = $this->Comments->newEntity([
'body' => $data['body'],
'bin_id' => $bin_id,
'user_id' => $this->Auth->user('id'),
'photofile' => new \Zend\Diactoros\UploadedFile(
$file,
filesize($file),
\UPLOAD_ERR_OK,
pathinfo($file, \PATHINFO_BASENAME),
'image/jpeg'
)
]);
However while this will pass validation, you won't be able to move that file using UploadedFile::moveTo(), as it uses move_uploaded_file(), which again only works with files that have actually been uploaded.
Also regex validation of MIME types only works when passed as a string (this currently isn't documented), and it must be a valid regex including delimiters, like
'types' => '#^image/.+$#'
Alternatively specify the exact types as an array, ie
'types' => ['image/jpeg', 'image/png', /* ... */]
See also
Zend Diactoros API > UploadedFile
CakePHP API > \Cake\Validation\Validation::uploadedFile()

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.'
)
);

Cakephp SaveAssociated and Save - Using same Model Validation code

Question: How can I use the same code in the model validation (in particular for child models) for both SaveAssociated and Save function calls in CakePHP,... given that SaveAssociated implementations expect the form data array to contain a numeric index [0] for data fields belonging to a child model?
Scenario:
Assuming I have a parent model with a hasMany relationship to several child models.
Typically if you use SaveAssociated to save data to all models at once, you would need to specify an index number (typically 0) on the view form input. Example:
echo $this->Form->input('MerchantControl.0.startdate', array('type' => 'text', 'class' => 'datepicker_start'));
As a result, any custom child model validation code will need to be written with [0] as well. See function urlParamNotUsedByOtherMerchants in the code sample below.
public $validate = array(
'urlparam' => array(
'In Use by other Merchants' => array(
'rule' => 'urlParamNotUsedByOtherMerchants',
'message' => 'URLPARAM belongs to another Merchant'
)
)
);
public function urlParamNotUsedByOtherMerchants($data) {
$searchfilter = array(
//Because of SaveAssociated, need to refer to index [0]
'MerchantControl.id !=' => $this->data['MerchantControl'][0]['merchant_id'],
'MerchantControl.urlparam ' => $data,
);
$merchantcontrol = $this->find('all', array('conditions' => $searchfilter));
if (sizeof($merchantcontrol) > 0) {
return false;
} else {
return true;
}
}
The problem is there are many other instances where I will also be using a "Save" and not a "SaveAssociated" in maintainence views where i directly update or create the child model only. In this case, this model validation code is going to fail with an error saying index "[0]" not defined or something similar.
How can I use the same code in the model validation (in particular for child models) for both SaveAssociated and Save function calls in CakePHP?
If I understand you correctly you want to check whether the urlparam is already used by another merchant or in other words whether it is unique.
Why don't you use the built-in validation rule isUnique?
Example:
public $validate = array(
'urlparam' => array(
'In Use by other Merchants' => array(
'rule' => 'isUnique',
'message' => 'URLPARAM belongs to another Merchant'
)
)
);

Cakephp validating specific field with specific rule without saving the data from controller

I want to validate fields with Cakephp model validation, without saving the data, for which i am using the following code in controller.
$this->Model->set($this->data);
if ($this->Model->validates()) {
......
}
But here i want to validate only some specific field like 'email_field' and one of its rule 'email'. In model i have specified some other rules for 'email_field' like 'unique' and 'notempty' but i don't want to validate those rules.
How can it be achieved ?
The above will work definitely but it's not an elegant solution when cake has already documented how to validate specific fields of a Model.
if ($this->Model->validates(array('fieldList' => array('field1', 'field2')))) {
// valid
} else {
// invalid
}
For more detail see cookbook
you have different options
you can dynamically unset those other rules:
unset($this->Model->validate['field']['someRuleName']);
or you can assign a completely new rule set to this field
or you can use a different "nonexistent" field for this validation, e.g. "some_other_field" with special rules.
......
$this->Registry->validate = array(
'email_field' => array(
'between' => array(
'rule' => array('between', 10, 100),
'message' => 'Your custom message here',
'required' => true,
),
)
);
$this->Model->set($this->data);
if ($this->Model->validates(array('fieldList' => array('email_field')))) {
......
}

Validating made up fields in CakePHP?

Here's my scenario:
I'm creating a password change page.
the real field that holds the password is User.password
On the password create page, I used 3 made up fields:
$form->input('User.old_passwd');
$form->input('User.new_passwd');
$form->input('User.confirm_new_passwd');
How do I validate them with the rules:
old password must match User.password
new_passwd and confirmnew_passwd must
be equal
Are there better solutions for this? I'm open for suggestions. Thanks!
The built-in authentication component doesn't offer that functionality. I would specify the validation rules for your "made up" fields in the validate property of the model and write my own validation methods, for example: correctPassword() to ensure that the users enter their old password and matchingPasswords() to ensure that the new password was re-typed correctly.
var $validate = array(
// your existing validation rules
'old_passwd' => array(
'rule' => 'correctPassword',
'message' => 'invalid password'
),
'new_passwd' => array(
'rule' => array('minLength', 8),
'message' => '8 characters minimum';
),
'confirm_new_passwd' => array(
'rule' => 'matchingPasswords',
'message' => 'passwords do not match'
)
);
function correctPassword($check) { }
function matchingPasswords($check) { }
See the Cookbook for more information about custom validation rules.

Resources