CodeIgniter 4 Model Callbacks not hashing password before insert - php

I am totally new to CI4 but do have some experience with PHP. I having trouble getting the Model class callback functions to work when inserting a new user row.
Here is the Controller:
<?php use App\Models\UserModel;
class Users extends BaseController
{
public $users_model;
public function __construct() {
$this->users_model = new UserModel();
}
public function register() {
$data = [
'page_title' => 'Register An Account',
'validation' => NULL //must have this set to null until/if any validation rules are broken
];
if ($this->request->getMethod() == 'post') {
//validation rules
$rules = [
'username' => [
'rules' => 'required|min_length[4]|max_length[20]|validateUsername[username]',
'errors' => [
'required' => 'A username is required',
'min_length' => 'Username must be at least {param} characters long',
'max_length' => 'Username cannot be more than {param} characters long',
'validateUsername' => 'Username can only contain letters and numbers',
],
] ,
'email' => [
'rules' => 'required|valid_email|is_unique[users.email]',
'errors' => [
'required' => 'An Email is required',
'valid_email' => 'Enter a valid email',
'is_unique' => 'That email has already been registerd',
],
],
'password' => [
'rules' => 'required|min_length[6]|max_length[16]|validatePassword[password]',
'errors' => [
'required' => 'A password is required',
'min_length' => 'Password must contain at least {param} characters',
'max_length' => 'Password cannot be more than {param} characters in length',
'validatePassword' => 'Password must have at least 1 numeric value',
],
],
'confirm_password' => [
'rules' => 'required|matches[password]',
'errors' => [
'required' => 'Must confirm password',
'matches' => 'Passwords do not match'
],
]
];
if ($this->validate($rules)) {
//all fields passed validation so need to save to the db
$user_data = [
'username' => $this->request->getVar('username', FILTER_SANITIZE_STRING),
'email' => $this->request->getVar('email', FILTER_SANITIZE_EMAIL),
'password' => $this->request->getVar('password')
];
if ($this->users_model->createUser($user_data)) {
echo 'user stored in the db.';
} else {
echo 'user not stored in the db.';
}
} else {
//there are some validation errors
$data['validation'] = $this->validator;
}
}//post request check ends here
return view('users/register', $data);
}// register method ends here.
public function login() {
$data = [
'page_title' => 'Login'
];
return view('users/login', $data);
}
public function logout() {
//not implemented yet
}
}
And here is the Model class:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $table = 'users';
protected $allowedFields = ['username', 'email', 'password'];
protected $beforeInsert = ['beforeInsert'];
protected $beforeUpdate = ['beforeUpdate'];
protected $allowCallbacks = TRUE;
protected $builder;
public function createUser(array $data) {
$this->builder = $this->db->table($this->table);
$this->db->transStart();
$this->builder->insert($data);
$this->db->transComplete();
if($this->db->affectedRows() == 1) {
return TRUE;
} else {
return FALSE;
}
}
protected function beforeInsert(array $data) {
if (isset($data['data']['password']))
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
return $data;
}
protected function beforeUpdate(array $data) {
if (isset($data['data']['password']))
$data['data']['password'] = password_hash($data['data']['password'], PASSWORD_DEFAULT);
return $data;
}
}
What is weird to me is that the record does get stored in the database but the password is plain text and not hashed. Also, in the controller class, the if statement:
if ($this->users_model->createUser($user_data)) {
echo 'user stored in the db.';
} else {
echo 'user not stored in the db.';
}
Always echo's the 'user not stored in the db' message and the user is stored in the db.
In the model:
if($this->db->affectedRows() == 1) {
return TRUE;
} else {
return FALSE;
}
I have echoed out the result of $this->db->affectedRows()
and it is 1 on a successful insert (although the password is not hashed) so I thought this would cause the method to return true, therefore over in the controller, I would see the truthy part of the if condition, 'user stored in the db'.
Can anyone tell me where I am going wrong in all of this?
I have successfully used the password_hash() function in the controller itself on the $this->request->getVar('password') data so I know it works. But I wanted to leverage the in-built Model callbacks and take care of the hashing for inserts and updates there.
I have also shared/posted this question in the CodeIgniter forums as well.
Thanks for any help.

