Yii2, custom validation: clientValidateAttribute() doesn't work correctly - php

I have form, created by ActiveForm widget. User enters polish postal code there. In appropriate controller I put entered data in DB, for example:
$company_profile_data->postal_code = $_POST['CompanyProfiles']['postal_code'];
$company_profile_data->update();
I decided to use standalone validator for postal code validation. Rules for this attribute in model:
public function rules() {
return [
//...some other rules...
['postal_code', 'string', 'length' => [6,6]],
['postal_code', PostalValidator::className()], //standalone validator
];
}
app/components/validators/PostalValidator class code:
namespace app\components\validators;
use yii\validators\Validator;
use app\models\CompanyProfiles;
use app\models\Users;
class PostalValidator extends Validator {
public function init() {
parent::init();
}
public function validateAttribute($model, $attribute) {
if (!preg_match('/^[0-9]{2}-[0-9]{3}$/', $model->$attribute))
$model->addError($attribute, 'Wrong postal code format.');
}
public function clientValidateAttribute($model, $attribute, $view) { //want js-validation too
$message = 'Invalid status input.';
return <<<JS
if (!/^[0-9]{2}-[0-9]{3}$/.test("{$model->$attribute}")) {
messages.push("$message");
}
JS;
}
}
So, an example of correct code is 00-202.
When I (in user role) enter incorrect value, page reloads and I see Wrong postal code format. message, although I redefined clientValidateAttribute method and wrote JS-validation, which, as I suggested, will not allow page to reload. Then I press submit button again: this time page doesn't reload and I see Invalid status input. message (so, the second press time JS triggers). But I when enter correct code after that, I still see Invalid status input. message and nothing happens.
So, what's wrong with my clientValidateAttribute() method? validateAttribute() works great.
UPDATE
Snippet from controller
public function actionProfile(){ //can't use massive assignment here, cause info from 2 (not 1) user models is needed
if (\Yii::$app->user->isGuest) {
return $this->redirect('/site/index/');
}
$is_user_admin = Users::findOne(['is_admin' => 1]);
if ($is_user_admin->id == \Yii::$app->user->id)
return $this->redirect('/admin/login/');
$is_user_blocked = Users::find()->where(['is_blocked' => 1, 'id' => \Yii::$app->user->id])->one();
if($is_user_blocked)
return $this->actionLogout();
//3 model instances to retrieve data from users && company_profiles && logo
$user_data = Users::find()->where(['id'=>\Yii::$app->user->id])->one();
$user_data->scenario = 'update';
$company_profile_data = CompanyProfiles::find()->where(['user_id'=>Yii::$app->user->id])->one();
$logo = LogoData::findOne(['user_id' => \Yii::$app->user->id]);
$logo_name = $logo->logo_name; //will be NULL, if user have never uploaded logo. In this case placeholder will be used
$upload_logo = new UploadLogo();
if (Yii::$app->request->isPost) {
$upload_logo->imageFile = UploadedFile::getInstance($upload_logo, 'imageFile');
if ($upload_logo->imageFile) { //1st part ($logo_data->imageFile) - whether user have uploaded logo
$logo_file_name = md5($user_data->id);
$is_uploaded = $upload_logo->upload($logo_file_name);
if ($is_uploaded) { //this cond is needed, cause validation for image fails (?)
//create record in 'logo_data' tbl, deleting previous
if ($logo_name) {
$logo->delete();
} else { //if upload logo first time, set val to $logo_name. Otherwise NULL val will pass to 'profile' view, and user wont see his new logo at once
$logo_name = $logo_file_name.'.'.$upload_logo->imageFile->extension;
}
$logo_data = new LogoData;
$logo_data->user_id = \Yii::$app->user->id;
$logo_data->logo_name = $logo_name;
$logo_data->save();
}
}
}
if (isset($_POST['CompanyProfiles'])){
$company_profile_data->firm_data = $_POST['CompanyProfiles']['firm_data'];
$company_profile_data->company_name = $_POST['CompanyProfiles']['company_name'];
$company_profile_data->regon = $_POST['CompanyProfiles']['regon'];
$company_profile_data->pesel = $_POST['CompanyProfiles']['pesel'];
$company_profile_data->postal_code = $_POST['CompanyProfiles']['postal_code'];
$company_profile_data->nip = $_POST['CompanyProfiles']['nip'];
$company_profile_data->country = $_POST['CompanyProfiles']['country'];
$company_profile_data->city = $_POST['CompanyProfiles']['city'];
$company_profile_data->address = $_POST['CompanyProfiles']['address'];
$company_profile_data->telephone_num = $_POST['CompanyProfiles']['telephone_num'];
$company_profile_data->email = $_POST['CompanyProfiles']['email'];
$company_profile_data->update();
}
if (isset($_POST['personal-data-button'])) {
$user_data->username = $_POST['Users']['username'];
$user_data->password_repeat = $user_data->password = md5($_POST['Users']['password']);
$user_data->update();
}
return $this->render('profile', ['user_data' => $user_data, 'company_profile_data' => $company_profile_data, 'upload_logo' => $upload_logo, 'logo_name' => $logo_name]);
}

