Laravel array validation: use field index in error message - laravel

I'm trying to validate a field array, and I'd like to point out which field is wrong in the validation errors.
I have a form to upload multiple images. For each image, there must be a caption and the alt attribute for HTML. If I try to upload 3 images and miss the fields for two of them, I'll get an error message like the following:
The field 'image file' is required.
The field 'image caption' is required.
The field 'image alt' is required.
The field 'image caption' is required.
The field 'image alt' is required.
The field 'image file' is required.
The problem is that the :attribute is repeated and if the user wants to update multiple images he/she will have to check all of them to find which field is missing!
What I want is this:
The field 'image file (item 1)' is required.
The field 'image caption (item 1)' is required.
The field 'image alt (item 1)' is required.
The field 'image caption (item 3)' is required.
The field 'image alt (item 3)' is required.
The field 'image file (item 1)' is required.
So the user can know where to fix the problem.
First, I tried this:
$attributes = [
'img.*.file' => 'Image file (item :* )',
'img.*.alt' => 'Image alt (item :* )',
'img.*.caption' => 'Image caption (item :* )',
];
//
$this->validate($request, $rules, [], $attributes);
I supposed that the :* would be replaced by the index of the field (1, 2, 3, 4, etc) as the same way :attribute is replaced by the attribute. However, the :* is not replaced by the index of the fields; instead, it is displayed as plain text.
It worths to note that I designed the code in such way that the HTML name attribute is indexed sequentially for all fields (img[1][alt], [img][2][alt], etc, img[1][caption], [img][2][caption], etc), so each field has the right index. Having that in mind, I suppose there is a way to get the index and use to create custom attributes in the error messages.
I searched for this problem and found the same question here Validation on fields using index position, but it uses angular, not laravel.
How can I get the index and put it in the attribute?If that is not possible, is there any other way to accomplish the desirable result without having to set up the error messages?
I would like to change the attributes and keep the default error messages that laravel provides

Try this example
$input = Request::all();
$rules = array(
'name' => 'required',
'location' => 'required',
'capacity' => 'required',
'description' => 'required',
'image' => 'required|array'
);
$validator = Validator::make($input, $rules);
if ($validator->fails()) {
$messages = $validator->messages();
return Redirect::to('venue-add')
->withErrors($messages);
}
$imageRules = array(
'image' => 'image|max:2000'
);
foreach($input['image'] as $image)
{
$image = array('image' => $image);
$imageValidator = Validator::make($image, $imageRules);
if ($imageValidator->fails()) {
$messages = $imageValidator->messages();
return Redirect::to('venue-add')
->withErrors($messages);
}
}

Thanks to the user sss S, I could implement his/her ideas to solve the problem.
Here is the code for the store method. It replaces (item) with (item $i) in the error message, where $i is the index of the field. Therefore the user can know exactly where is the error.
public function store(Request $request)
{
$rules = $this->rules();
$attributes = $this->attributes();
$validator = Validator::make($request->all(), $rules, [], $attributes);
$errors = [];
if ($validator->fails()) {
$errors = $validator->errors()->all();
}
$imgRules = [
'file' => 'required|image|mimes:jpeg,jpg,webp,png|max:1999',
'alt' => 'required|string|max:100|min:5',
'caption' => 'required|string|max:100|min:5',
];
$imgAttributes = [
'alt' => 'HTML alt attribute (item)',
'caption' => 'Caption(item)',
'file' => 'image file (item)',
];
foreach($request->img as $i => $img) {
$imgValidator = Validator::make($img, $imgRules, [], $imgAttributes);
$imgErrors = [];
if($imgValidator->fails()) {
$imgErrors = $imgValidator->errors()->all();
}
foreach($imgErrors as $k => $v) {
$imgErrors[$k] = str_replace('(item)', "(item $i)", $v);
}
$errors = array_merge($errors, $imgErrors);
}
if(count($errors) > 0) {
return response()->json(['success' => false, 'errors' => $errors]);
}
// here is the code store the new resource
// ...
// then return success message
}

Related

CakePHP 3.0 throwing 'field required' error on non-empty field

