Blog

mAkismet 3.0

Simple Akismet library for CodeIgniter –

<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
 * mBlog Akismet library
 * Mark LaDoux <http://markladoux.com/>
 *
 * Inspired by the DBlog Akismet library
 * David Behler <http://www.davidbehler.de/>
 *
 * Provides functions and utilities for checking comments against Akismet
 * for spam, and for reporting spam and ham.
 *
 * Changes:
 *
 * Simplified initialization process
 * Added internationalization support for error logs
 * Added more tests for logging purposes
 * Added library version reporting
 * Updated for efficiency and simplified naming conventions
 * Removed depreciated code
 * Added API key validity check reporting
 * Not entirely backwards compatible with 2.x
 *
 * @version     3.0.0
 * @author      Mark LaDoux <me@markladoux.com>
 * @copyright   Copyright (c) 2012, Mark LaDoux
 * @license     http://www.gnu.org/licenses/gpl.html
 */

class mAkismet
{
    /**
     * Library Version
     *
     * @access  protected
     * @static  string
     */
    protected static $version = '3.0.0';

    /**
     * api version
     *
     * @access  protected
     * @static  string
     */
    protected static $api = '1.1';

    /**
     * User Agent String
     *
     * @access  protected
     * @static  string
     */
    protected static $user_agent;

    /**
     * WordPress api Key
     *
     * @access  protected
     * @static  string
     */
    protected static $api_key;

    /**
     * Valid api Key
     *
     * @access  protected
     * @static  bool
     */
    protected static $valid_key = FALSE;

    /**
     * Akismet Server Address
     *
     * @access  protected
     * @static  string
     */
    protected static $server = 'rest.akismet.com';

    /**
     * Users preferred langauge
     *
     * @access  protected
     * @static  string
     */
    protected static $lang;

    /**
     * Class Constructor
     *
     * Prepares the library for use
     *
     * @access  public
     * @return  void
     */
    public function __construct($args = array())
    {
        // load config files
        $this->load->config('mAkismet', TRUE);

        // Configure server address
        if(
            $this->config->item('server', 'mAkismet') !== FALSE &&
            $this->config->item('server', 'mAkismet') != '')
        {
            self::$server = $this->config->item('server', 'mAkismet');
        }

        // configure api key
        self::$api_key = $this->config->item('api_key', 'mAkismet');

        // configured preferred language
        // ( only the admin will recieve these errors )
        self::$lang = $this->config->item('language');

        // configure User Agent
        self::$user_agent = 'CodeIgniter/'.CI_VERSION.' | mAkismet/'.self::$version;

        // override config values
        if(isset($args['server'])) self::$server = $args['server'];
        if(isset($args['api_key']))

        // load language files TODO
        // $this->lang->load('mAkismet', self::$lang);

        // verify api key
        $post_values['key']     = self::$api_key;
        $post_values['blog']    = site_url();
        $response = $this->$_request(
            $this->_query_string($post_values),
            self::$server,
            '/'.self::$api.'/verify-key');
        self::$valid_key = (strtolower($response[1]) == 'valid') ? TRUE : FALSE;
    }

    /**
     * Magic Get
     *
     * Allows use of the CI SuperGlobal without defining extra variables
     *
     * @access  public
     * @param   string
     * @return  object
     */
    public function __get($object)
    {
        return get_instance()->$object;
    }

    /**
     * Verify API key (testing purposes)
     *
     * @access  public
     * @return  bool
     */
    public function verify_api_key()
    {
        return self::$valid_key;
    }

    /**
     * Reports the version of the library being used
     *
     * @access  public
     * @return  string
     */
    public function version()
    {
        return self::$version;
    }

