CakePHP data change during validation and beforeSave is not being save with the changes - validation

I'm saving data sent from a form.
In the Controller I am doing :
$this->User->create();
$this->User->save($this->request->data)
The $this->request->data looks like this:
'User' => array(
'password' => '*****',
'username' => 'ddddd',
'role' => '256/aa01bdf80d42beb48dd3225acddf447fdd7a39f3',
'parent_id' => '0/b6ba9bd57f6c70cf738891d4c6fac22abed4161d'
)
There are validation rules that works on 'role' and 'parent_id' to insure the role/parent ids are among those the user can access.
The validation changes the field values if the data is valid.
I also have a Tree behavior that is setting some tree fields in a beforeSave() filter in the behavior.
The validation rule is writing the change to $this->data->[$model][$field] as shown below.
public function checkListHash($check, $field) {
$explodedCheck = explode('/', $check[$field]);
if ($this->secureId($explodedCheck[0], $explodedCheck[1])) {
$this->data['User'][$field] = $explodedCheck[0];
return true;
}
return false;
}
The beforeFilter() in the behavior is changing the data array with statements like this:
$Model->data[$Model->alias][$ancestors] = $ancestorList;
When validation and the beforeFilter() processing is complete, I have a beautiful and correct array of data at $this->User->data that looks like this:
'User' => array(
'password' => '*****',
'active' => '0',
'role' => '256',
'parent_id' => '0',
'node' => '0',
'username' => 'ddddd',
'modified' => '2013-09-15 09:55:02',
'created' => '2013-09-15 09:55:02',
'ancestor_list' => ',0,'
)
However, $this->request->data is unchanged. And that is what is being save.
Clearly I'm not understanding the relationship of these various ways to get to the data. I've tried a variety of ways to address the data in the three contexts:
Controller
Model
Behavior
And I've tried $this->User->create($this->request->data); before the Controller save() statement.
In the controller, what I'm seeing as available data arrays:
PRIOR TO THE SAVE
$this->request->data = $this->data = proper data from the form
$this->User->data = some default, unpopulated array
PRIOR TO THE SAVE when I use $this->User->create($this->request->data)
all three arrays contain raw form data
AFTER THE SAVE in either case
$this->request->data = $this->data = exactly as before
$this->User->data = the properly massaged data
Can anyone sort me out?
Don Drake

Just to explain the data arrays to you, when you submit the form, the data from it is stored in $this->request->data on the controller. You are then modifying $this->User->data from inside the model, which is a different array on the model itself. It would not affect $this->request->data because it's a completely different array which belongs to the controller, and the model has no knowledge of it.
You are then saving the User model using the request data, which remains unchanged from when the form was submitted. This is logical and normal behaviour because you're not actually using the $this->User->data array that you've modified.
Your save might always be failing because the data the model is trying to save isn't the updated data, it's just the basic data from $this->request->data.
Try this:
$this->User->set($this->request->data);
$this->User->save();
Also, if you are using a beforeSave in your model, make sure the method returns true, or it will never actually go on to save.

Related

How to validate laravel input only when data is present?

So I have a Laravel form where I'm collecting data, I use same form for creating and updating the table. Problem is that I only want the input fields to be validated when they contain data, this is my validation logic.
$validated = $request->validate([
'site_name' => 'sometimes|string',
'site_email' => 'sometimes|email',
'site_keywords' => 'sometimes|string',
'site_description' => 'sometimes|string',
'site_acronym' => 'sometimes|string|max:7',
'site_favicon' => 'sometimes|mimes:jpg,png,jpeg,svg',
'site_logo' => 'sometimes||mimes:jpg,png,jpeg,svg',
'site_currency' => 'sometimes|string',
'site_currency_symbol' => 'sometimes|string',
]);
But when the form is submitted, laravel returns an error for empty input fields, e.g it returns an error like so "The site name must be a string." for the first validation rule.
What am I doing wrong please?
EDIT
So I took the advice from #roj-vroemen and modified my code by using the nullable validation rule and it works just fine now.
Here's the new code block
$validated = $request->validate([
'site_name' => 'string|nullable|sometimes',
'site_email' => 'email|nullable|sometimes',
'site_keywords' => 'string|nullable|sometimes',
'site_description' => 'string|nullable|sometimes',
'site_acronym' => 'string|nullable|sometimes|max:7',
'site_favicon' => 'mimes:jpg,png,jpeg,svg|nullable|sometimes',
'site_logo' => 'mimes:jpg,png,jpeg,svg|nullable|sometimes',
'site_currency' => 'string|nullable|sometimes',
'site_currency_symbol' => 'string|nullable|sometimes',
]);
You'll want to use nullable in this case. sometimes basically means it only runs the validation for that field when it's present in the data.
In short:
Use nullable when you want to allow null values.
https://laravel.com/docs/8.x/validation#rule-nullable
Use sometimes if your only want to validate the field when it's present in the given input array.
https://laravel.com/docs/8.x/validation#validating-when-present
Note: you might want to make sure to filter out the empty values as the fields containing null will still be there:
$validated = array_filter($validated);