Codeigniter4 documentation is not really clear about it, but whenever you want a model callback to be triggered, you need to use directly the model functions and not the builder ones.
For example, inside your model, using $this->builder->insert() will NOT trigger the beforeInsert nor the afterInsert functions of your model but using $this->insert() will do the trick.
So in your case you should replace your createUser function with
public function createUser(array $data) {
$this->db->transStart();
$this->insert($data);
$this->db->transComplete();
if ($this->db->affectedRows() == 1) {
return TRUE;
} else {
return FALSE;
}
}

Related

How do you customize the default login error message in laravel 8

I was browsing through the web and looking for solution on how can I modify this error message on Jetstream login:
Inside app/Actions/Fortify there is a file CreateNewUser.php where I can put some validation custom message on each field like this:
public function create(array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'phone' => ['required', 'string', 'max:17'],
'password' => $this->passwordRules(),
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required', 'accepted'] : '',
],[
'name.required'=>'Name is required',
'email.required'=>'Email is required',
'phone.required'=>'Phone is required',
])->validate();
However, there is none for Login.php. I search the web and saw something about vendors/laravel/fortify/src/Actions/AttemptToAuthenticate.php but its consist with lots of code where I don't know where to put the customization:
public function handle($request, $next)
{
if (Fortify::$authenticateUsingCallback) {
return $this->handleUsingCustomCallback($request, $next);
}
if ($this->guard->attempt(
$request->only(Fortify::username(), 'password'),
$request->filled('remember'))
) {
return $next($request);
}
$this->throwFailedAuthenticationException($request);
}
Is there an easy way to customize the "The email field is required" to a different custom message in Laravel 8?
Been stuck for a couple of hours.
You can customize FortifyServiceProvider inside App > Providers by using the Fortify::authenticateUsing method and it should be called from the boot method.
public function boot()
{
Fortify::authenticateUsing(function (Request $request) {
$user = User::where('email', $request->email)->first();
if ($user &&
Hash::check($request->password, $user->password)) {
return $user;
} else {
$request->session()->flash('message', 'Invalid!');
return false;
}
});
}
Reference
step 1 go to vendor\laravel\fortify\src\Http\Requests\LoginRequest.php
step 2 in the loginRequest.php you will find two method
public function authorize()
{
return true;
}
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
];
}
step 3 add the following method below
public function messages() {
return = [
'email.required' => 'email required',
'password.required' => 'password required'
];
}

how to remove the error showing below:(laravel framework)