My inaccuracy was in clientValidateAttribute() method. Instead of $model->$attribute in code snippet:
if (!/^[0-9]{2}-[0-9]{3}$/.test("{$model->$attribute}")) {
...I had to use predefined JS-var value, cause this var changes with entered value change. So, my new code is:
public function clientValidateAttribute($model, $attribute, $view) {
return <<<JS
if (!/^[0-9]{2}-[0-9]{3}$/.test(value)) {
messages.push("Wrong postal code format.");
}
JS;
}

Model does not load rules and behaviors until not called any function from model. When you call $company_profile_data->update(); model call update and validate functions.
Try add after $company_profile_data = CompanyProfiles::find() this code:
$company_profile_data->validate();
Or just use load function. I think it will help.

Related

Modify form value after submit in symfony2

I have form like this :
name (required).
slug (required).
slug is required in back end but user is allowed to leave it blank in form field ( if user leave slug blank, it will use name as the input instead ).
I have tried with Event form listener but it said You cannot change value of submitted form. I tried with Data transformers like this :
public function reverseTransform($slug)
{
if ($slug) {
return $slug;
} else {
return $this->builder->get('name')->getData();
}
}
return $this->builder->get('name')->getData(); always return null. So I tried like this:
public function reverseTransform($slug)
{
if ($slug) {
return $slug;
} else {
return $_POST['category']['name'];
}
}
it works but I think it against the framework. How I can done this with right way?
You can also do it in the controller
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
// get the data sent from your form
$data = $form->getData();
$slug = $data->getSlug();
// if no slug manually hydrate the $formObject
if(!$slug)
{
$formObject->setSlug($data->getName());
}
$em->persist($formObject);
$em->flush();
return ....
}
}
If you use a function to keep the code at one place then you should also not work with Request data.
In the form action you call that function including the name variable.
public function reverseTransform($name, $slug)
{
if (!empty($slug)) {
return $slug;
} else {
return $name;
}
}
Another possible way is to set via request class like this:
Array form <input name="tag['slug']"...>:
public function createAction(Request $request)
{
$postData = $request->request->get('tag');
$slug = ($postData['slug']) ? $postData['slug'] : $postData['name'];
$request->request->set('tag', array_merge($postData,['slug' => $slug]));
.......
Common form <input name="slug"...>:
$request->request->set('slug', 'your value');
I think this is the best way because if you are using dml-filter-bundle you don't need to filter your input in your controller like this again:
$this->get('dms.filter')->filterEntity($entity);

Modify a row with CakePHP

I'm using CakePHP to show a frontend GUI for a MySQL database table. I've used bake to auto generate the screens and I currently have a fully functioning app with View, Edit and Delete buttons per row. I want to add a button per row, called Accept, which should set IsAccepted = 1 on the SQL row.
I've managed to add an Accept button per row as follows:
echo $this->Html->link(__('Accept'), array('action' => 'accept', $product['Product']['ID']))
But the code in ProductController.php does not work:
public function accept($id = null){
...
$this->Product->IsAccepted = 1; // does not work, silently fails
}
What am I doing wrong? How do I properly edit a row using a per-row button?
public function accept($id = null){
$this->Product->save(array('id' => $id, 'is_accepted' => 1));
}
// assuming cake 2.1+
public function accept($id = null){
if($this->Product->exists($id)) {
$this->Product->saveField('is_accepted', 1);
// success..
}
// else throw not found exception...
}
Thanks to cornelb I found the answer! This is the final code I used to modify a row, with a per-row button.
Modifies the row when the per-row button is pressed (works just like a POST/AJAX button)
A flash message that says "Accepted!" shows if saving succeeds
Redirects back to the listing page (appears to never leave the listing)
This is the code that goes in ProductController.php (or whatever controller class you have):
public function accept($id = null) {
if ($this->Product->exists($id)) {
// save the row
// you absolutely need to fill the 'id' slot, even if its not your primary key!!
// this ensures that the row is EDITED, and not INSERTED!
if($this->Product->save(array('id' => $id, 'ID' => $id, 'IsApproved' => 1, 'ApprovedDate' => date('Y-m-d H:i:s', time())))){
// show a "flash" message
// (not Adobe Flash, just a message that shows on top of the list)
$this->Session->setFlash(__('The product has been accepted!'));
// this action does not have a view so no need to render
$this->autoRender = false;
// redirect to index view
return $this->redirect(array('action' => 'index'));
}
}
}
**Try this.....**
<?php
public function accept($id = null) {
$this->autoRender = false; // if action has not view.
if ($this->Product->exists($id)) {
$this->Product->id = $id;
if ($this->Product->save(array('is_accepted' => 1))) {
$this->Session->setFlash(__('The product has been accepted!'));
return $this->redirect(array('action' => 'index'));
}
}
}
?>
Just run updateAll query from accept function as shown below:
public function accept($id = null){
if(!empty($id)){
$this->Product->updateAll(
array('Product.is_accepted' => 1),
array('Product.id' => $id)
);
}
}
Hope this will help you...
For reference: http://book.cakephp.org/2.0/en/models/saving-your-data.html

Yii - updating a model and using the model to echo data in the view

I have the following code for updating a Yii model:
public function actionSettings($id) {
if (!isset($_POST['save_hostname']) && isset($_POST['Camera']) && isset($_POST['Camera']['hostname'])) {
$_POST['Camera']['hostname'] = '';
}
$model = $this->loadModel($id);
$model->setScenario('frontend');
$this->performAjaxValidation($model);
if (isset($_POST['Camera'])) {
$model->attributes = $_POST['Camera'];
unset($model->api_password);
if ($model->save()) {
Yii::app()->user->setFlash('success', "Camera settings has been saved!");
} else {
Yii::app()->user->setFlash('error', "Unable to save camera settings!");
}
}
$this->render('settings', array(
'model' => $model,
));
}
This works fine, except in my model I have code like this:
<h1>Settings For: <?php echo CHtml::encode($model->name); ?></h1>
The problem is that, even when the user input fails validation, the h1 tag is having bad input echoed out into it. If the input fails the validation, the h1 attribute should stay the same.
I can 'reset' the $model variable to what is in the database before the view is returned, but this then means I don't get any error feedback / validation failed messages.
Is my only option to have 2 $models ($model and $data perhaps), one used for handling the form and the other for sending data to the page? Or does someone have a more elegant solution?
performAjaxValidation assigns all save attributes to the model so this behavior is normal.
I would reload model if save fails.
$model->refresh();

is_unique for codeigniter form validation

I'm trying to figure out how I can use the is_unique rule from the Codeigniter form validation library in the following situation.
I'm trying to submit a edit user form and have the rule:
$this->form_validation->set_rules('user_name', 'User Name', 'required|trim|xss_clean|is_unique[users.user_name]');
What if other values in the form are being changed but this value stays the same. The form is going to see that this value already exists so how would I protect it from editing if this value isn't changed.
Using your code as an example, the is_unique validation rule works by looking for a field called user_name in your users database table. If the field with the same value exists it validates as false.
To make sure it runs only when the user submits a new value, you could check the posted value $this->input->post('user_name') against the value you pulled from the database to populate your form with. If they are the same, don't validate is_unique;
if($this->input->post('user_name') != $original_value) {
$is_unique = '|is_unique[users.user_name]'
} else {
$is_unique = ''
}
$this->form_validation->set_rules('user_name', 'User Name', 'required|trim|xss_clean'.$is_unique);
There's a better way to go around it, I think, still using CodeIgniters' validation library...
Use edit_unique where you pass an extra parameter which is the id of the row you're editing.. See below.. I use it and works pretty fine for me.. hope it helps
$this->form_validation->set_rules('user_name', 'User Name', 'required|trim|xss_clean|edit_unique[users.user_name.'.$id.']');
$something = $this->input->post('something');
$this->form->validation->set_rules('something','Something','xss_clean|is_unique['tbl'.users]');
if($this->form_validation->run()== FALSE){
}
Simple Way
Just Change isset to is_object in system/libraries/form_validation.php
public function is_unique($str, $field)
{
sscanf($field, '%[^.].%[^.]', $table, $field);
return is_object($this->CI->db) //default isset
? ($this->CI->db->limit(1)->get_where($table, array($field => $str))->num_rows() === 0)
: FALSE;
}
Here's an easy method that worked for me and uses well documented code (thanks to https://github.com/ivantcholakov for sharing it!). I found it referenced at https://github.com/bcit-ci/CodeIgniter/issues/3109#issuecomment-46346280
Download https://github.com/ivantcholakov/starter-public-edition-3/blob/master/platform/application/libraries/MY_Form_validation.php (MIT licensed) and save it to your application at application\libraries\MY_Form_validation.php
Delete these two lines from __construct():
$this->CI->load->helper('checkbox');
$this->CI->load->helper('email');
Delete all the functions except __construct() and unique().
At the end of the __construct() method of your controller add this line:
$this->load->library('form_validation');
As per the documentation of the unique() method update your validation rule to add a "unique" rule like this (e.g. if you already have required and trim rules):
…|required|unique[tablename.fieldname,tablename.(primaryKey-used-for-updates)]|trim...
Extend Form_validation.php library create class inside of application/libraries file name MY_Form_validation.php
<?php
class MY_Form_validation extends CI_Form_validation{
protected $ci;
public function __construct($config = array()){
parent::__construct($config);
$this->ci =& get_instance();
}
public function is_unique_update($str, $field){
$explode=explode('#', $field);
$field_name=$explode['0'];
$field_id_key=$explode['1'];
$field_id_value=$explode['2'];
sscanf($field_name, '%[^.].%[^.]', $table, $field_name);
if(isset($this->ci->db)){
if($this->ci->db->limit(1)->get_where($table, array($field_name => $str,$field_id_key=>$field_id_value))->num_rows() === 0){
$this->ci->form_validation->set_message('is_unique_update', 'The {field} field must contain a unique value.');
return false;
}
return true;
}
}
}
Now in your controller
$this->form_validation->set_rules('user_name', 'User Name', 'required|trim|xss_clean|is_unique_update[users.user_name#id#'.$id.']');
"#" I used for explode the string
where id is primary key of users table
and $id is the value of id.
Now you can use this is_unique_update validation in any controller.
This question is very old but maybe some new people experience this problem and this is the solution for it.
I bet your are using Modular Extensions (HMVC) and you have created a new library, MY_Form_validation. You did id for callbacks, so you have this line of code on your class in order to use callbacks:
$this->form_validation->CI =& $this;
Well, the solution to this is whenever you want to use "is_unique" you must delete this line of code "$this->form_validation->CI =& $this;" from the class. I have experienced this problem and i fix it this way, it works fine now.
If you realy want to use callbacks "$this->form_validation->CI =& $this;", then do it only on required "methods" / "functions" where you don't want to use is_unique.
This code helpful for unique validation to create and update function...
In controller
Add this form validation code in both create and update function
$this->form_validation->set_rules('order_no', 'Order no', 'required|callback_check_order_no');
Add this call back function in controller
function check_order_no($order_no) {
if($this->input->post('id'))
$id = $this->input->post('id');
else
$id = '';
$result = $this->Data_model->check_unique_order_no($id, $order_no);
if($result == 0)
$response = true;
else {
$this->form_validation->set_message('check_order_no', 'Order no already exist');
$response = false;
}
return $response;
}
In model
function check_unique_order_no($id = '', $order_no) {
$this->db->where('order_no', $order_no);
$this->db->where('status', "A");
if($id) {
$this->db->where_not_in('id', $id);
}
return $this->db->get('delivery_order')->num_rows();
}
I'm using codeigniter3 and it shows me error when I check username on updating the value, is_unique is not designed to work with update scenario
so using #Anthony Mutisya's answer, here is the complete solution
in your controller, add this line while validation username of the current user with the database
$this->form_validation->set_rules('user_name', 'User Name', 'required|trim|xss_clean|edit_unique[users.user_name.'.$id.']');
You can get that $id from your submited form.
Now, add the following function to /system/libraries/Form_Validation.php this file. System folder is present in your root of CodeIgniter3 folder.
/**
* edit_unique // for check on update value
*
* Check if the input value doesn't already exist
* in the specified database field.
*
* #param string $str
* #param string $field
* #return bool
*/
function edit_unique($value, $params) {
$CI =& get_instance();
$CI->load->database();
$CI->form_validation->set_message('edit_unique', "Sorry, that %s is already being used.");
list($table, $field, $current_id) = explode(".", $params);
$query = $CI->db->select()->from($table)->where($field, $value)->limit(1)->get();
if ($query->row() && $query->row()->id != $current_id)
{
return FALSE;
} else {
return TRUE;
}
}
It works perfectly fine in my case
CodeIgniter 4 has already solution for that,
$validation->setRules([
'email' => 'required|valid_email|is_unique[users.email,id,{id}]',
]);
$_POST = [
'id' => 4,
'email' => 'foo#example.com',
];
then the {id} placeholder would be replaced with the number 4, giving this revised rule:
$validation->setRules([
'email' => 'required|valid_email|is_unique[users.email,id,4]',
]);
Official Documentation
we must have to add table name for is_unique
for Exp.
is_unique[users.email]

kohana form validation

There is a 'main.php' view that contains a form with email and name fields and a submit button. Eveyrthing works fine with action_index (the code is below), but I'm curious how to modify the code below so it validates if the email was entered correctly. It should not put values in the database if the email field is not valid. I hope it is possible to made using ->rule. Is it? If yes, then how where to add the validation? (I had no luck trying it in different ways).
public function action_index()
{
if ( !empty($_POST) ) {
$model = ORM::factory('tbl1'); // create
$model->values($_POST); // load values to model
if ($model->check()) {
$model->save(); // save the model
} else {
//show errors
}
}
$this->response->body(View::factory('main'));
}
Thank you.
Use rules function in your ORM model:
public function rules()
{
return array(
'email' => array(
array('email', array(':value')),
),
);
}

Categories