Darren Nolan

Computer Tech… and stuff

CodeIgniter Model Validation and Rule Sets

12 Feb 2012 T05:28:46pm - Dazz - 3 Comment(s)

Update 1.0.3 19/02/12: Updated checking for validation variables and methods before calling them.
Update 1.0.2 14/02/12: Removed static from libraries context. Might add static support later.
Update 1.0.1 13/02/12: Changed so CI Form_Validation rules are run first.

So, once again been tasked to play with the mighty CodeIgniter and having myself some fun. Except I still don't, for the love of god, understand why default validation is defined within controllers and not the models you're about to play with.

And so, I present some simple awesomeness to do two things.

Firstly we can define Form Validation rules within our Model class. Secondly, we can do all custom callbacks or complex rules within the model as well. So under application/models/User.php

class User extends Model
{
    // Required to store Form_validation error Messages in.
    public $model_validate_error;
    
    // When declared $model_validate_<fieldname> will add additional rules
    public $model_validate_username = 'required|min_length[10]|max_length[50]';
    
    // Rest of your model logic here.  This will even work awesomely with PHPActiveRecord
    //
    //

    public function model_validate_username($string)
    {
        if (!validate_string_length_between($string, 4, 15)) {
            $this->model_validate_error = 'Username not between 4 and 15 characters';
            return FALSE;
        } else {
            return TRUE;
        }
    }
}

So here we're defining the rules for "username" and these are normal CodeIgniter Form_validation rules. To create custom functions (or callbacks within the model itself) you can use the public static function model_validate_. Do your logic within that function, set the validation error message if you have one, and return true/false. validate_string_length_between() was a little testing function I added to the bottom of my form validation extender, just cause.

Within your controller application/controllers/signup.php for exmaple

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Signup extends MY_Controller
{
    function index()
    {
        $this->load->library('form_validation');
        
        $this->load->model('User');  // Loading for model here for validation is not required, but I assume right after validation passed you'll be saving stuff.
        
        // Call validate_model functions in the [Modelname.field] style (sort of like is_unique for table checking)
        $this->form_validation->set_rules('username', 'Username', 'validate_model[User.username]');
        $this->form_validation->set_rules('password', 'Password', 'validate_model[User.password]');
        $this->form_validation->set_rules('email', 'Email Address', 'validate_model[User.email]');
        
        if ($this->form_validation->run()) {
            $this->view_data['message'] = "pass";
        } elseif ($this->input->post()) {
            $this->view_data['message'] = "failed";
        } else {
            $this->view_data['message'] = "not run";
        }
    }
}

One nice thing is that even after you declare validate_model on the email field, you don't need the rules already saved in the Model nor the extended callback function there. If it's there, it will be used. If it's not, it won't. Means you can setup your controllers today, and populate the rules tomorrow.

Your extending function application/libraries/MY_Form_validation.php

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
class MY_Form_validation extends CI_Form_validation
{
     
    public function set_rules($field, $label = '', $rules = '')
    {
        // Check if "validate_model" is a rule within our set of rules.  If it is, load up the model's rules
        // within the rule-set and continue on to CI's class.
         
        if (strlen($rules) != 0) {
            $rule_list = explode('|', $rules);
            if ($rule_list != FALSE) {
                foreach ($rule_list as $rule) {
                    if (substr($rule, 0, 15) == 'validate_model[') {
                        // Found a validate_model ruling.  Grab the class name and field type.
                        $rule = substr($rule, 15);
                        $rule = substr($rule, 0, -1);
                        $functionName = explode ('.', $rule);
                        if (count($functionName == 2)) {
                            $modelName = $functionName[0];
                            $variableName = 'model_validate_' . $functionName[1];
                            
                            $this->CI->load->model($modelName, 'validate_model'); // Load the model
                            
                            if (isset($this->CI->validate_model->$variableName)) {
                                $rules = $this->CI->validate_model->$variableName . '|' . $rules;
                            }
                        }
                    }
                }
            }
        }
        // Always continue with the default CI set_rules even if we can't work anything additional out.
        parent::set_rules($field, $label, $rules);
    }
     
    public function validate_model($input = FALSE, $model_field = FALSE)
    {
        $functionName = explode('.', $model_field);
        if (count($functionName) != 2) {
            // Unable to work out 'model.function' to call.
            return;
        }
         
        $modelName = $functionName[0];
        $methodName = 'model_validate_' . $functionName[1];
         
        $this->CI->load->model($modelName, 'validate_model'); // Load the model if it's not already loaded.
         
        if (method_exists($this->CI->validate_model, $methodName)) {
            //$result = call_user_func(array($modelName, $methodName), $input);
            $result = $this->CI->validate_model->$methodName($input);
            if ($result) {
                return TRUE;
            } else {
                $this->set_message("validate_model", $this->CI->validate_model->model_validate_error);
                $this->CI->validate_model->model_validate_error = NULL;
                return FALSE;
            }
        } else {
            log_message('debug', "Unable to find validation rule: $modelName -> $methodName");
            return;
        }
    }
}