I have viewed a lot off documents already. I am trying to do my login, but its not working. Showing errors. I don't know the reason.i am very new in laravel
This is my controller code
public function do_login()
{
$credentials = [
'username'=>Input::get('username'),
'password'=>Input::get('password')
];
$rules = [
'username' => 'required',
'password'=>'required'
];
//validating the credentials.
$validator = Validator::make($credentials,$rules);
//in case the credentials are valid. Try to login the user.
if($validator->passes())
{
if (Auth::attempt($credentials))
{
//if successfull redirect the user
return Redirect::to('home');
}
else
{
//else send back the login failure message.
return Redirect::back()->withInput()->with('failure','username or password is invalid!');
}
}
else
{
//send back the validation errors.
return Redirect::back()->withErrors($validator)->withInput();
}
}
this is my model code:
<?php namespace LARAVEL\laravel_1st_project\models\UserModel;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class UserModel extends \Eloquent implements UserInterface , RemindableInterface
{
public $table = "user";
protected $primaryKey = 'employee_id';
//public $timestamps = false;
}
this is my app\config\auth.php :
'model' => 'LARAVEL\laravel_1st_project\UserModel',
'table' => 'user',
'reminder' => array(
'email' => 'email.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
Error :

Yii2: Either one field is required Validation

I have to implement the validation as mentioned in the title that either one of the two fields (email, phone) is required. I am doing this in my model:
[['email'],'either', ['other' => ['phone']]],
And this is the method:
public function either($attribute_name, $params) {
$field1 = $this->getAttributeLabel($attribute_name);
$field2 = $this->getAttributeLabel($params['other']);
if (empty($this->$attribute_name) && empty($this->$params['other'])) {
$this->addError($attribute_name, Yii::t('user', "either {$field1} or {$field2} is required."));
return false;
}
return true;
}
When I access my index page, it gives me this error:
Exception (Unknown Property) 'yii\base\UnknownPropertyException' with
message 'Setting unknown property: yii\validators\InlineValidator::0'
Any help?
If you don't care that both fields show an error when the user provides neither of both fields:
This solutions is shorter than the other answers and does not require a new validator type/class:
$rules = [
['email', 'required', 'when' => function($model) { return empty($model->phone); }],
['phone', 'required', 'when' => function($model) { return empty($model->email); }],
];
If you want to have a customized error message, just set the message option:
$rules = [
[
'email', 'required',
'message' => 'Either email or phone is required.',
'when' => function($model) { return empty($model->phone); }
],
[
'phone', 'required',
'message' => 'Either email or phone is required.',
'when' => function($model) { return empty($model->email); }
],
];
The rule should be:
['email', 'either', 'params' => ['other' => 'phone']],
And method:
public function either($attribute_name, $params)
{
$field1 = $this->getAttributeLabel($attribute_name);
$field2 = $this->getAttributeLabel($params['other']);
if (empty($this->$attribute_name) && empty($this->{$params['other']})) {
$this->addError($attribute_name, Yii::t('user', "either {$field1} or {$field2} is required."));
}
}
Improved variant
['gipsy_team_name', 'either', 'skipOnEmpty'=>false, 'params' => ['other' => 'poker_strategy_nick_name']],
['vkontakte', 'either', 'skipOnEmpty'=>false, 'params' => ['other' => ['odnoklasniki','odnoklasniki']]],
Added 'skipOnEmpty'=>false for forcing validating and 'other' can be array
/**
* validation rule
* #param string $attribute_name
* #param array $params
*/
public function either($attribute_name, $params)
{
/**
* validate actula attribute
*/
if(!empty($this->$attribute_name)){
return;
}
if(!is_array($params['other'])){
$params['other'] = [$params['other']];
}
/**
* validate other attributes
*/
foreach($params['other'] as $field){
if(!empty($this->$field)){
return;
}
}
/**
* get attributes labels
*/
$fieldsLabels = [$this->getAttributeLabel($attribute_name)];
foreach($params['other'] as $field){
$fieldsLabels[] = $this->getAttributeLabel($field);
}
$this->addError($attribute_name, \Yii::t('poker_reg', 'One of fields "{fieldList}" is required.',[
'fieldList' => implode('"", "', $fieldsLabels),
]));
}

How to use sometimes rule in Laravel 5 request class

I have the following request class:
<?php namespace App\Http\Requests\User;
use App\Http\Requests\Request;
use Validator;
use Session;
use Auth;
use App\User;
class RegisterStep1Request extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Set up the validation rules
*/
public function rules()
{
Validator::extend('valid_date', function($attribute, $value, $parameters)
{
$pieces = explode('/', $value);
if(strpos($value, '/')===FALSE) {
return false;
} else {
if(checkdate($pieces[1], $pieces[0], $pieces[2])) {
return true;
} else {
return false;
}
}
});
return [
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required|email|unique:users,email',
'dob' => 'required|regex:/[0-9]{2}\/[0-9]{2}\/[0-9]{4}/|valid_date',
'mobile' => 'required',
'password' => 'required|confirmed'
];
}
public function messages()
{
return [
'first_name.required' => 'The first name field is required.',
'last_name.required' => 'The last name field is required.',
'email.required' => 'The email address field is required.',
'email.email' => 'The email address specified is not a valid email address.',
'email.unique' => 'The email address is already registered with this website.',
'dob.required' => 'The date of birth field is required.',
'dob.regex' => 'The date of birth is invalid. Please use the following format: DD/MM/YYYY.',
'dob.valid_date' => 'The date of birth is invalid. Please check and try again.',
'mobile.required' => 'The mobile number field is required.',
'password.required' => 'The password field is required.',
'password.confirmed' => 'The confirm password field does not match the password field.'
];
}
}
I want to add the following sometimes rule:
Validator::sometimes('dob', 'valid_date', function($input)
{
return apply_regex($input->dob) === true;
});
How would I add this to my request class?
I have amended my rules method to the following:
public function rules()
{
Validator::extend('valid_date', function($attribute, $value, $parameters)
{
$pieces = explode('/', $value);
if(strpos($value, '/')===FALSE) {
return false;
} else {
if(checkdate($pieces[1], $pieces[0], $pieces[2])) {
return true;
} else {
return false;
}
}
});
Validator::sometimes('dob', 'valid_date', function($input)
{
return apply_regex($input->dob) === true;
});
return [
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required|email|unique:users,email',
'dob' => 'sometimes|required|regex:/[0-9]{2}\/[0-9]{2}\/[0-9]{4}/|valid_date',
'mobile' => 'required',
'password' => 'required|confirmed'
];
}
But I now get the following error when I submit the form:
FatalErrorException in Facade.php line 216:
Call to undefined method Illuminate\Validation\Factory::sometimes()
There is a documented way to make changes to the request's validator instance in Laravel 5.4. You should implement the withValidator method for that.
Based on the example from #lukasgeiter's answer, you may add the following to your request class:
/**
* Configure the validator instance.
*
* #param \Illuminate\Validation\Validator $validator
* #return void
*/
public function withValidator($validator)
{
$validator->sometimes('dob', 'valid_date', function ($input) {
return apply_regex($input->dob) === true;
});
}
By doing this you don't have to worry about overriding internal methods. Besides, this seems to be the official way for configuring the validator.
You can attach a sometimes() rule by overriding the getValidatorInstance() function in your form request:
protected function getValidatorInstance(){
$validator = parent::getValidatorInstance();
$validator->sometimes('dob', 'valid_date', function($input)
{
return apply_regex($input->dob) === true;
});
return $validator;
}
You just need to add the dob key to the array you are returning, along with the validation ruleset to follow, including sometimes.
In this case:
'dob' : 'sometimes|required|regex:/[0-9]{2}\/[0-9]{2}\/[0-9]{4}/|valid_date'
According to your comment
I want the rule valid_date to only run if the regex rule returns true. Otherwise
the valid_date rule errors if the date isnt in the right format.
Validator::extend('valid_date', function($attribute, $value, $parameters)
{
\\use the regex here instead
if (!preg_match('/[0-9]{2}\/[0-9]{2}\/[0-9]{4}/', $value)) return false;
$pieces = explode('/', $value);
if(strpos($value, '/')===FALSE) {
return false;
} else {
if(checkdate($pieces[1], $pieces[0], $pieces[2])) {
return true;
} else {
return false;
}
}
});
$validator = Validator::make($data, [
'first_name' => 'required',
'last_name' => 'required',
'email' => 'required|email|unique:users,email',
'dob' => 'required|valid_date',
'mobile' => 'required',
'password' => 'required|confirmed'
]);