    /**
     * Checks if a comment is spam
     *
     * @access  public
     * @param   array
     * @return  bool
     */
    public function is_spam($comment)
    {
        $output = FALSE;
        if(self::$valid_key)
        {
            // prepare array for query generation
            $post['blog']                   = site_url();
            $post['user_ip']                = $this->input->ip_address();
            $post['user_agent']             = $this->input->user_agent();
            $post['referrer']               = $this->input->server('HTTP_REFERER');
            $post['comment_type']           = $comment['comment_type'];
            $post['comment_author']         = $comment['comment_author'];
            $post['comment_author_email']   = $comment['comment_author_email'];
            $post['comment_author_url']     = $comment['comment_author_url'];
            $post['comment_content']        = $comment['comment_content'];
            $post['permalink']              = $comment['permalink'];

            // get server response
            $response = $this->_request(
                $this->_query_string($post),
                self::$api_key.'.'.self::$server,
                '/'.self::$api.'/comment-check'
                );

            // set output based on response
            $output =  (strtolower($response[1]) == 'true') ? TRUE : FALSE;
        }
        else
        {
            // log errors
            log_message('error',$this->lang->line('akismet_api_error'));
        }
        return $output;
    }

    /**
     * Submit a comment to the Akismet server as spam
     *
     * @access  public
     * @param   array
     * @return  void
     */
    public function submit_spam($comment)
    {
        if(self::$valid_key)
        {
            // prepare array for query generation
            $post['blog']                   = site_url();
            $post['user_ip']                = $this->input->ip_address();
            $post['user_agent']             = $this->input->user_agent();
            $post['referrer']               = $this->input->server('HTTP_REFERER');
            $post['comment_type']           = $comment['comment_type'];
            $post['comment_author']         = $comment['comment_author'];
            $post['comment_author_email']   = $comment['comment_author_email'];
            $post['comment_author_url']     = $comment['comment_author_url'];
            $post['comment_content']        = $comment['comment_content'];
            $post['permalink']              = $comment['permalink'];

            // submit to server
            $this->_request(
                $this->_query_string($post),
                self::$api_key.'.'.self::$server,
                '/'.self::$api.'/submit-spam'
                );
        }
        else
        {
            // log errors
            log_message('error',$this->lang->line('akismet_api_error'));
        }
    }

    /**
     * Submit comment to the Akismet server as ham
     *
     * @access  public
     * @param   array
     * @return  void
     */
    public function submit_ham($comment)
    {
        if(self::$valid_key)
        {
            // prepare array for query generation
            $post['blog']                   = site_url();
            $post['user_ip']                = $this->input->ip_address();
            $post['user_agent']             = $this->input->user_agent();
            $post['referrer']               = $this->input->server('HTTP_REFERER');
            $post['comment_type']           = $comment['comment_type'];
            $post['comment_author']         = $comment['comment_author'];
            $post['comment_author_email']   = $comment['comment_author_email'];
            $post['comment_author_url']     = $comment['comment_author_url'];
            $post['comment_content']        = $comment['comment_content'];
            $post['permalink']              = $comment['permalink'];

            // submit to server
            $this->_request(
                $this->_query_string($post),
                self::$api_key.'.'.self::$server,
                '/'.self::$api.'/submit-ham'
                );
        }
        else
        {
            // log errors
            log_message('error',$this->lang->line('akismet_api_error'));
        }
    }

    /**
     * Generate a query string from an array
     *
     * @access  protected
     * @param   array       $post_values
     * @return  string
     */
    protected function _query_string($post_values)
    {
        // initialize query string
        $query_string = '';

        // generate query string
        foreach ($post_values as $key => $value) {
            if($query_string == '')
            {
                $query_string .= $key.'='.urlencode($value);
            }
            else
            {
                $query_string .= '&'.$key.'='.urlencode($value);
            }
        }

        // return completed string for processing
        return $query_string;
    }

    /**
     * Send a request to Akismet server and return the results as an array
     *
     * @access  protected
     * @param   string      $query_string
     * @param   string      $host
     * @param   string      $path
     * @param   string      $port
     * @return  array
     */
    protected function _request($query_string, $host, $path, $port = 80)
    {
        // initialize the request
        $req =  "POST {$path} HTTP/1.0n";
        $req .= "Host: {$host} n";
        $req .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8n";
        $req .= "Content-Length: ".strlen(self::$user_agent)."n";
        $req .= "User-Agent: {self::$user_agent}nn";
        $req .= $query_string;

        //initialize the response container.
        $response = '';

        // retrieve the response
        if(($fs = @fsockopen($host, $port, $errno, $errstr, 3)) !== FALSE)
        {
            fwrite($fs, $req);
            while(!feof($fs))
            {
                $response .= fgets($fs, 1160);
            }
            fclose($fs);
            $response = explode("n", $response, 2);
        }

        // return response for processing
        return $response;
    }
}