I'm having difficulty with my CakePHP 3.0 site throwing an error when it shouldn't. I'm building an "Add Page" form, shown below:
echo $this->Form->create($newpage);
echo $this->Form->control('title');
echo $this->Form->control('content');
echo $this->Form->button('Save');
echo $this->Form->end();
The title and content are required. Once the form is submitted, 'title' is used to generate a page 'name', which is currently just the title in lowercase and with spaces removed (so "About Us" would have the name "aboutus"). This name is also saved, but must be unique (eg if you had pages with titles "No Space" and "NOSpace" they would both end up with the name "nospace" even though the titles are unique).
I have the following validation rules in PagesTable.php:
public function validationDefault(Validator $validator)
{
$validator = new Validator();
$validator
->requirePresence('title','content')
->lengthBetween('title', [0, 50])
->add(
'name',
['unique' => [
'rule' => 'validateUnique',
'provider' => 'table',
'message' => 'Not unique']
]
);
return $validator;
}
The form is submitted to the controller, which contains this:
public function add($mainpage_id = null) {
$newpage = $this->Pages->newEntity();
if ($this->request->is('post')) {
$newpage = $this->Pages->patchEntity($newpage, $this->request->data);
// Get page name by removing spaces from title
$name = strtolower(str_replace(' \'\"', '', $newpage->title));
// Get navigation order by getting number of current siblings, and adding 1
$siblings = $this->Pages->find('all')
->where(['parent_id' => $newpage->parent_id])
->count();
$nav_order = $siblings + 1;
$newpage = $this->Pages->patchEntity($newpage, ['name' => $name, 'nav_order' => $nav_order]);
if ($newpage->errors()) {
$errors = $newpage->errors();
if(isset($errors['name'])) {
$this->Flash->error('The title you have entered is too similar to one that already exists. To avoid confusion, please choose a different title.');
}else {
$this->Flash->error('Please correct errors below');
}
}else {
$this->Pages->save($newpage);
$page_id = $newpage->id;
$this->Flash->success('Page created');
return $this->redirect('/admin/pages/index');
exit;
}
}
$this->set(compact('newpage'));
$this->set('_serialize', ['newpage']);
}
However, when I try to submit a page, I get "This field is required" on the title field even if I've entered a title. In fact, it won't let me submit the form without entering a title (a message pops up saying "Fill out this field"), but then it throws the error.
Can anyone see what I'm doing wrong?
Codebase looks good. However, this might be not obvious behavior: calling patch entity method validate the passed patch data.
So the first time your patch is validated here:
$newpage = $this->Pages->patchEntity($newpage, $this->request->data);
and second time it's validated here:
$newpage = $this->Pages->patchEntity($newpage, ['name' => $name, 'nav_order' => $nav_order]);
and as second patch have no title, then validator said that it missed. To fix this you need to run patch once with all the data, i.e.:
$data = $this->request->data();
// Get page name by removing spaces from title
$data['name'] = strtolower(str_replace(' \'\"', '', $data['title']));
// Get navigation order by getting number of current siblings, and adding 1
$siblings = $this->Pages->find('all')
->where(['parent_id' => $data['parent_id']])
->count();
$data['nav_order'] = $siblings + 1;
$newpage = $this->Pages->patchEntity($newpage, $data);
Hope you understand the idea, and it'll help.
UPDATE: just noticed that there might be no parent_id in request... You can move this logic into afterSave callback in PagesTabe:
public function afterSave(Event $event, EntityInterface $entity) {
$siblings = $this->count()
->where(['parend_id '=> $entity->parent_id]);
// updateAll won't trigger the same hook again
$this->updateAll(
['nav_order' => ++$siblings],
['id' => $entity->id]
);
}

How to exclude a perticular field from unique validation in edit mode in cakephp3.0 validation