// Validation Functions
if (!function_exists('validate_string_length_between')) {
    function validate_string_length_between($string, $min, $max)
    {
        if (strlen($string) >= $min AND strlen($string) <= $max) {
            return TRUE;
        } else {
            return FALSE;
        }
    }
}

So! I hope with this simple stuff, makes your controllers much neater, and rules and validation far more reusable than either saving your rules in a config file, or copy/pasting your rules between controllers and extending Form_validation with custom function callbacks.

Feedback welcome, let me know!

Categories: CodeIgniter | PHP

PHP and HTML – Keep it clean, keep it separate

01 Sep 2011 T04:11:04pm - Dazz - 2 Comment(s)

I've been helping out of couple of friends with their University assignments recently and one thing that has bugged me to no end, is how they're not being shown how to keep code clean and manageable.

Take the following example (which is close to what their University lecture's have been giving out as an example).

<?php
	$fruitArray	= array(
					"apple" => "green",
					"tomato" => "red", 
					"banana" => "yellow", 
					"grape" => "purple", 
					"orange" => "orange");
	
	echo "<table>";
	echo "<tr>";
	echo "<th>Fruit</th>";
	echo "<th>Colour</th>";
	echo "</tr>";
	ksort($fruitArray);
	foreach ($fruitArray as $fruit => $colour) {
		echo "<tr>";
		echo "<td>" . $fruit . "</td>";
		echo "<td>" . $colour . "</td>";
		echo "</tr>";
	}
	echo "</table>";
?>
(more...)

Categories: PHP | Programming

XAMPP, Eclipse and xDebug oh my!

11 May 2011 T01:43:59pm - Dazz - 4 Comment(s)

So I hope this gets into the world out there. I’m running the latest version possible of XAMPP for windows (shame on me for windows right?), Eclipse and xDebug.

One interesting thing that has frustrated the absolute hell out of me while working recently is that Apache continues to fail on every breakpoint in my code. “Apache has Stopped working”, and then silently restarts itself.

Sadly there is no further advise from Apache or PHP Logs even went making everything log to their fullest capacity.

And thus brings me to http://windows.fyicenter.com/view.php?ID=68&R=71 – Which is a fantastic little thing you can try on your local dev machines – which quickly and shamefully easily makes your PHP scripts run in php-cgi rather than the default setup of XAMPP.

I’m break-pointing again and everything is well in the land of New Zealand (Oh btw, I’m in New Zealand working my little coding ass off at the moment, should be back in the land of Oz early early June).

Cheers Chaps.

Categories: PHP | Programming

Email Contact Forms

03 Sep 2010 T04:00:26pm - Dazz - 5 Comment(s)

Yet another awesome service from google. reCaptcha. More on that later.  But firstly, sending from the right address.

Helping out a friend at her new(ish) job and their contact form simply sucked.  Suppose it takes a bit of a background in both PHP web coding and more so in understanding Mail protocols than anything else.

The first thing people really need to get out of the habit is trying to send emails as the person who wishes to contact you.  Take in this small example;

<?php
	$to = 'contact@mydomain.com';
	$subject = 'Website Contact Form';
	$message = $_POST['formMessage'];
	$headers = 'From: ' . $_POST['formEmailAddress'] . "\r\n";
	mail($to, $subject, $message, $headers);
?>

Without getting hooked up on using raw entries from $_POST, basically the above code will take your contact details and attempt to send it to yourself.   You write into the headers that this email is coming from whatever address the user entered when filling in the form.

THAT'S A BIG NO NO.  More and more mail servers today (including your own, which is where some of the issue comes) checks the IP and/or domain name of where an email is being sent from, in this case it's your webserver, and looks up that domain to see if that domain or IP address is allowed to pretend to be sending mail.  I promise no one has added in your domain details to allow to send email as them.  Certainly not the bigger dogs like Bigpond, Optus etc.

This is just one method that checks mail servers use today to check the authenticity of mail getting into people's boxes.  Otherwise you can basically be sending mail out as Google or even PayPal.

Here's what you should have done when coding your contact form.

<?php
	$to = 'contact@mydomain.com';
	$subject = 'Website Contact Form';
	$message = $_POST['formMessage'];
	$headers = 'From: contact@mydomain.com' . "\r\n" .
				'Reply-To: ' . $_POST['formEmailAddress'] . "\r\n" .
				'X-Mailer: PHP/' . phpversion();
	mail($to, $subject, $message, $headers);
?>

So how's this work now?  Basically, we're saying this email we're going to get is from our OWN domain.  However when you hit the reply to start a conversation with the user that filled in the contact form, your email client will use the details in the Reply-To.  Adding the X-Mailer section there is to let severs know that yes indeed this message has been sent from a PHP script.

Because your script is now sending from your website as your own email (typically where your mailserver resides) this solves some of the more common 550 errors in emails (Email error: 550 SPF: x.x.x.x is not allowed to send mail from ...)

Doing things properly you shouldn't stop there guys.  The really good and neat way of sending mail out of your web scripts is to connect to your mail server directly, and send it from there.  Need pear with it's mail modules installed for PHP.

If you don't already have this, you should lightly slap yourself and then quickly install it.

On Ubuntu:

sudo apt-get install php-pear
sudo pear install mail
sudo pear install Net_SMTP
sudo pear install Auth_SASL
sudo pear install mail_mime

Here's how our example would work now.

<?php
	include "Mail.php";
	$to = 'contact@mydomain.com';
	$headers['From']		= 'webmaster@mydomain.com';
	$headers['To']			= $to;
	$headers['Subject']		= 'Website Contact Form';
	$headers['Reply-To']	= $_POST['formName'] .
							'<' .$_POST['formEmailAddress'] . '>';

	$smtpinfo['host']		= 'localhost';
	$smtpinfo['port']		= '25';
	$smtpinfo['auth']		= true;
	$smtpinfo['username']	= 'my_mail_username';
	$smtpinfo['password']	= 'my_mail_password';

	$mail_message = $_POST['formMessage'];

	$mail_handler =& Mail::factory("smtp", $smtpinfo);
	$mail_handler->send($to, $headers, $mail_message);
?>

Naturally I'd expect you to validate your data and not just use raw data sent from the browser, but this will connect up to a SMTP server and send it off via that server (which lets your messages get signed by a range of other anti-spam measures in place today).

If you require an SSL connection to your mail server (for example, google handles your mail for you (like it does for me)) you can change the host to "ssl://hostname" and the Mail package will work it out from there.  Sadly it's not shown on the documentation about that handy little trick on the Pear page.

Another alternative is using sockets directly, which is fantastic and awesomes (and doesn't require Pear/Mail), and you can find a heap of PHP Libraries out there to give you access to do so.  Personally I'm a bit of a fan of CodeIgniter and has these sorts of things built in already.  Epic win.

GETTING SIDE TRACK HERE, but hopefully you'll start to use a neater way of sending your contact form messages out now.

Google reCaptcha adds that (sometimes) annoying little image to forms before you get to fill them in.  Basically ensuring you're human and not some spam bot with insecurities about it's epen requiring extensions (CWOTIDIDTHAR?).

It's actually really to get set up and working on your site, for anything really - not just contact forms (although it works quickly there too).  Basically you sign up for a Private and Public Key over at google and then download the quick library to use it.

After you've put it somewhere with your code files, you include the file, add some lines of code, and volia.  How easy is that.  See below;

<?php
	require_once('recaptchalib.php');
	$privatekey = "your_private_key"; // Change this to your Private Key
	$publickey = "your_public_key"; // Change this to your Public Key
 
	if (isset($_POST['login'])) {
		// Do some validation stuffs here!
 
		// Check the Captcha
		$resp = recaptcha_check_answer ($privatekey,
		$_SERVER["REMOTE_ADDR"],
		$_POST["recaptcha_challenge_field"],
		$_POST["recaptcha_response_field"]);
		if (!$resp->is_valid) {
			// reCaptcha failed. Do stuffs
 
		} else {
			// reCaptcha all good. Do whatever else
 
		}
	}
?><html>
<head></head>
<body>
	<form method="POST" action="<?=$_SERVER['PHP_SELF'];?>">
		<input type="text" name="username" /> <br />
		<input type="password" name="password" /> <br />
		<?=recaptcha_get_html($publickey);?>
		<input type="submit" name="login" />
	</form>
</body>
</html>

You can change the "look" or theme of reCaptcha by adding a small snippet of javascript before the element appears;

<script type="text/javascript">
	var RecaptchaOptions = {
		theme : 'clean'
	};
</script>

The element appears in a div with the ID of "recaptcha_widget_div" so if you want to adjust margins or padding for that div it appears in (being as lazy as me and NOT wanting to have more container divs than I can poke a stick at) you can change that in your own CSS stylesheets.

And that's about it for the moment. Hope I killed loads of your time and you wish for it back.  ^_^

Categories: PHP | Programming