/* End of file mAkismet.php */
/* Location: ./application/libraries/mAkismet.php */
Software

Quick and Easy CodeIgniter Database Connection Test!

I’ve been working on an installer for a CodeIgniter application that I’m building. The thing is, it needs to be database agnostic. If you have seen my previous two CodeIgniter script, you can see that I’ve been boning up on this subject, and now is the time to put it to work. I’ve played around with activerecord, and dborge, and I’ve had some success. With the installer, however, I’ve run into an issue. I need a database agnostic way of checking the connection details without throwing that stupid database error page. I think I found one, utilizing the dbutil class.

Some people might ask me why not just use the conn_id property. Well three reasons, I don’t know if it’s truly database agnostic, I don’t have access to every type of database that CodeIgniter supports, and it doesn’t check to see if the table is there. I could check for the table afterwards, but, that doesn’t really matter at this point. I needed a simple way that would check all three, and this one seems to work so far.

Now, I’m assuming at this point that the database configuration is not yet written, and I don’t want to go through the trouble of having my script edit it before knowing if the database details even work. On the bonus, if db_debug isn’t defined, it defaults to FALSE. I can work with that, and it makes my job easier. For this process I do a couple of things after getting the database details. I override the database configuration file entirely by passing along a DSN string instead. This way, debugging never comes into the picture, and we don’t have to worry about writing the configuration until we know we got all our facts straight. The end result is a script so simple that it must be a sin. Anyway, on with the script:

// Format DSN
$dsn = 'mysqli://username:password@localhost/database';

// Load database and dbutil
$this->load->database($dsn);
$this->load->dbutil();

// check connection details
if(! $this->dbutil->database_exists('database'))
{
	// if connection details incorrect show error
	echo 'Incorrect database information provided';
}

That script is just a test script, but it should give developers enough to go on in order to customize it for their own purposes. What it does is attempt to connect to the database server, load up dbutil, and then checks the database server to see if the requested database exists on it. If any part of this operation fails, you’ll get the error. Anyway, have fun, and I hope this helps you out on a project of your own.

Blog

Updated Theme

Installed a new them that is more conducive for displaying source code. I realize that I like to script a lot of little libraries and such, and I hate having that little scroll bar forcing you to have to side scroll to see everything. Solution? Get a wider theme. Done. Enjoy!

Software

mAuth Authentication Library

Introducing mAuth, my user authentication library for CodeIgniter.

What it does

mAuth creates, deletes, and verifieACs user authentication information. It does not handle sessions, cookies, or ACL. This is on purpose, I wanted to make it bare bones so that it can be easily adapted to any user management schema.

Installation Instructions

I’m going to skip over the database configuration this time around, but you’ll need to first configure your database. Next you need to create two files:

( Configuration file: application/config/mauth.php )

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

$config['mauth_table']	= 'users';
$config['mauth_rounds']	= 12;

( Library file: application/libraries/mauth.php )

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * mAuth Authentication Library
 *
 * Simple authentication library for CodeIgniter 2. This class does not
 * make any attempts at handling access control, so you will need another
 * library or helper to do that for you. This library also doesn't perform any
 * cookie or session management, that should be left up to your individual
 * application. This is a simple drop in user creation and verification library.
 *
 * @author      Mark LaDoux <mark.ladoux@gmail.com>
 * @version     20120708
 * @copyright   Copyright (c) 2012, Mark LaDoux
 */

class mAuth
{
    /**
     * CodeIgniter Object
     *
     * @access  protected
     * @since   20120708
     * @var     object
     */
    protected $ci;

    /**
     * Database table to use
     *
     * @access  protected
     * @since   20120708
     * @var     string
     */
    protected $table;

    /**
     * Number of iterations to process hash with
     *
     * @access  protected
     * @since   20120708
     * @var     int
     */
    protected $rounds;

