Password Validator
<?php
/**
* Squiloople Framework
*
* LICENSE: Feel free to use and redistribute this code.
*
* @author Michael Rushton <michael@squiloople.com>
* @link http://squiloople.com/
* @category Squiloople
* @package Models
* @subpackage Validators
* @version 1.0
* @copyright © 2011 Michael Rushton
*/
// Define the namespace
namespace Models\Validators;
/**
* Password Validator
*
* Hash or validate passwords
*/
final class PasswordValidator
{
/**
* The password
*
* @access private
* @var string $_password
*/
private $_password;
/**
* The salt
*
* @access private
* @var string $_salt
*/
private $_salt;
/**
* The pepper
*
* @access private
* @var string $_pepper
*/
private $_pepper = 'Sz^3X6r[UyvV~2]_0stT}8 uY7RwZx4{q|Q91W5';
/**
* Set the password
*
* @access public
* @param string $password
*/
public function __construct($password = '')
{
$this->_password = $password;
}
/**
* Call the constructor fluently
*
* @access public
* @static
* @param string $password
* @return \Models\Validators\PasswordValidator
*/
public static function setPassword($password)
{
return new self($password);
}
/**
* Return the password
*
* @access public
* @return string
*/
public function getPassword()
{
return $this->_password;
}
/**
* Create a random password
*
* @access public
* @return \Models\Validators\PasswordValidator
*/
public function randomizePassword()
{
// If the password is incorrectly formed then randomize again
if (!$this->isValid($this->_password = substr($this->getSalt(true), 0, 8)))
{
$this->randomizePassword();
}
// Reset the salt
$this->_salt = null;
// Return the object
return $this;
}
/**
* Validate the password
*
* @access public
* @param bool|string $new
* @return bool
*/
public function isValid($new = false)
{
// The password must contain at least one character of each case, one digit, and be between 8 and 39 characters inclusive in length
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[\x20-\x7E]{8,39}$/D', $new ?: $this->_password))
{
return false;
}
// Otherwise return true
return true;
}
/**
* Set the salt
*
* @access public
* @param string $salt
* @return \Models\Validators\PasswordValidator
*/
public function setSalt($salt)
{
// Set the salt
$this->_salt = $salt;
// Return itself
return $this;
}
/**
* Generate and return a salt
*
* @access public
* @param bool $reset
* @return string
*/
public function getSalt($reset = false)
{
// If a salt has been set and a reset is not required then return the stored salt
if (!$reset && isset($this->_salt))
{
return $this->_salt;
}
// Reset the salt
$salt = '';
// Generate a random salt of 39 printable ASCII characters
for ($i = 1; $i <= 39; ++$i)
{
$salt .= chr(mt_rand(32, 126));
}
// If the salt does not have the correct syntax then regenerate
if (!$this->isValid($salt))
{
$this->getSalt(true);
}
// Return the random salt
return $this->_salt = $salt;
}
/**
* Return the pepper portion
*
* @access private
* @return string
*/
private function _getPepper()
{
return substr($this->_pepper, 0, 39 - strlen($this->_password));
}
/**
* Hash the password
*
* @access private
* @return string
*/
private function _getHash()
{
return hash('sha384', $this->getSalt() . $this->_password . $this->_getPepper());
}
/**
* Hash the password using an HMAC-inspired hash
*
* @access public
* @return string
*/
public function getHash()
{
return hash('sha512', substr(strrev($this->_pepper), 0, 20) . $this->_getHash());
}
}
(Pseudo-) Database Class
This class is designed to be used with the MySQLi Classes provided in an earlier post
<?php
/**
* Squiloople Framework
*
* LICENSE: Feel free to use and redistribute this code.
*
* @author Michael Rushton <michael@squiloople.com>
* @link http://squiloople.com/
* @category Squiloople
* @package Models
* @subpackage Databases
* @version 1.0
* @copyright © 2011 Michael Rushton
*/
// Define the namespace
namespace Models\Databases;
/**
* Database
*
* Acts as a database for simple queries
*/
final class Database
{
/**
* An instance of \Models\MySQLi\MySQLiConnection
*
* @access private
* @var \Models\MySQLi\MySQLiConnection $_connection
*/
private $_connection;
/**
* An array of the data
*
* @access private
* @var array $_data
*/
private $_data = array();
/**
* Store an instance of \Models\MySQLi\MySQLiConnection
*
* @access public
* @param string $hostname
* @param string $username
* @param string $password
* @param string $database
*/
public function __construct()
{
$this->_connection = call_user_func_array('\Models\MySQLi\MySQLiConnection::setConnection', func_get_args());
}
/**
* Insert a row with the given data
*
* @access public
* @param string $table
* @return \Models\Databases\Database
*/
public function insert($table)
{
// Prepare the statement
$stmt = $this->_connection->prepareInsertStatement()
->addTable($table);
// Prepare, bind, and execute the query
$stmt = $this->_executeQuery($stmt, __FUNCTION__);
// Store the new row's ID
$this->_data['id'] = $stmt->getInsertID();
// Return the \Models\Databases\Database object
return $this;
}
/**
* Delete the row with the given data
*
* @access public
* @param string $table
* @return \Models\Databases\Database
*/
public function delete($table)
{
// Prepare the statement
$stmt = $this->_connection->prepareDeleteStatement()
->addTable($table)
->addWhere('id = i:id');
// Prepare, bind, and execute the query
$this->_executeQuery($stmt, __FUNCTION__);
// Return the \Models\Databases\Database object
return $this;
}
/**
* Update the row with the given data
*
* @access public
* @param string $table
* @return \Models\Databases\Database
*/
public function update($table)
{
// Prepare the statement
$stmt = $this->_connection->prepareUpdateStatement()
->addTable($table)
->addWhere('id = i:id');
// Prepare, bind, and execute the query
$this->_executeQuery($stmt, __FUNCTION__);
// Return the \Models\Databases\Database object
return $this;
}
/**
* Select the rows with the given data
*
* @access public
* @param string $table
* @return \Models\Databases\Database
*/
public function select($table)
{
// Prepare the select statement
$stmt = $this->_connection->prepareSelectStatement()
->addTable($table);
->setLimit('1');
// Prepare, bind, and execute the query
$stmt = $this->_executeQuery($stmt, __FUNCTION__);
// Store the row
$this->_data = (array) current($stmt->getOne()->getData());
// Free the result
$stmt->freeResult();
// Return the \Models\Databases\Database object
return $this;
}
/**
* Prepare, bind, and execute the query
*
* @access private
* @param \Models\MySQLi\MySQLiStatement $stmt
* @param string $function
* @return \Models\MySQLi\MySQLiStatement
*/
private function _executeQuery($stmt, $function)
{
// Iterate through the data
foreach ($this->_data as $field => $value)
{
// Store the field and value in an array
$parameters[] = $field . ':' . $value;
// Convert the datatype into an initial or throw an exception if not valid for MySQL
switch (gettype($value))
{
case ('integer'):
$value = 'i';
break;
case ('double'):
$value = 'd';
break;
case ('string'):
$value = 's';
break;
case ('blob'):
$value = 'b';
break;
default:
throw new \Exception('Unknown data type for MySQL query');
}
// If the function is "select" then add a WHERE clause
if ($function == 'select')
{
$stmt->addWhere($field . ' = ' . $value . ':' . $field);
}
// Otherwise if the function is not "delete" and the field is not "id" then add a SET clause
elseif ($function != 'delete' && $field != 'id')
{
$stmt->addValue($field, $value . ':' . $field);
}
}
// Prepare the query
$stmt = $stmt->prepareQuery();
// Set the values of the bound parameters
call_user_func_array(array($stmt, 'setParameters'), $parameters);
// Return the \Models\MySQLi\MySQLiResult object
return $stmt;
}
/**
* Set the data
*
* @access public
* @param string $field
* @param mixed $value
*/
public function __set($field, $value)
{
$this->_data[$field] = $value;
}
/**
* Get the field data
*
* @access public
* @param string $field
* @return mixed
*/
public function __get($field)
{
// If the field data is not present then return null
if (!isset($this->_data[$field]))
{
return null;
}
// Return the field data
return $this->_data[$field];
}
}
BBCode Parser
<?php
/**
* Squiloople Framework
*
* LICENSE: Feel free to use and redistribute this code.
*
* @author Michael Rushton <michael@squiloople.com>
* @link http://squiloople.com/
* @version 1.0
* @category Squiloople
* @package Models
* @subpackage Parsers
* @copyright © 2010 Michael Rushton
*/
// Define the namespace
namespace Models\Parsers;
/**
* BBCode Parser
*
* Parses BBCode in a string
*/
final class BBCodeParser
{
/**
* Array to contain regular expressions of BB tags
*
* @access private
* @var array $_bbTags
*/
private $_bbTags = array(
'/\[b\]([\x20-\x7E]+?)\[\/b\]/i',
'/\[i\]([\x20-\x7E]+?)\[\/i\]/i',
'/\[u\]([\x20-\x7E]+?)\[\/u\]/i',
'/\[s\]([\x20-\x7E]+?)\[\/s\]/i',
'/\[sub\]([\x20-\x7E]+?)\[\/sub\]/i',
'/\[sup\]([\x20-\x7E]+?)\[\/sup\]/i',
'/\[img\]([\x20-\x7E]+?)\[\/img\]/i',
'/\[url\]([\x20-\x7E]+?)\[\/url\]/i',
'/\[email\]([\x20-\x7E]+?)\[\/email\]/i',
'/\[quote\]([\x20-\x7E]+?)\[\/quote\]/i',
'/\[color=([0-9a-f]{6})\]([\x20-\x7E]+?)\[\/color\]/i',
'/\[size=([1-9]?[0-9])\]([\x20-\x7E]+?)\[\/size\]/i',
'/\[font=([a-z\x20]+)\]([\x20-\x7E]+?)\[\/font\]/i',
'/\[img=([\x20-\x7E]+?)\]([\x20-\x7E]+?)\[\/img\]/i',
'/\[url=([\x20-\x5A\x5C\x5E-\x7E]+)\]([\x20-\x7E]+?)\[\/url\]/i',
'/\[email=([\x20-\x5A\x5C\x5E-\x7E]+)\]([\x20-\x7E]+?)\[\/email\]/i',
'/\[quote=([\x20-\x5A\x5C\x5E-\x7E]+)\]([\x20-\x7E]+?)\[\/quote\]/i',
);
/**
* Array to contain HTML tag replacements
*
* @access private
* @var array $_htmlTags
*/
private $_htmlTags = array(
'<strong>$1</strong>',
'<em>$1</em>',
'<span style="text-decoration:underline">$1</span>',
'<del>$1</del>',
'<sub>$1</sub>',
'<sup>$1</sup>',
'<img src="$1" alt="" />',
'<a href="$1">$1</a>',
'<a href="mailto:$1">$1</a>',
'<fieldset>$1</fieldset>',
'<span style="color:#$1;background-color:transparent">$2</span>',
'<span style="font-size:$1px">$2</span>',
'<span style="font-family:\'$1\', sans-serif">$2</span>',
'<img src="$2" alt="$1" />',
'<a href="$1">$2</a>',
'<a href="mailto:$1">$2</a>',
'<fieldset><legend>$1</legend>$2</fieldset>',
);
/**
* Create "[tag]Text[/tag] => <tag>Text</tag>" style BB tags
*
* @access public
* @param string $bbTag
* @param string|bool $htmlTag
* @return array
*/
public function createTag($bbTag, $htmlTag = false)
{
// If an HTML tag is not specified, emulate the BB tag
$htmlTag = $htmlTag ?: $bbTag;
// Create a new BB tag regular expression of the form: [tag]Text[/tag]
$this->_bbTags[] = '/\[' . $bbTag. '\]([\x20-\x7E]+?)\[\/' . $bbTag . '\]/i';
// Create a new HTML tag replacement of the form Text
$this->_htmlTags[] = '<' . $htmlTag . '>$1</' . strtok($htmlTag, ' ') . '>';
// Return an array with the BBCode regular expression and the HTML replacement
return array(end($this->_bbTags), end($this->_htmlTags));
}
/**
* Create "[tag=option]Text[/tag] => <tag option="option">Text</tag>" style BB tags
*
* @access public
* @param string $bbTag
* @param string $htmlTag
* @return array
*/
public function createParameterTag($bbTag, $htmlTag)
{
// Create a new BB tag regular expression of the form [tag=option][/tag]
$this->_bbTags[] = '/\[' . $bbTag . '=([\x20-\x5A\x5C\x5E-\x7E]+)\]([\x20-\x7E]+?)\[\/' . $bbTag . '\]/i';
// Create a new HTML tag replacement of the form Text
$this->_htmlTags[] = '<' . $htmlTag . '>$2</' . strtok($htmlTag, ' ') . '>';
// Return an array with the BBCode regular expression and the HTML replacement
return array(end($this->_bbTags), end($this->_htmlTags));
}
/**
* Create "[smile] => :)" style BB tags
*
* @access public
* @param string $bbTag
* @param string $htmlTag
* @return array
*/
public function createSpecialTag($bbTag, $htmlTag)
{
// Create a new BB tag regular expression of the form [tag] (does not require usual brackets)
$this->_bbTags[] = '/' . preg_quote($bbTag) . '/i';
// Create a new replacement (does not need to be an HTML tag)
$this->_htmlTags[] = $htmlTag;
// Return an array with the BBCode regular expression and the HTML replacement
return array(end($this->_bbTags), end($this->_htmlTags));
}
/**
* Return the parsed string
*
* @access public
* @param string $string
* @return string
*/
public function parseString($string)
{
// Only replace tags if the string has not been fully parsed
for ($count = 1; $count != 0;)
{
$string = preg_replace($this->_bbTags, $this->_htmlTags, $string, -1, $count);
}
// Return the parsed string
return $string;
}
}
MySQLi Classes
MySQLi Connection
<?php
/**
* Squiloople Framework
*
* LICENSE: Feel free to use and redistribute this code.
*
* @author Michael Rushton <michael@squiloople.com>
* @link http://squiloople.com/
* @category Squiloople
* @package Models
* @subpackage MySQLi
* @version 1.0
* @copyright © 2010 Michael Rushton
*/
// Define the namespace
namespace Models\MySQLi;
/**
* MySQLiConnection
*
* Control a MySQLi connection
*/
final class MySQLiConnection
{
/**
* An instance of the class
*
* @access private
* @static
* @var \Models\MySQLi\MySQLiConnection|null $_instance
*/
private static $_instance;
/**
* The connection resource
*
* @access private
* @var mysqli $_connection
*/
private $_connection;
/**
* Try to connect to the server
*
* @access private
* @param array $connect
*/
private function __construct($connect)
{
// If a connection cannot be made then throw an exception
if (!$this->_connection = call_user_func_array('mysqli_connect', $connect))
{
throw new \Exception('Unable to connect to the database server');
}
}
/**
* Try to create an instance of the object, and then return it
*
* @access public
* @static
* @param string $hostname
* @param string $username
* @param string $password
* @param string $database
* @return \Models\MySQLi\MySQLiConnection
*/
public static function setConnection()
{
// If an instance has not been created then create one
if (!isset(self::$_instance))
{
self::$_instance = new self(func_get_args());
}
// Return the \Models\MySQLi\Connection instance
return self::$_instance;
}
/**
* Return the connection
*
* @access public
* @return mysqli|false
*/
public function getConnection()
{
return $this->_connection;
}
/**
* Try to select the database
*
* @access public
* @param string $database
* @return \Models\MySQLi\MySQLiConnection
*/
public function setDatabase($database)
{
// If the database cannot be selected then throw an exception
if (!$this->_connection->select_db($database))
{
throw new \Exception('Unable to select the database');
}
// Return the \Models\MySQLi\MySQLiConnection instance
return $this;
}
/**
* Try to prepare the query
*
* @access public
* @param string $query
* @param string $result
* @return \Models\MySQLi\MySQLiResult
*/
public function prepareQuery($query, $result = '')
{
// Store all the matches to be used for bound parameters
preg_match_all('/[idsb]:([a-zA-Z0-9_$]+|`[^`]+`)/', $query, $vars);
// If the re-formatted query cannot be prepared then throw an exception
if (!$stmt = $this->_connection->prepare(preg_replace('/[idsb]:([a-zA-Z0-9_$]+|`[^`]+`)/', '?', $query)))
{
throw new \Exception('Unable to prepare the query');
}
$result = '\Models\MySQLi\MySQLi' . ucfirst(strtolower($result)) . 'Result';
// Instantiate and return the \Models\MySQLi\MySQLiResult object
return new $result($stmt, $vars[0]);
}
/**
* Prepare an INSERT statement
*
* @access public
* @return \Models\MySQLi\MySQLiInsertStatement
*/
public function prepareInsertStatement()
{
return new MySQLiInsertStatement;
}
/**
* Prepare a DELETE statement
*
* @access public
* @return \Models\MySQLi\MySQLiDeleteStatement
*/
public function prepareDeleteStatement()
{
return new MySQLiDeleteStatement;
}
/**
* Prepare an UPDATE statement
*
* @access public
* @return \Models\MySQLi\MySQLiUpdateStatement
*/
public function prepareUpdateStatement()
{
return new MySQLiUpdateStatement;
}
/**
* Prepare a SELECT statement
*
* @access public
* @return \Models\MySQLi\MySQLiSelectStatement
*/
public function prepareSelectStatement()
{
return new MySQLiSelectStatement;
}
/**
* Try to disconnect from the server
*
* @access public
*/
public function disconnect()
{
// If the server connection cannot be closed then throw an exception
if (!$this->_connection->close())
{
throw new \Exception('Unable to close the connection');
}
// Destroy the instance
self::$_instance = null;
}
/**
* Throw an exception if a clone is attempted
*
* @access public
*/
public function __clone()
{
throw new \Exception('Attempt to clone ' . __CLASS__);
}
/**
* Disconnect from the server
*
* @access public
*/
public function __destruct()
{
$this->disconnect();
}
}
IP Address Validation
An IP address is one of an IPv4 address, an IPv6 address, or an IPv4-mapped IPv6 address.
An IPv4 address consists of four groups, separated by dots, each containing a decimal value between 0 and 255. A regular expression check to match for an IPv4 address would be as follows:
// IPv4 address
'/^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?1)){3}$/D'
Email Address Validation
Email addresses have a local-part and a domain separated by an (unquoted) “@” symbol. The local-part must be either a dot-atom or a quoted string, and the domain must be either a domain name or a domain literal.
A dot-atom can only contain letters, numbers, dots, and the following characters: ! # $ % & ‘ * + – / = ? ^ _ ` { | } ~. However, neither the first nor the last character can be a dot, and two or more consecutive dots are not allowed. The maximum length of a dot-atom is 64 characters. A regular expression to match for a dot-atom local-part would be as follows:
// Dot-atom
/^(?!.{65,})([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*$/iD