Two fields overwriting each others values on saving in Backpack for Laravel CRUD (spatie/laravel-tags implementation issues)?

I am trying to use spatie/laravel-tags together with Backpack for Laravel. I have 2 types of tags defined. Currently I have extended the Tag model from spatie/laravel-tags as MyCategory and MyTag and added global scopes to separate the two tag types. This works to the extent that it will display the current categories and tags correctly in Backpack, but when I try to save any changes it will only save the entries in the last field, and delete everything in the first field.
Here is my current field configuration for my CRUD:
$this->crud->addField([
'name' => 'categories',
'label' => 'Categories',
'type' => 'select2_multiple',
'tab' => 'Overview',
'attribute' => 'name',
'model' => 'App\MyCategory',
'pivot' => true,
]);
$this->crud->addField([
'name' => 'tags',
'label' => 'Tags',
'type' => 'select2_multiple',
'tab' => 'Overview',
'attribute' => 'name',
'model' => 'App\MyTag',
'pivot' => true,
]);
When I check Laravel Telescope I see that the same thing happens for both fields. First all current tags (regardless of type) for the item I am saving are deleted, and the new tags from the field are added. This is then repeated for the second field, which of course deletes the tags from the first field that should also be kept.
It seems that GlobalScope on my extended Tag models does not stick around for this part. Is there any way to reintroduce the scopes into the queries run by backpack to get these tags to save correctly?
In my CrudController I created custom update and store functions. See the update example below. This seems to work fine. There are still 2 queries being run, with the second one undoing the first one, but for my purposes this is a good enough workaround to be able to have 2 fields with different tag types in the same form in Backpack, using spatie/laravel-tags.
public function update(UpdateRequest $request)
{
$request = request();
// Merge the values from the two tag fields together into the second field
$request->merge(['tags' => array_merge((array)$request->input('categories'), (array)$request->input('tags'))]);
$redirect_location = $this->traitUpdate($request);
return $redirect_location;
}

Unable to retain values of dependent select box in Drupal 7

I have two dependent dropdown one for country and one for state. I am using the concept multiple times in a multistep webform.
When the form loads, the list of country is okay. On selecting a particular country the state list is also okay.
Now comes the issue:
If there is some mandatory field in the same part of webform, and the user does not fill that up the form refreshes and loses the entire list of states. Also, when moving to the next step (Multi step webform) and coming back to the same page the value is lost.
However in the entire process the value of country select list is retained.
The select options generated using ajax are not retained.
Thanks in advance. Below is the code used.
$form['submitted']['employment_history']['employer_1']['address_of_employer']['country']['#ajax'] = array(
'callback' => 'my_custom_ajax_callback_for_employer_one',
'wrapper' => 'edit-submitted-employment-history-employer-1-address-of-employer-state',
'method' => 'replace',
);
/*
* Implements Ajax callback for populating list of provinces (Employer One).
*/
function my_custom_ajax_callback_for_employer_one($from, $form_state) {
$selected_country = $form_state['values']['submitted']['employment_history']['employer_1']['address_of_employer']['country'];
$states = location_get_provinces($selected_country);
$form['submitted']['employment_history']['employer_1']['address_of_employer']['state']= array(
'#type' => 'select',
'#options' => $states,
'#attributes' => array('id' => 'edit-submitted-employment-history-employer-1-address-of-employer-state'),
);
$form['rebuild'] = TRUE;
return $form['submitted']['employment_history']['employer_1']['address_of_employer']['state'];
}
My initial thought is that you should be passing the form and form state by reference ($from, $form_state) should be (&$form, &$form_state) on your ajax callback (as well you have a typo).

