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

cPanel + Redmine 1.2.1 – Pages delivered as text/plain when using rewrite

10 Oct 2011 T06:38:52pm - Dazz - 0 Comment(s)

Well.

Bit more of a trouble than I wish to deal with today, but apparently Mongrel has changed somewhat in the latest version and doesn't deliver content as HTML, instead delivering it as text/plain (source html).

If you are using Mongrel to deliver your Ruby Apps, and you're sure your versions of gems meet the requirements - take a look at the below to try.

Add this file in, restart your application - and hopefully away you go. Again there is a lot of read/take in when it comes to dealing with this issue - hopefully I'm saving you about 1.5 hours of research. Original Source for the Patch I've forked..

Add this to redmine/config/initializers/mongrel.rb (create a new file)

# Pulled right from latest rack. Old looked like this in 1.1.0 version.
  # 
  # def [](k)
  #   super(@names[k] ||= @names[k.downcase])
  # end
  # 
  module Rack
    module Utils
      class HeaderHash < Hash
        def [](k)
          super(@names[k]) if @names[k]
          super(@names[k.downcase])
        end
      end
    end
  end
  
  # Code pulled from the ticket above.
  # 
  class Mongrel::CGIWrapper
    def header_with_rails_fix(options = 'text/html')
      @head['cookie'] = options.delete('cookie').flatten.map { |v| v.sub(/^\n/,'') } if options.class != String and options['cookie']
      header_without_rails_fix(options)
    end
    alias_method_chain :header, :rails_fix
  end
  
  # Pulled right from 2.3.8 ActionPack. Simple diff was
  # 
  # if headers.include?('Set-Cookie')
  #   headers['cookie'] = headers.delete('Set-Cookie').split("\n")
  # end
  # 
  # to 
  # 
  # if headers['Set-Cookie']
  #   headers['cookie'] = headers.delete('Set-Cookie').split("\n")
  # end
  #       
  module ActionController
    class CGIHandler
      def self.dispatch_cgi(app, cgi, out = $stdout)
        env = cgi.__send__(:env_table)
        env.delete "HTTP_CONTENT_LENGTH"
        cgi.stdinput.extend ProperStream
        env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
        env.update({
          "rack.version" => [0,1],
          "rack.input" => cgi.stdinput,
          "rack.errors" => $stderr,
          "rack.multithread" => false,
          "rack.multiprocess" => true,
          "rack.run_once" => false,
          "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
        })
        env["QUERY_STRING"] ||= ""
        env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
        env["REQUEST_PATH"] ||= "/"
        env.delete "PATH_INFO" if env["PATH_INFO"] == ""
        status, headers, body = app.call(env)
        begin
          out.binmode if out.respond_to?(:binmode)
          out.sync = false if out.respond_to?(:sync=)
          headers['Status'] = status.to_s
          if headers['Set-Cookie']
            headers['cookie'] = headers.delete('Set-Cookie').split("\n")
          end
          out.write(cgi.header(headers))
          body.each { |part|
            out.write part
            out.flush if out.respond_to?(:flush)
          }
        ensure
          body.close if body.respond_to?(:close)
        end
      end
    end
  end

Categories: cPanel | Redmine

MySQL: Return dates between two dates – Make Intervals Procedure

06 Sep 2011 T01:13:55pm - Dazz - 2 Comment(s)

Full credit to Ron Savage over at Stack Exchange. He's written this fantastic function which creates a temporary table with two columns, 'interval_start' and 'interval_end'.

You call this function with a MySQL datetime ('2011-02-23 05:00:00'), and a MySQL end datetime ('2011-02-23 16:00:00'). You then specify what you want returned back to you (Seconds, Minutes, Hours, Days etc. See the function below for the full list of options).

What is returned is below will be similar;

interval_start interval_end
2011-02-23 05:00:00 2011-02-23 05:59:59
2011-02-23 06:00:00 2011-02-23 06:59:59
2011-02-23 07:00:00 2011-02-23 07:59:59
2011-02-23 08:00:00 2011-02-23 08:59:59
2011-02-23 09:00:00 2011-02-23 09:59:59
2011-02-23 10:00:00 2011-02-23 10:59:59
2011-02-23 11:00:00 2011-02-23 11:59:59
2011-02-23 12:00:00 2011-02-23 12:59:59
2011-02-23 13:00:00 2011-02-23 13:59:59
2011-02-23 14:00:00 2011-02-23 14:59:59
2011-02-23 15:00:00 2011-02-23 15:59:59
(more...)

Categories: MySQL | Programming

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

WordPress from a subdirectory served from a domain root

01 Jul 2011 T01:18:33am - Dazz - 3 Comment(s)

So the dilemma rose tonight when James wanted WordPress installed on his domain that he’s had since the dawn of time.  Awesome I say!  How exciting!  Get the zip, unzip it, set it up and go for it.

Well after fighting with Dreamhost for a little while on setting up new SQL database, I discovered there’s 5+ years worth of junk sitting in his root public html directory.  ”K dude, I’m just going to move these to a subdomain files. and you can start using that”.  Well, after posting for 5 years of links around the internet – he didn’t like that idea.

So I had this fantastic idea – install WordPress in a subdirectory and I’m sure we can find some .htaccess rewrite rules to compensate.  Turns out, it doesn’t appear to be a popular topic on the web.

(more…)

Categories: Programming | Web