I want to validate a field called survey_id which is an input from user for uniqueness. It is working properly and giving the correct response when adding the new record, but when I tried to edit this record it is giving an error [unique] => Provided value already exist. So what I want is to exclude the survey_id of the current record from uniqueness check and if user input some other value for survey_id it should check for uniqueness search.
Currently I am using the CakePHP 3.0 validation with on create validation. Here is the validation rule that I am using:
validator
->requirePresence('survey_id', __('msg_required'))
->notEmpty('survey_id', __('msg_required'))
->maxlength('survey_id', 32, __('msg_maxlength'))
->add('survey_id', 'unique', ['rule' => ['validateUnique',['id']], 'provider' => 'table', 'message' => 'Provided value already exist', 'on'=>'create']);
return $validator;
Is there anything wrong with this code?
Thanks in advance.
`
It will work with this validation rule
$validator
->requirePresence('survey_id', __('msg_required'))
->notEmpty('survey_id', __('msg_required'))
->maxlength('survey_id', 32, __('msg_maxlength'))
->alphaNumeric('survey_id', __('msg_surveyid_format'))
->add('survey_id', 'custom', [
'rule' => function ($value, $context) {
if (!empty($context['data']['projectId'])) { $values = array($context['data']['projectId']); } else { $values = array(); }
$data = $this->getSurveyId($value, $values);
return (!empty($data)) ? false : true;
},
'message' => __('msg_surveyid_exsist')]);
return $validator;
}
public function getSurveyId($surveyId = null, $exclude = null) {
$where = array('p.survey_id' => $surveyId);
if (!empty($exclude) && is_array($exclude)) {
$where[] = array('p.id NOT IN' => $exclude);
}
return $this->db->newQuery()
->select('*')
->from(['p' => 'projects'])
->where($where)
->execute()
->fetch('assoc');
}

How to get array index in validation message Laravel 5.2

These arrays I put into Laravel Validator as arguments:
['item.*' => 'string'] // rules
['item.*.string' => 'Item number (index) is not string'] // messages
I want to have index number in validation message. Code above is just for demonstration and does not work. How to do this?
Try this or use this one
'name' : [ { 'value' : 'raju' } , { 'value' : 'rani'} ]
and validate it by
'name.*' or 'name.*.value' => 'required|string|min:5'
The message will be
'name.*.required' => 'The :attribute is required'
'name.*.value.required' => 'The :attribute is required'
I think it will help to you..
Try this one,
public function messages()
{
$messages = [];
foreach ($this->request->get('name') as $key => $value){
$messages['name.'. $key .'.required'] = 'The item '. $key .' is not string';
}
return $messages;
}

CodeIgniter: validating form array not working

I have an array of profile data I need to validate:
$user_group_profiles = $this->input->post('user_group_profiles', TRUE);
foreach ($user_group_profiles as $key => $user_group_profile)
{
$this->form_validation->set_rules("user_group_profiles[$key][profile_name]", 'Profile Name', 'trim|required');
$this->form_validation->set_rules("user_group_profiles[$key][birthdate]", 'Birthdate', 'trim|required');
// TODO: heigth/weight not required, but the validation somehow makes it required
$this->form_validation->set_rules("user_group_profiles[$key][height]", 'Height', 'trim|greater_than[0]');
$this->form_validation->set_rules("user_group_profiles[$key][weight]", 'Weight', 'trim|greater_than[0]');
}
Height and weight are option, but when no value is set for those fields, CI validation complains. A var_dump($user_group_profiles); shows this:
array
'ugp_b33333338' =>
array
'profile_name' => string '' (length=0)
'birthdate' => string '' (length=0)
'height' => string '' (length=0)
'weight' => string '' (length=0)
Any ideas what might be wrong?
EDIT 1:
I went into CI's Form_validation library and made $_field_data and public member. When I var_export it after, I got this:
'user_group_profiles[ugp_833333338][height]' =>
array
'field' => string 'user_group_profiles[ugp_833333338][height]' (length=42)
'label' => string 'Height' (length=6)
'rules' => string 'greater_than[1]' (length=15)
'is_array' => boolean true
'keys' =>
array
0 => string 'user_group_profiles' (length=19)
1 => string 'ugp_833333338' (length=13)
2 => string 'height' (length=6)
'postdata' => string '' (length=0)
'error' => string 'The Height field must contain a number greater than 1.' (length=54)
Ok - I've just spent an hour on this - and I've worked out the problem + solution
The issue is that when the variable you are testing is part of an array, CI interprets the field as being set, and thus "contains" a value (even when it is empty). Because that "value" is NOT a number greater than 0 - it fails.
Therefore you'll need to unset the $_POST (NOT $user_group_profiles) variable when it is "empty" so that it passes validation. Note - validation is run on $_POST - that is why you are unsetting $_POST and not $user_group_profiles
So the workaround is this:
$user_group_profiles = $this->input->post('user_group_profiles', TRUE);
foreach ($user_group_profiles as $key => $user_group_profile)
{
// Set the rules
$this->form_validation->set_rules($key."[profile_name]", "Profile Name", "trim|required");
$this->form_validation->set_rules($key."[birthdate]", "Birthdate", "trim|required");
$this->form_validation->set_rules($key."[height]", "Height", "trim|greater_than[0]");
$this->form_validation->set_rules($key."[weight]", "Weight", "trim|greater_than[0]");
// Now check if the field is actually empty
if (empty($user_group_profile['height']))
{
// If empty, remove from array so CI validation doesnt get tricked and fail
unset($_POST[$key]['height']);
}
if (empty($user_group_profile['weight']))
{
unset($_POST[$key]['weight']);
}
}
I've tested this - and it fixes your problem.
You could also program it this way if you dont want to touch the $_POST data:
foreach ($user_group_profiles as $key => $user_group_profile)
{
// Set the rules
$this->form_validation->set_rules($key."[profile_name]", "Profile Name", "trim|required");
$this->form_validation->set_rules($key."[birthdate]", "Birthdate", "trim|required");
// Now check if the field is actually empty
if ( ! empty($user_group_profile['height']))
{
$this->form_validation->set_rules($key."[height]", "Height", "trim|greater_than[0]");
}
if ( ! empty($user_group_profile['weight']))
{
$this->form_validation->set_rules($key."[weight]", "Weight", "trim|greater_than[0]");
}
}
Let's try and break this down into parts. I hope I can help you out here.
I'm assuming you're doing the following for the XSS Filtering with the second "TRUE" argument:
$user_group_profiles = $this->input->post('user_group_profiles', TRUE);
You can actually do the XSS filtering with the form validation rules, or if you prefer filter the post after the rules. See here for my preference:
$this->form_validation->set_rules('profile_name', 'Profile Name', 'xss_clean|trim|required');
So with knowing that now, we can follow the CI convention for their Form Validation Library. It's not necessary to grab the post before using the Form Validation Library because it auto-detects I believe the POST data anyway by the field name. For example:
$this->form_validation->set_rules('profile_name', 'Profile Name', 'trim|required|xss_clean');
$this->form_validation->set_rules('birthdate', 'Birthdate', 'trim|required|xss_clean');
$this->form_validation->set_rules('height', 'Height', 'trim|greater_than[0]|numeric');
$this->form_validation->set_rules('weight', 'Weight', 'trim|greater_than[0]|numeric');
if ($this->form_validation->run() == FALSE) {
$this->load->view('my_view_where_this_post_came_from');
} else {
$profile_name = $this->input->post('profile_name');
//or if you prefer the XSS check here, this:
//$profile_name = $this->input->post('profile_name', TRUE);
$birthdate= $this->input->post('birthdate');
$height= $this->input->post('height');
$weight= $this->input->post('weight');
//$user_group_profiles = $this->input->post();
}
I hope this helps!
EDIT: I also just noticed this. If you're trying to grab the entire post array the code is:
$user_group_profiles = $this->input->post(NULL, TRUE); // returns all POST items with XSS filter
$user_group_profiles = $this->input->post(); // returns all POST items without XSS filter
Not:
$user_group_profiles = $this->input->post('user_group_profiles');
A good help if you don't know your $_POST names or are confused, you can do this to see if that data is even there! Like this as your first line:
var_dump($_POST);
exit();

cakephp custom validation does not display error message in the nested rule

im doing a custom validation but it does not display error message when invalidated.
do you know where is the problem? I think the problem might be in the invalidate function. do you know how to set it up for the nested validation like this one?
var $validate = array(
'receiver' => array(
'maxMsg' => array(
'rule' => array('maxMsgSend'),
//'message' => ''
),
'notEmpty' => array(
'rule' => array('notEmpty'),
'message' => 'field must not be left empty'
))......
custom validation method in the model:
function maxMsgSend ( $data )
{
$id = User::$auth['User']['id'];
$count_contacts = (int)$this->Contact->find( 'count', array( 'conditions' =>array( 'and' =>array( 'Contact.contact_status_id' => '2',
'Contact.user_id' => $id))));
$current_credit = (int)$this->field( '3_credit_counter', array( 'id' => $id));
$max_allowed_messages = ($count_contacts >= $current_credit)? $current_credit: $count_contacts ;
if ($data>$max_allowed_messages)
{
$this->invalidate('maxMsg', "you can send maximum of {$max_allowed_messages} text messages.");
}
}
UPDATE: how is solved it.
i moved the the guts of the function to beforeValidate() in the model.
function beforeValidate($data) {
if (isset($this->data['User']['receiver']))
{
$id = User::$auth['User']['id'];
$count_contacts = (int)$this->Contact->find( 'count', array( 'conditions' =>array( 'and' =>array( 'Contact.contact_status_id' => '2',
'Contact.user_id' => $id))));
$current_credit = (int)$this->field( '3_credit_counter', array( 'id' => $id));
$max_allowed_messages = ($count_contacts >= $current_credit)? $current_credit: $count_contacts ;
if ($data>$max_allowed_messages)
{
$this->invalidate('receiver', "you can send maximum of {$max_allowed_messages} text messages.");
return false;
}
}
return true;
}
I think your maxMsgSend function still needs to return false if validation fails.
I think the problem is in your Model::maxMsgSend function. As written in the bakery, (http://bakery.cakephp.org/articles/view/using-equalto-validation-to-compare-two-form-fields), to build a custom validation rule (they want to compare two fields, but the concepts are the same), they write:
I return a false if the values don't match, and a true if they do.
Check out their code for the Model class, about half way down. In short, you don't need to call invalidate from within the custom validation method; you simply return true if it passes validation, and false if it doesn't pass validation.

Resources