Should I save validation $_POST value into a variable or not in CI?

E.g. I have something like this in my controller:
$this->form_validation->set_rules('email', 'Email', 'required|valid_email|xss_clean');
$this->form_validation->set_rules('name', 'Name', 'trim|xss_clean');
Now my question is, if I later in my code do something like this (inside the same method):
$user_profiles = array(
'email' => $_POST['email'],
'name' => $_POST['name']
);
Will, the variables in this array sanitized or not?
I mean will the form_validation->set_rules remain for all the later code usage of the $_POST values or I need to use some other technique?
Codeigniter provides the input class which has a method post that can get the values you require, after they have been sanitised by the form validation:
$user_profiles = array(
'email' => $this->input->post('email'),
'name' => $this->input->post('name')
);
I would more or less recommend that you use $this->input->post() instead of $_POST unless there is a specific reason not to. The docs have some more explanation and uses of the input class: http://ellislab.com/codeigniter/user_guide/libraries/input.html.
I'm not 100% sure but as far as i know, form_validation just tests variables for given arguments, but dosen't clean them up.
I have always used $postdata = $this->input->post(NULL, TRUE); when handling form data.

Yii CGridview - search/sort works, but values aren't being displayed on respective cells

I am semi-frustrated with this Yii CGridView problem and any help or guidance would be highly appreciated.
I have two related tables shops (shop_id primary) and contacts (shop_id foreign) such that a single shop may have multiple contacts. I'm using CGridview for pulling records and sorting and my relation function in shops model is something like:
'shopscontact' => array(self::HAS_MANY, 'Shopsmodel', 'shop_id');
On the shop grid, I need to display the shop row with any one of the available contacts. My attempt to filter, search the Grid has worked pretty fine, but I'm stuck in one very strange problem. The respective grid column does not display the value that is intended.
On CGridview file, I'm doing something like
array(
'name' => 'shopscontact.contact_firstname',
'header' => 'First Name',
'value' => '$data->shopscontact->contact_firstname'
),
to display the contact's first name. However, even under circumstances that searching/sorting are both working (I found out by checking the db associations), the grid column comes out empty! :( And when I do a var_dump
array(
'name' => 'shopscontact.contact_firstname',
'header' => 'First Name',
'value' => 'var_dump($data->shopscontact)'
),
The dump shows record values in _private attributes as follows:
private '_attributes' (CActiveRecord) =>
array
'contact_firstname' => string 'rec1' (length=4)
'contact_lastname' => string 'rec1 lsname' (length=11)
'contact_id' => string '1' (length=1)
< Edit: >
My criteria code in the model is as follows:
$criteria->with = array(
'owner',
'states',
'shopscontacts' => array(
'alias' => 'shopscontacts',
'select' => 'shopscontacts.contact_firstname,shopscontacts.contact_lastname',
'together' => true
)
);
< / Edit >
How do I access the values in their respective columns? Please help! :(
Hmm, I have not used the with() and together() methods much. What's interesting is how in the 'value' part of the column, $data->shopscontacts loads up the relation fresh, based on the relations() definition (and is not based on the criteria you declared).
A cleaner way to handle the array output might be like this:
'value' => 'array_shift($data->shopscontacts)->contact_lastname'
Perhaps a better way to do this, though, would be to set up a new (additional) relation, like this in your shops model:
public function relations()
{
return array(
'shopscontacts' => array(self::HAS_MANY, 'Shopsmodel', 'shop_id'), // original
'firstShopscontact' => array(self::HAS_ONE, 'Shopsmodel', 'shop_id'), // the new relation
);
}
Then, in your CGridView you can just set up a column like so:
'columns'=>array(
'firstShopscontact.contact_lastname',
),
Cheers
Since 'shopscontact' is the name of the has-many relation, $data->shopscontact should be returning an array with all the shops related... did you modify the relation in order to return only one record (if I didn't get you wrong, you only need to display one, right?)? If you did it, may I see your filtering code?
P.S. A hunch to get a fast but temporal solution: have you tried 'value' => '$data->shopscontact['contact_firstname']'?

Resources