I am using modular MVC with codeigniter. I have a module playlist in which I have a admin controller and I am having a private $rules variable for setting the form validation rules.
I have both create and edit functions in the same file and validating both the forms (add,edit which are also created dynamically from only one file form.php).
$this->load->library('form_validation');
$this->form_validation->set_rules($this->rules);
$this->form_validation->set_error_delimiters(' <p class="error">', '</p>');
These are used in both create and edit functions. some of the fields i dont want to validate in edit mode. Should I need to create different private rules for both of them or is there any better way to handle this in codeigniter since I am new to it. I want to remove validation for FILE tag as user dont need to upload at edit mode.
Thanks
Here is an answer from the CI forums (original link).
You can define the rules for create/edit using some form of heirachy, then;
<?php
$this->form_validation->set_group_rules('createModule');
$this->form_validation->set_group_rules('editModule');
if($this->form_validation->run() == FALSE) {
// whatevere you want
}
?>
Or, you could do this;
<?php
// This will validate the 'accidentForm' first
$this->form_validation->set_group_rules('createModule');
if($this->form_validation->run() == FALSE) {
// whatevere you want
}
// Now we add the 'locationForm' group of rules
$this->form_validation->set_group_rules('editModule');
// And now we validate *both* sets of rules (remember that the createModule rules are still
// there), but it doesn't necessarily matter, since it will simply redo the 'createModule'
// validation while also doing the 'editModule' validation
if($this->form_validation->run() == FALSE) {
// whatevere you want
}
?>
Below is the code for the extended Form_validation class, saved in the application libraries folder as MY_Form_validation.php
<?php
class MY_Form_validation extends CI_Form_validation {
/**
* Set Rules from a Group
*
* The default CodeIgniter Form validation class doesn't allow you to
* explicitely add rules based upon those stored in the config file. This
* function allows you to do just that.
*
* #param string $group
*/
public function set_group_rules($group = '') {
// Is there a validation rule for the particular URI being accessed?
$uri = ($group == '') ? trim($this->CI->uri->ruri_string(), '/') : $group;
if ($uri != '' AND isset($this->_config_rules[$uri])) {
$this->set_rules($this->_config_rules[$uri]);
return true;
}
return false;
}
}
?>
Related
EDIT2: Scroll down for most up-to-date information!
In CodeIgniter 3, I recently moved callback rules across all of my controllers to application/libraries/MY_Form_validation.php to prevent code repetition etc., and to clean up.
Right now, they don't seem to be working anymore.
MY_Form_validation.php starts like this:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation
{
public $CI;
function run($module = '', $group = '')
{
log_message('debug',"Now running the MY_Form_validation");
(is_object($module)) AND $this->CI = &$module;
return parent::run($group);
}
Then a whole list of callback function follows, all defined as public function callback_name(){}.
I also have one (in the same class) which checks if the provided user login information is correct (and thus, if the user can login etc.), but the form validation can't find the rule.
The error log looks like this:
INFO - 2016-06-23 13:33:18 --> Form Validation Class Initialized
DEBUG - 2016-06-23 13:33:18 --> Now running the MY_Form_validation
INFO - 2016-06-23 13:33:18 --> Language file loaded: language/english/form_validation_lang.php
DEBUG - 2016-06-23 13:33:18 --> Unable to find callback validation rule: check_database
The first DEBUG message indicates that MY_Form_validation is loaded (as its overwritten run() method is used, hence the debug logging), but it somehow can't find the callback functions clearly defined below.
I also included language file application/language/english/form_validation_lang.php with the following line:
$lang['form_validation_check_database'] = 'The password does not match the username. Try again.';, which it catches correctly (i.e. this message is displayed when performing the form validation), but it somehow cannot find the callback function itself.
EDIT: I checked to see if methods weren't inherited correctly:
public function __construct($rules = array())
{
$this->CI =& get_instance();
var_dump(get_class_methods($this));
The var_dump() does output the correct, full array of methods, both my own custom callbacks and the built-in ones.
EDIT2:
I read the system/libraries/Form_validation.php and investigated where the debug message occurs, which can be seen in this code sample (line 734-749) :
// Call the function that corresponds to the rule
if ($callback OR $callable !== FALSE)
{
if ($callback)
{
if ( ! method_exists($this->CI, $rule))
{
log_message('debug', 'Unable to find callback validation rule: '.$rule);
$result = FALSE;
}
else
{
// Run the function and grab the result
$result = $this->CI->$rule($postdata, $param);
}
}
It seems as though callbacks are only looked for in the main CI object, but not in the form validation library itself. I could add some hacky exceptions that would pick the library callbacks, but I doubt that that's the best thing to do and I guess I'm overlooking something simple...
If any additional info is required, please let me know.
Alright, I found out...
Apparently, as soon as you move callback functions to MY_Form_validation.php, they are actually built-in validation rules, and no longer act as callbacks.
When setting form rules, I still had the callback_ prefix applied, which makes the Form_validation library look for the rule in the normal CI object (i.e. in the controller) rather than the (MY_)Form_validation class.
The fix for me was to simply remove this prefix from the applied validation rule settings. Now it finds the 'callback' functions correctly.
Please Try This
<?php
class Form extends CI_Controller {
public function index()
{
$this->load->helper(array('form', 'url'));
$this->load->library('form_validation');
$this->form_validation->set_rules('username', 'Username', 'callback_username_check');
$this->form_validation->set_rules('password', 'Password', 'required');
$this->form_validation->set_rules('passconf', 'Password Confirmation', 'required');
$this->form_validation->set_rules('email', 'Email', 'required|is_unique[users.email]');
if ($this->form_validation->run() == FALSE)
{
$this->load->view('myform');
}
else
{
$this->load->view('formsuccess');
}
}
public function username_check($str)
{
if ($str == 'test')
{
$this->form_validation->set_message('username_check', 'The {field} field can not be the word "test"');
return FALSE;
}
else
{
return TRUE;
}
}
}
?>
I have recently dived into OOP & PHP MVC Application Design. At the moment I am learning a lot but I have one thing that is currently bugging me.
I read and now understand why it isn't wise to place http redirects within a service layer. We do not know what the controller will need to do once the service is complete, etc. etc. I also read that the service should not do anything outside of its purpose. Example: User Registration should only create a new user, using input passed by controller, but I am wondering if it is also fine to set flash messages within the service layer.
My application displays a lot of flash messages session based notifications for users. All of them are based on service related input validation checks, and produce alerts similar to the following
The username xxxxxx is already in use
Usernames Should be > 5 Characters
Should/can this be defined/set within the service class or is there something wrong with that? I have a Alert Helper function that handles setting the alerts. I can easily use my dependency injector to make it available I am just wondering if there is an issue with doing that.
I made the mistake of implementing all redirects within the services and I just finished removing all of them and placing them in the controllers, I don't want to make the same time consuming mistake so I am looking for advice here.
Thank you in advance for the help.
EDIT - CODE EXAMPLE
<?php
/**
*-----------------------------------------------------------------
*
* LOGIN CONTROLLER
*
*/
namespace Controller\www;
use \Helper\Controller;
class Login extends Controller {
public $dependencies = ['arena', 'login', 'site'];
/**
* Login
*
* Login Runs Through Various Checks Including If User is Banned, Account is Locked,
* or Forgot Password Request Is Active. Then the Entered Password is Matched & if Valid
* User is Logged In
*/
public function index() {
// Define Default
$username = '';
/**
* User Login
*
* If Successful, Login User, Redirect Home
* Else Set Error Alerts
*/
if ($this->form->post('login')) {
// Define and Sanitize Post Data
$username = $this->input->get('username');
$password = $this->input->get('password');
// Login Service Layer
$login = $this->factory->make('user/login');
// If Successful Redirect Home - Else Set Errors
if ($login->user($username, $password) === true) {
$this->redirect->home();
}
$this->alert->error($login->get('errors'));
}
/**
* Define Site Title & Display Page
*/
$this->view->sitetitle('login');
$this->view->display('www/login', [
'video' => $this->arena->video(),
'username' => $this->input->set($username)
], ['notifications' => 'user/forgotpassword']);
}
}
Service Layer
/**
*-----------------------------------------------------------------
*
* USER LOGIN SERVICE LAYER
*
*/
namespace Service\User;
use \Helper\Service;
class Login extends Service {
public $dependencies = ['login', 'mail', 'time', 'user', 'vars'];
/**
* Handles Entire Login Process For Site Users
*
* #params all User Submitted Form Data
*/
public function user($username = '', $password = '') {
// Validate $_POST Form Data
$this->validateInput($username, $password);
/**
* No Errors Produced - Complete Form Submission
*
* We Are Not Using `elseif` Between Forgot Password & Normal Login
* After a Forgot Password Code is Generated User May Remember Old Passwords
* We Need to Ensure Users Can Still Login Using Account Password As Well
*/
if (!$this->errors()) {
/**
* User Input Password Matches Account Password
*/
if ($this->input->verifyhash($password, $this->user->get('info.password'))) {
$this->login->user();
return true;
}
/**
* If We Have Not Been Redirected Login Was Unsuccessful
*/
$message = $forgotPW ? 'Forgot Password Code Invalid - Login Lost Incorrect' : 'Login Unsuccessful - Incorrect Username or Password';
$this->log->error($message, ['Username' => $username, 'Password' => $password]);
$this->error('Incorrect Username or Password');
}
/**
* If We Have Made It This Far Login Was Unsuccessful - Log Unsuccessful Attempt
*/
$this->login->logAttempt();
return false;
}
/**
* Validate $_POST Data
*
* #params all User Submitted Form Data
*/
private function validateInput($username = '', $password = '') {
// Display Error if Username is Empty
if (!$username) {
$this->error('Please enter a username');
}
// Display Error if Password is Empty
elseif (!$password) {
$this->error('Please enter a password');
}
// Search DB For User With Matching Username - If User Not Found Display/Log Error, Else Set User
else {
$user = $this->user->info($username, 'username', '', '`userid`');
if (!$user) {
$this->error('The username ' . $username . ' does not exist');
$this->log->error('User Not Found When Attempting to Login', ['username' => $username]);
} else {
$this->user->set('user', $user['userid']);
}
}
}
}
In order to answer your question, I think it's best to break down the concept of MVC into a very basic form, and its individual parts. I apologise in advance if this comes across as being somewhat condescending.
View
The view of the application displays anything and everything. If something is going to be displayed, it should be done in this layer
Controller
The controller is a mediator between the view and the model. It takes input from the view, applies logic/rules to it (where required), and interacts with the model to then get data to pass back to the view.
Model
This is where the loading and saving of data are done. The majority of the validation should have been done as part of the rules in the controller, and this should only pass details of any errors during loading or saving back to the controller should the arise. If there are no errors, it should return the relevant data, or a success status back to the controller.
With those points in mind, the model should not set flash messages to the session, that should be done within the controller depending on the result from the model.
Look at redirects and alerts as specific to one particular form of UI, and it should be obvious that there's no place for them in the Model. Simply always try to picture an alternative interface for your application; e.g. a command line interface for administrative tasks or a REST API. Redirects obviously have no place in either of these alternatives. Alerts are debatable... at the very least the form of the alert will be very different. Your Model will need to be able to pass back some status code to your Controller or View, and then it's the job of the Controller to react to "negative" events and the job of the View to visualise any alerts if necessary.
For example, your model may do something like this:
public function registerUser(User $user) {
...
if (!$successful) {
throw new EmailAlreadyRegisteredException;
}
return true;
}
The controller may then look like this:
public function userRegistration(Request $request) {
try {
$user = User::fromRequest($request);
$this->services->get('Users')->registerUser($user);
$this->view->render('registration_successful', $user);
} catch (InvalidUserData $e) {
$this->view->render('registration_form', $request, $e);
} catch (EmailAlreadyRegisteredException $e) {
$this->view->render('registration_failed', $user, $e);
}
}
The "alert" is passed around as an exception. It's just a method for the Model to signal to its callers what happened. It's up to the callers then to react to and visualise those events. You should certainly not expect any particular type of visualisation in the Model. So you don't want to hardcode specific HTML encoded messages or such. You don't even want to touch human languages at all, that's all the job of the View.
So I have this problem. I'm doing a server side validation and a jquery validation.
In server side validation what I do is to use codeigniter's form_validation library, more specifically:
$this->form_validation->set_rules('documentn', 'Passport number', 'required|min_length[7]|max_length[20]|is_natural|callback_checkDocAndUser');
which needs a return true or return false.
I have this user edit form, to change user data. But there are some restrictions... user when stored in database has a unique passport number. I need to be able to change this passport number if it's wrong... but passport numbers should not be repeated on the database.
This is the php function that is called from callback_checkDocAndUser :
public function checkDocAndUser(){
if ((isset($_POST['documentn'])) && (isset($_POST['id']))) {
$dn = UserManager::getInstance()->checkUserDocument($_POST['documentn'],$_POST['id']);
if ($dn) {
//passport belongs to the user
echo "true";
// return true;
}else{
//does the passport entered belong to another user?
$exists = UserManager::getInstance()->getByDocument($_POST['documentn']);
if (!$exists) {
//passport belongs to another user
echo "true";
// return true;
}else{
//passport number is free to use
echo "false";
// return false;
}
}
}
}
As you can see I put some "echo" in the functions. This is because I want to use the same function for a jQuery validation (which needs echo, doesn't work with "return").
documentn: {
required: true,
minlength: 7,
maxlength: 20,
remote: {
url: '/admin/checkDocAndUser',
type: 'POST',
data: {
id: function(){
return $('#id').val();
}
}
}
},
So how can I use the same function for both kind of validations...? is there a way to make jquery function receive a return..or codeigniter's function to receive an echo?
I do the same thing in my CodeIgniter projects and the solution is quite simple. The following answer is generically named where you only need to add your validation logic.
This answer follows the DRY principle where your validation code is not repeated, as well as CodeIgniter structure.
This MODEL does the actual validation for both CodeIgniter (server-side) validation and jQuery Validate (client-side) remote...
// file name 'models/demo_model.php'
class Demo_model extends CI_Model {
public function check_demo($params)
{
// insert your validation logic here...
// the entirety of your validation logic, check the DB, etc.
// if it passes validation
return TRUE;
// if it fails validation
return FALSE;
}
}
This CONTROLLER is only called by client-side remote for jQuery Validation...
// file name 'controllers/demo.php'
class Demo extends CI_Controller {
public function remote_demo($params = FALSE)
{
// call the Model to do the actual validation
$valid = $this->demo_model->check_demo($params);
if ($valid)
{
echo 'true'; // passes validation
}
else
{
echo 'false'; // fails validation
}
}
}
This LIBRARY is only used for server-side validation by CodeIgniter...
// file name 'libraries/MY_Form_validation.php'
class MY_Form_validation extends CI_Form_validation {
public function __construct()
{
parent::__construct();
$this->ci =& get_instance();
}
public function demo_check($params)
{
$this->ci->form_validation->set_message('demo_check', 'The %s is incorrect.');
// call the Model to do the actual validation
return $this->ci->demo_model->check_demo($params);
}
}
The way it's organized, you'll have ONE centrally located validation function residing with your CodeIgniter Models. I chose Models as the central location because the code is primarily interacting with the database.
The following uses the same Model function for both kinds of validation.
Server-side CodeIgniter validation: Calls the Model from MY_Form_validation and the Model will return TRUE or FALSE back to your other CodeIgniter Controllers as per your CodeIgniter validation rules.
Client-side jQuery Validate remote: Calls the Model from the Controller and the Model will return TRUE or FALSE back to the Controller. Then the Controller function will echo true or false based on this boolean response from the Model.
Validation functions always need to return a boolean value.
In your controller, try to retrieve the return value of the validation methods and echo "true" or "false" there.
Put an exit at the end of your function.
EDIT:
If you want to use the same set of validations for both client and server side validation, divide your call to two functions, one which handles client and the another which handles server. check the following code:
In you jquery function call url - admin/validate_form_client
function validate_form_client(){
$op = $this->checkDocAndUser($_POST['documentn'],$_POST['id']);
echo $op;
exit;
}
function validate_form_server(){
if ((isset($_POST['documentn'])) && (isset($_POST['id']))) {
return $this->checkDocAndUser($_POST['documentn'],$_POST['id']);
}
}
public function checkDocAndUser($documentn,$id){
$dn = UserManager::getInstance()->checkUserDocument($documentn,$id);
if ($dn) {
//id belongs to the user
return true;
}else{
//does the id entered belong to another user?
$exists = UserManager::getInstance()->getByDocument($documentn);
if (!$exists) {
// id number belongs to another user
return "true";
}else{
//id number is free to use
return "false";
}
}
}
}
Note: the given function names are just for example. please follow standard practice in the variable and function naming conventions.
In codeigniter I have used codeigniter form validation library in many controller,
ex: -
$this->form_validation->set_rules('rank', 'Rank', 'required');
If form validation failed, Then it displays
"The Rank field is required."
Here I want to display custom message,One method I can follow is
$this->form_validation->set_message('required', '%s can not be blank');
If I using this method then I need to repeat this code in every controller wherever I am using "required" form validation method
My question is there is any method to declare this custom message in configure file and automatically use this custom message whenever I using form validation rule "required"?
If you want to permanently over-ride the error message just hard code it into the form_validation_lang.php file.
(Located at system/language/english)
It you want the choice the extend the form_validation class adding your custom required function and add the appropriate error message in the lang file.
For example;
class MY_Form_validation {
public function custom_required($str) {
if ( ! is_array($str)) {
return (trim($str) == '') ? FALSE : TRUE;
} else {
return ( ! empty($str));
}
}
}
Then in the lang file;
$lang['custom_required'] = "%s can not be blank.";
From the CI docs themselves:
"All of the native error messages are located in the following language file: language/english/form_validation_lang.php
To set your own custom message you can either edit that file, or use the following function:
$this->form_validation->set_message('rule', 'Error Message');"
http://codeigniter.com/user_guide/libraries/form_validation.html#settingerrors
class MY_Form_validation extends CI_Form_validation {
public function __construct()
{
parent::__construct();
}
function required_select($input)
{
$this->set_message('required_select','select %s');
return FALSE;
}
}
Works for me : )
I am having a web project where some access decisions are dependant on the page itself (e.g. /logout which shall only be visible to logged in users) and some are dependant on dynamic model objects (e.g. /article/delete/1234 where we have to check if 1234 was written by the logged in user or if he is an admin).
Now, I am facing the problem of how to bring both things together. No matter how I tried, I cannot rely on any of the two alone:
Some pages do not use any models, so I cannot setup any model rules there
On the other hand, I cannot create dynamic assertions for a modular approach, because Comment is just a comment and not a default/comment. A Comment is not restricted to the default module, it may also be used in the admin module.
With modular ACL I am trying to check for each page if a user is allowed to visit it, e.g.
if (!$acl->isAllowed($user, 'default/secrets', 'mysecrets')) {
$this->forward('auth', 'login');
$this->setDispatched(false);
}
And with dynamic assertions I am checking if somebody is allowed to edit a specific model object.
// $comment has a method getResourceId() returning 'comment'
if ($acl->isAllowed($user, $comment, 'delete')) {
// display a link for deletion
}
Of course it would be nice if the check for
deletion of a specific comment, and
accessing the /comment/delete/???? page
would be the same, but I guess this is not possible and I would have to create two rules:
$acl->allow('member', 'default/comment', 'delete');
$acl->allow('member', 'comment', 'delete', new Acl_Assert_CommentAuthor());
$acl->allow('admin', 'comment', 'delete');
Now, this seems not perfect to me as this can lead to duplicate work in some cases.
Is there some better method to approach this problem? Or is the only method to at least create a coherent naming scheme like: mvc:default/comment, model:comment
The way i did it, custom sql queries that restrict results, functions that check before insert/delete/modify sql
and then an ACL plugin i wrote that checks permission and uses these 5 tables
acl_groups
acl_permession
acl_permession_groups
acl_permession_risource
acl_risource
Code:
class Abc_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
/**
* Return whether a given request (module-controller-action) exists
*
* #param Zend_Controller_Request_Abstract $request Request to check
* #return boolean Whether the action exists
*/
private function _actionExists(Zend_Controller_Request_Abstract $request)
{
$dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
// Check controller
if (!$dispatcher->isDispatchable($request)) {
return false;
}
$class = $dispatcher->loadClass($dispatcher->getControllerClass($request));
$method = $dispatcher->formatActionName($request->getActionName());
return is_callable(array($class, $method));
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
// fetch the current user
$controller = $request->controller;
$action = $request->action;
$logger = Zend_Registry::get('log');
//prima controlla se sei autenticato e poi controlla se l'azione esiste, cosi non esponi il sito
$auth = Zend_Auth::getInstance(); /* #var $auth Zend_Auth */
if($auth->hasIdentity()) {
if(! $this->_actionExists($request))//l'azione non esiste?
{
$request->setControllerName('error');
$request->setActionName('pagenotfound');
$logger->notice( " IP: ". $_SERVER['REMOTE_ADDR']. " http://".$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]. " ?" .http_build_query($_REQUEST));
return ;
}
$allowed = Abc_Action_Helper_CheckPermission::checkPermission($controller, $action);
if ($allowed !== 1)
{
$request->setControllerName('error');
$request->setActionName('noauth');
}
//fine azione esiste
}else{
$request->setControllerName('user');
$request->setActionName('login');
return ;
}
}//fine preDispatch
}
You can then add your code(which i ommited for shortness) to remember the request and redirect you there after login.