    /**
     * Class Constructor
     *
     * Initializes the class for first use!
     *
     * @access  public
     * @since   20120708
     * @return  void
     */
    public function __construct()
    {
        // get CodeIgniter instance
        $this->ci =& get_instance();

        // retrieve settings
        $this->ci->config->load('mauth');
        $this->table    = $this->ci->config->item('mauth_table');
        $this->rounds   = $this->ci->config->item('mauth_rounds');

        // verify secure settings for $this->rounds
        if($this->rounds < 4 || $this->rounds > 32) $this->rounds = 8;

        // load dependancies
        $this->ci->load->database();

        // Install database if necessary
        if(! $this->ci->db->table_exists($this->table))
        {
            $this->_install();
        }
    }

    /**
     * Install database table
     *
     * @access  protected
     * @since   20120708
     * @return  void
     */

    protected function _install()
    {
        // load dbforge
        $this->ci->load->dbforge();

        // prepare fields
        $fields = array(
            'user_id'   => array(
                'type'              => 'INT',
                'unsigned'          => TRUE,
                'auto_increment'    => TRUE,
                ),
            'username'  => array(
                'type'          => 'VARCHAR',
                'constraint'    => '50',
                ),
            'email'     => array(
                'type'          => 'VARCHAR',
                'constraint'    => '255',
                ),
            'password'  => array(
                'type'          => 'CHAR',
                'constraint'    => '60',
                ),
            );
        $this->ci->dbforge->add_field($fields);

        // configure indexes
        $this->ci->dbforge->add_key('user_id', TRUE);
        $this->ci->dbforge->add_key('username');
        $this->ci->dbforge->add_key('email');

        // create table
        $this->ci->dbforge->create_table($this->table, TRUE);
    }

    /**
     * Generate password hash
     *
     * Generates a password hash using the bcrypt algorithm
     *
     * @access  protected
     * @since   20120708
     * @param   string      $password   plaintext password to hash
     * @return  string                  hashed password
     */
    protected function _hash_pass($password)
    {
        // generate a salt
        $lowercase      = str_shuffle('abcdefghijklmnopqrstuvwxyz');
        $uppercase      = str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
        $other          = str_shuffle('./');
        $legal_chars    = str_shuffle($lowercase.$uppercase.$other);
        $salt           = '';

        for($i = 0; $i < 22; $i++)
        {
            $salt .= $legal_chars[mt_rand(0,63)];
        }

        // format salt
        $salt = sprintf('$2a$%02d$', $this->rounds).str_shuffle($salt);

        // return hash
        return crypt($password, $salt);
    }

    /**
     * Verify password hash
     *
     * Checks plaintext password against stored hash to see if it is valid
     *
     * @access  protected
     * @since   20120708
     * @param   string      $password   plaintext password to verify
     * @param   string      $stored     hash from database to check against
     * @return  bool
     */
    protected function _verify_pass($password, $stored)
    {
        $check = (crypt($password, $stored) == $stored) ? TRUE : FALSE;
        return $check;
    }

    /**
     * Check if username is in use
     *
     * @access  public
     * @since   20120708
     * @param   string      $username   username to check
     * @return  bool
     */
    public function check_user($username)
    {
        $this->ci->db->where('username', $username);
        $query = $this->ci->db->get($this->table);

        // check if the username is in use
        if($query->num_rows() > 0)
        {
            // username is in use
            return TRUE;
        }

        // username is not in use
        return FALSE;
    }

    /**
     * Check if email address is in use
     *
     * @access  public
     * @since   20120708
     * @param   string      $email  email address to check
     * @return  bool
     */
    public function check_email($email)
    {
        $this->ci->db-where('email', $email);
        $query = $this->ci->db->get($this->table);

        // check if email is in use
        if($query->num_rows() > 0)
        {
            // email is in use
            return TRUE;
        }

        // email is not in use
        return FALSE;
    }