Form Validation with CodeIgniter + MySql Not Working

I can't seem to get my form validation working with Codeigniter. I've tried extending the Form_validation class by creating My_Form_validation.php and have had no success. I'm now trying the callback method. I was getting errors to show up for a little while, however they were incorrect.
This is the code that is located in my controller:
function create_user() {
$this->load->library('form_validation');
$validate = array(
array(
'field' => 'first_name',
'label' => 'First Name',
'rules' => 'trim|required|xss_clean'
),
array(
'field' => 'last_name',
'label' => 'Last Name',
'rules' => 'trim|required|xss_clean'
),
array(
'field' => 'username',
'label' => 'Username',
'rules' => 'trim|required|xss_clean|callback_user_exists'
),
array(
'field' => 'email_address',
'label' => 'Email Address',
'rules' => 'trim|required|valid_email|callback_email_exists'
),
array(
'field' => 'password',
'label' => 'Password',
'rules' => 'trim|required|min_length[5]|max_length[32]'
),
array(
'field' => 'password2',
'label' => 'Confirm Password',
'rules' => 'trim|required|matches[password]'
)
);
$this->form_validation->set_rules($validate);
if($this->form_validation->run() == FALSE) {
$this->load->view('user/user-signup');
} else {
$this->load->model('user_model');
if($query = $this->user_model->create_user()) {
$this->load->view('user/user-login');
} else {
$this->index();
}
}
}
function user_exists($username) {
$this->load->model('user_model');
$this->user_model->user_exists($username);
$this->form_validation->set_message('user_exists', 'This username is already taken');
}
function email_exists($email) {
$this->load->model('user_model');
$this->user_model->email_exists($email);
$this->form_validation->set_message('email_exists', 'This email is already in use');
}
And this is the code located in my Model:
function create_user() {
$insert_user = array(
'first_name' => $this->input->post('first_name'),
'last_name' => $this->input->post('last_name'),
'username' => $this->input->post('username'),
'email_address' => $this->input->post('email_address'),
'password' => md5($this->input->post('password'))
);
$insert = $this->db->insert('users', $insert_user);
return $insert;
}
function user_exists($username) {
$this->db->where('username', $username);
$query = $this->db->get('users');
if($query->num_rows > 0) {
return true;
} else {
return false;
}
}
function email_exists($email) {
$this->db->where('email_address', $email);
$query = $this->db->get('users');
if($query->num_rows > 0) {
return true;
} else {
return false;
}
}
I'm wanting to validate by checking to see if a Username or Email Address already exists in the database, and if so, the user will need to make the appropriate changes.
Any ideas?
Your code is very hard to read, so I'll show you how improve it. :)
In your controller, you can use constructor for model loading instead this two lines:
$this->load->model('user_model');
Like this:
function __constructor() {
parent::__constructor();
$this->load->model('user_model');
}
Change your user_exists callback to this:
function user_exists($username) {
$user_check = $this->user_model->user_exists($username);
if($user_check > 0) {
$this->form_validation->set_message('user_exists', 'This username is already taken');
return FALSE;
}
else {
return TRUE;
}
}
Change your email_exists callback to this:
function email_exists($email) {
$check_email = $this->user_model->email_exists($email);
if($check_email > 0) {
$this->form_validation->set_message('email_exists', 'This email is already in use');
return FALSE;
}
else {
return TRUE;
}
}
Now, go back to your model and change these two models methods:
function user_exists($username) {
$this->db->where('username', $username);
$query = $this->db->get('users');
return $query->num_rows();
}
function email_exists($email) {
$this->db->where('email_address', $email);
$query = $this->db->get('users');
return $query->num_rows();
}
Now, you do it wrong because you don't understand what model means. in the models methods, you can write database queries... So, if you want to create an user, you should get inputs' information in the controller and then pass them to the model method create_user, like this:
Controller method create_user:
function create_user() {
$this->load->library('form_validation');
$validate = array(
array(
'field' => 'first_name',
'label' => 'First Name',
'rules' => 'trim|required|xss_clean'
),
array(
'field' => 'last_name',
'label' => 'Last Name',
'rules' => 'trim|required|xss_clean'
),
array(
'field' => 'username',
'label' => 'Username',
'rules' => 'trim|required|xss_clean|callback_user_exists'
),
array(
'field' => 'email_address',
'label' => 'Email Address',
'rules' => 'trim|required|valid_email|callback_email_exists'
),
array(
'field' => 'password',
'label' => 'Password',
'rules' => 'trim|required|min_length[5]|max_length[32]'
),
array(
'field' => 'password2',
'label' => 'Confirm Password',
'rules' => 'trim|required|matches[password]'
)
);
$this->form_validation->set_rules($validate);
if($this->form_validation->run() == FALSE) {
$this->load->view('user/user-signup');
} else {
$user_data['first_name'] = $this->input->post("first_name");
$user_data['last_name'] = $this->input->post("last_name");
$user_data['username'] = $this->input->post("username");
$user_data['email_address'] = $this->input->post("email_address");
$user_data['password'] = $this->input->post("password");
if($query = $this->user_model->create_user($user_data)) {
$this->load->view('user/user-login');
} else {
$this->index();
}
}
}
Model's method create_user:
function create_user($user_data) {
return $this->db->insert("users", $user_data);
}
That's all, it will work. Good luck.
Have you tried is_unique[table_name.field_name] rule?
Example:
$this->form_validation->set_rules('username', 'Username',
'required|min_length[5]|max_length[12]|is_unique[users.username]');
$this->form_validation->set_rules('email', 'Email',
'required|valid_email|is_unique[users.email]');
Update
If you want to use a callback function then user_exists function should be in the controller instead in the model as you mentioned.
The correct way to define is -
public function username_check($str)
{
if ($str == 'test')
{
$this->form_validation->set_message('username_check', 'The %s field can not be the word "test"');
return FALSE;
}
else
{
return TRUE;
}
}
Re-write your function like this
function user_exists($username) {
$this->load->model('user_model');
$result = $this->user_model->user_exists($username);
if($result != NULL){
$this->form_validation->set_message('user_exists', 'This username is already taken');
return FALSE;
}else{
return TRUE;
}
}
You are not returning true or false therefore it is always getting last true returned by xss_clea.
i have had the same problem.
One of the problems with the callback function is that it can only accept one parameter.
there are two states to consider when checking for uniqueness of a record in your form.
1) you are adding a new record
2) you are editing an existing record.
if you are adding a new record the inbuilt is_unique works fine.
if you are editing an existing record is_unique does not work, because it finds the record you are editing and says the form data is not unique.
to get around this problem i have used the session class, set it for case 2 before you run the validation script, so you need to know if you are editing an existing record, or adding a new record. to do this i just add a hidden input to the form when it is edited, eg the records unique id.
presumably you have a unique user id in your users table, eg so set it before the validation is run.
if($this->input->post('user_id')){$this->session->set_userdata('callback_user_id',$this->input->post('user_id'));}
then in your callback, use this sort of algorithm:
case 1) ie $this->session->userdata('callback_user_id') == FALSE
if the user name is unique, validate, and return true.
if the user name is not unique, return false with validation message user has to be unique.
case 2) ie, the callback_user_id is set.
if the user name is unique, validate and return true
if the user name is already set, and that record has the same id as user_id, that means you are updating the same record, and its fine to validate.
otherwise, another record has the username, and it should fail validation.
in the model i just have a method that returns the unique id for a username.
after you run the validation, its probably a good idea to unset the callback_user_id session variable.
i'm sorry i don't have code to paste, but i think this description should help you.
==== edits
nowadays, i think overriding the form validation with a new function is the way to go.
so: have a language pack entry, a form validation line and the override:
This assumes that a field is posted named ID that has the id of the row.
$lang['form_validation_is_unique_not_current'] ='The {field} field must contain a unique value.';
array('field' => 'username', 'label' => 'lang:…username…', 'rules' => 'trim|required|min_length[2]|max_length[40]|is_unique_not_current[users.username]'),
class MY_Form_validation extends CI_Form_validation {
function __construct($rules = array())
{
parent::__construct($rules);
$this->_error_prefix = '<div class="alert alert-danger"><p>';
$this->_error_suffix = '</p></div>';
}
public function is_unique_not_current($str, $field)
{
sscanf($field, '%[^.].%[^.]', $table, $field);
$id = $this->CI->input->post('id');
if($this->CI->input->post('field_name'))
{
return isset($this->CI->db)
? ($this->CI->db->limit(1)->get_where($table, array(
$field => $str,
'id <> ' => $id))->num_rows() === 0)
: FALSE;
}
return FALSE;
}
}

Categories