    /**
     * Create user
     *
     * @access  public
     * @since   20120708
     * @param   string      $username   username to set
     * @param   string      $email      email address to set
     * @param   string      $password   password to set
     * @return  bool
     */
    public function create_user($username, $email, $password)
    {
        // check to make sure username is not taken
        if($this->check_username !== FALSE)
        {
            return FALSE;
        }

        // check to make sure email address is not taken
        if($this->check_email !== FALSE)
        {
            return FALSE;
        }

        // check to make sure email is valid
        if(! filter_var($email, FILTER_VALIDATE_EMAIL))
        {
            return FALSE;
        }

        // prepare our password hash
        $hash   = $this->_hash_pass($password);

        // prepare user information
        $data = array(
            'username'  => $username,
            'email'     => $email,
            'password'  => $hash,
            );

        // add user to the database
        $this->ci->db->insert($this->table, $data);
        return TRUE;
    }

    /**
     * Verify user
     *
     * This function is set up to verify a user with either a username or an
     * email address, and will check for which you are using automatically so
     * that you can set up your application to operate however you prefer.
     *
     * @access  public
     * @param   string  $user       username or email address to verify
     * @param   string  $password   password for the user to verify
     * @return  bool
     */
    public function verify_user($user, $password)
    {
        $valid = FALSE;

        if(filter_var($user, FILTER_VALIDATE_EMAIL))
        {
            $this->ci->db->where('email', $user);
        }
        else
        {
            $this->ci->db->where('username', $user);
        }
        $query  = $this->ci->db->get($this->table);

        if($query->num_rows() > 0)
        {
            $row = $query->result();
            $valid = $this->_verify_pass($password, $row->password);
        }

        return $valid;
    }

    /**
     * Change password
     *
     * This application is set up to change a password using the username
     * or email address, and will check for which you are using automatically
     * so that you can set up your application however you prefer.
     *
     * @access  public
     * @since   20120708
     * @param   string      $user       user to update password for
     * @param   string      $password   new password to set
     * @return  void
     */
    public function change_password($user, $password)
    {
        // prepare hash
        $hash   = $this->_hash_pass($password);
        $data   = array('password' => $hash);

        // prepare where statement
        if(filter_var($user, FILTER_VALIDATE_EMAIL))
        {
            $this->ci->db->where('email', $user);
        }
        else
        {
            $this->ci->db->where('username', $user);
        }

        // update password
        $this->ci->db->update($this->table, $hash);
    }

    /**
     * Retrieve user info
     *
     * This function will retrieve user info using either an username or
     * email address. It will check which you are using automatically so
     * that you can set up your application to use whatever method you prefer.
     * This function will also strip password information from the array, so
     * that you don't have to worry about it accidentally getting out.
     *
     * @access  public
     * @param   string  $user   username or email address to retrieve info for
     * @return  array
     */
    public function user_info($user)
    {
        // retrieve data
        if(filter_var($user, FILTER_VALIDATE_EMAIL))
        {
            $this->ci->db->where('email', $user);
        }
        else
        {
            $this->ci->db->where('username', $user);
        }
        $query  = $this->ci->db->get($this->table);
        $row    = $query->result();

        // prepare data
        $data = array(
            'user_id'   => $row->user_id,
            'username'  => $row->username,
            'email'     => $row->email,
            );

        // return data for further processing
        return $data;
    }

    /**
     * Delete user
     *
     * This function will delete a user using either the username or the
     * email address. It will check which you are using automatically, so that
     * you can set up your application to use whichever that you prefer.
     *
     * @access  public
     * @param   string  $user   username or email address of the user to delete.
     * @return  void
     */
    public function delete_user($user)
    {
        if(filter_var($user, FILTER_VALIDATE_EMAIL))
        {
            $this->ci->db->where('email', $user);
        }
        else
        {
            $this->ci->db->where('username', $user);
        }
        $this->ci->db->delete($this->table);
    }
}

As I did with my last library, and plan on doing with all future libraries, this library is database agnostic. You don’t need to worry about creating the database tables or anything. As long as your database is properly configured in CodeIgniter, this library will install the appropriate database tables the first time it’s run. By using DBForge, I’ve made it so that it will work with any database that CodeIgniter has appropriate drivers for. Enjoy!