Pagination Class

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */

  /**
   * Paginator
   *
   * Create the pagination
   */
  final class Paginator
  {

    /**
     * The URL
     *
     * @access private
     * @var string $_url
     */
    private $_url;

    /**
     * The total number of results
     *
     * @access private
     * @var integer $_total
     */
    private $_total;

    /**
     * The page number
     *
     * @access private
     * @var integer $_page
     */
    private $_page;

    /**
     * The limit
     *
     * @access private
     * @var integer $_limit
     */
    private $_limit;

    /**
     * The total number of pages
     *
     * @access private
     * @var integer $_pages
     */
    private $_pages;

    /**
     * The offset
     *
     * @access private
     * @var integer $_offset
     */
    private $_offset;

    /**
     * Set the initial conditions
     *
     * @access public
     * @param string $url
     * @param integer $total
     * @param integer $page
     * @param integer $limit
     */
    public function __construct($url, $total, $page, $limit)
    {

      // Set the URL
      $this->_setURL($url);

      // Set the total
      $this->_setTotal($total);

      // Set the page
      $this->_setPage($page);

      // Set the limit
      $this->_setLimit($limit);

      // Set the total number of pages
      $this->_setTotalPages();

      // Set the offset
      $this->_setOffset();

    }

    /**
     * Set the URL
     *
     * @access private
     * @param string $url
     */
    private function _setURL($url)
    {
      $this->_url = (string) $url;
    }

    /**
     * Set the total number of results
     *
     * @access private
     * @param integer $total
     */
    private function _setTotal($total)
    {

      // Set the total to 0 if less than 0
      if (($this->_total = (int) $total) < 0)
      {
        $this->_total = 0;
      }

    }

    /**
     * Get the total
     *
     * @access public
     * @return integer
     */
    public function getTotal()
    {
      return $this->_total;
    }

    /**
     * Set the page
     *
     * @access private
     * @param integer $page
     */
    private function _setPage($page)
    {

      // Set the page to 1 if less than 1
      if (($this->_page = (int) $page) < 1)
      {
        $this->_page = 1;
      }

    }

    /**
     * Get the page
     *
     * @access public
     * @return integer
     */
    public function getPage()
    {
      return $this->_page;
    }

    /**
     * Set the limit
     *
     * @access private
     * @param integer $limit
     */
    private function _setLimit($limit)
    {

      // Set the limit to 10 if less than 1
      if (($this->_limit = (int) $limit) < 1)
      {
        $this->_limit = 10;
      }

    }

    /**
     * Get the limit
     *
     * @access public
     * @return integer
     */
    public function getLimit()
    {
      return $this->_limit;
    }

    /**
     * Set the offset
     *
     * @access private
     */
    private function _setOffset()
    {
      $this->_offset = $this->_limit * ($this->_page - 1);
    }

    /**
     * Get the offset
     *
     * @access public
     * @return integer
     */
    public function getOffset()
    {
      return $this->_offset;
    }

    /**
     * Set the total number of pages
     *
     * @access private
     */
    private function _setTotalPages()
    {

      // Set the page to the last if later than the last
      if ($this->_page > $this->_pages = 0 != $this->_total ? ceil($this->_total / $this->_limit) : 1)
      {
        $this->_page = $this->_pages;
      }

    }

    /**
     * Get the total number of pages
     *
     * @access public
     * @return integer
     */
    public function getTotalPages()
    {
      return $this->_pages;
    }

    /**
     * Get the limit options
     *
     * @access public
     * @param array $options
     * @param string $name
     * @return string
     */
    public function getLimitOptions(array $options = array(10, 25, 50, 100), $name = 'squiloople-pagination-limit')
    {

      // Include the limit if not already included
      if (!in_array($this->_limit, $options))
      {
        array_push($options, $this->_limit);
      }

      // Sort the array
      sort($options);

      // Open the drop-down
      $select[] = '<select name="' . $name . '">';

      // Iterate over each option and add
      foreach (array_unique($options) as $option)
      {
        $select[] = '<option value="' . $option . '" ' . ($this->_limit == $option ? 'selected' : '') . '>' . $option . '</option>';
      }

      // Close the drop-down
      $select[] = '</select>';

      // Return the limit options
      return '<div class="squiloople-pagination-limit"><span>Results Per Page:</span> ' . implode(PHP_EOL, $select) . '</div>';

    }

    /**
     * Get the information
     *
     * @access public
     * @return string
     */
    public function getInformation()
    {

      // Get the first result
      $first = 0 != $this->_total ? $this->_offset + 1 : 0;

      // Get the last result
      if ($this->_total < $last = $this->_limit * $this->_page)
      {
        $last = $this->_total;
      }

      // Return the result information
      return '<div class="squiloople-pagination-information">Results ' . number_format($first) . ' - ' . number_format($last) . ' of ' . number_format($this->_total) . '</div>';

    }

    /**
     * Get the first page link
     *
     * @access private
     * @return string
     */
    private function _getFirstPageLink()
    {
      return '<li class="squiloople-pagination-active"><a href="' . $this->_url . '?page=1&limit=' . $this->_limit . '" title="First page">«</a></li>';
    }

    /**
     * Get the previous page link
     *
     * @access private
     * @return string
     */
    private function _getPreviousPageLink()
    {
      return '<li class="squiloople-pagination-active"><a href="' . $this->_url . '?page=' . ($this->_page - 1) . '&limit=' . $this->_limit . '" title="Previous page">‹</a></li>';
    }

    /**
     * Get the next page link
     *
     * @access private
     * @return string
     */
    private function _getNextPageLink()
    {
      return '<li class="squiloople-pagination-active"><a href="' . $this->_url . '?page=' . ($this->_page + 1) . '&limit=' . $this->_limit . '" title="Next page">›</a></li>';
    }

    /**
     * Get the last page link
     *
     * @access private
     * @return string
     */
    private function _getLastPageLink()
    {
      return '<li class="squiloople-pagination-active"><a href="' . $this->_url . '?page=' . $this->_pages . '&limit=' . $this->_limit . '" title="Last page">»</a></li>';
    }

    /**
     * Get the page link
     *
     * @access private
     * @param integer $page
     * @return string
     */
    private function _getPageLink($page)
    {

      // Return an empty link if we're on the last page
      if ($page == $this->_page)
      {
        return '<li class="squiloople-pagination-inactive">' . number_format($page) . '</li>';
      }

      // Return the link
      return '<li class="squiloople-pagination-active"><a href="' . $this->_url . '?page=' . $page . '&limit=' . $this->_limit . '" title="Page ' . number_format($page) . '">' . number_format($page) . '</a></li>';

    }

    /**
     * Get the pagination
     *
     * @access public
     * @return string
     */
    public function getPagination()
    {

      // If we only have one page then return an empty string
      if (1 == $this->_pages)
      {
        return '';
      }

      // If we are not on the first page
      if (1 != $this->_page)
      {

        // Add the first page link
        $pages[] = $this->_getFirstPageLink();

        // Add the previous page link
        $pages[] = $this->_getPreviousPageLink();

        // Add an empty page link
        $pages[] = '<li class="squiloople-pagination-inactive"> </li>';

      }

      // Iterate over available pages
      for ($i = 1; $i <= $this->_pages; ++$i)
      {

        // Determine what is to be added
        switch (TRUE)
        {

          // If there are 7 or fewer pages
          case ($this->_pages <= 7):

          // If the page is the first, the one before the current, the current, the one after the current, or the last
          case (in_array($i, array(1, $this->_page - 1, $this->_page, $this->_page + 1, $this->_pages))):

          // If the page is at most the fifth and the current is at most the fourth
          case ($i <= 5 && $this->_page < 5):

          // If the page is at least the fifth-to-last and the current is at least the fourth-to-last
          case ($i >= $this->_pages - 4 && $this->_page > $this->_pages - 4):

            // Add the page link
            $pages[] = $this->_getPageLink($i);

            // Break
            break;

          // If the page is the second or the second-to-last
          case (in_array($i, array(2, $this->_pages - 1))):

            // Add the ellipsis
            $pages[] = '<li class="squiloople-pagination-inactive">…</li>';

            // Break
            break;

        }

      }

      // If we are not on the last page
      if ($this->_pages != $this->_page)
      {

        // Add an empty page link
        $pages[] = '<li class="squiloople-pagination-inactive"> </li>';

        // Add the next page link
        $pages[] = $this->_getNextPageLink();

        // Add the last page link
        $pages[] = $this->_getLastPageLink();

      }

      // Return the pagination
      return '<ul class="squiloople-pagination">' . implode(PHP_EOL, $pages) . '</ul>';

    }

  }

Continue reading Pagination Class

Password Validator

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */

  /**
   * Password Validator
   *
   * Hash, generate, 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
     */
    const PEPPER = 'SK7,8%4\#|U_Y^\'Eg*\IVHObKg)~{,IGXn=8(8`';

    /**
     * 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 PasswordValidator
     */
    public static function setPassword($password)
    {
      return new self($password);
    }

    /**
     * Return the password
     *
     * @access public
     * @return string
     */
    public function getPassword()
    {
      return $this->_password;
    }

    /**
     * Generate a random password
     *
     * @access public
     * @return PasswordValidator
     */
    public function randomizePassword()
    {

      // Randomize the password
      $this->_password = substr($this->getSalt(TRUE), 0, 8);

      // Reset the salt
      $this->_salt = NULL;

      // Return the PasswordValidator object
      return $this;

    }

    /**
     * The password must contain at least one letter of each case, one digit, and be between 8 and 39 characters inclusive in length
     *
     * @access public
     * @param boolean|string $new
     * @return integer
     */
    public function isValid($new = FALSE)
    {
      return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\x20-\x7E]{8,39}$/D', $new ?: $this->_password);
    }

    /**
     * Set the salt
     *
     * @access public
     * @param string $salt
     * @return PasswordValidator
     */
    public function setSalt($salt)
    {

      // Set the salt
      $this->_salt = $salt;

      // Return the PasswordValidator object
      return $this;

    }

    /**
     * Generate and return a salt
     *
     * @access public
     * @param boolean $reset
     * @return string
     */
    public function getSalt($reset = FALSE)
    {

      // Return the salt if one has been set and a reset is not required
      if (!$reset && isset($this->_salt))
      {
        return $this->_salt;
      }

      // Generate a random salt of 39 printable ASCII characters
      for ($i = 1; $i <= 39; ++$i)
      {
        $salt[] = chr(mt_rand(32, 126));
      }

      // Return the random salt
      return $this->_salt = implode($salt);

    }

    /**
     * Return the pepper portion
     *
     * @access private
     * @return string
     */
    private function _getPepper()
    {
      return substr(self::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(self::PEPPER), 0, 20) . $this->_getHash());
    }

  }

Continue reading Password Validator

Database Class

This class is designed to be used with the MySQL 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/
   * @package Models
   * @subpackage Databases
   * @version 1.0
   * @copyright © 2013 Michael Rushton
   */

  /**
   * Database
   *
   * Acts as a database for simple queries
   */
  class Database
  {

    /**
     * An instance of MySQLConnection
     *
     * @access protected
     * @var MySQLConnection $_connection
     */
    protected $_connection;

    /**
     * The database table
     *
     * @access private
     * @var string $_table
     */
    private $_table;

    /**
     * The indexes
     *
     * @access private
     * @var array $_indexes
     */
    private $_indexes = array();

    /**
     * The auto increment column
     *
     * @access private
     * @var null|string $_auto_increment
     */
    private $_auto_increment = NULL;

    /**
     * An array of the data
     *
     * @access protected
     * @var array $_data
     */
    protected $_data;

    /**
     * Store the connection object and set the initial data
     *
     * @access public
     * @param string $table
     * @param array $data
     */
    public function __construct($table, array $data = array())
    {

      // Store the table name
      $this->_table = $table;

      // Store the connection
      $this->_connection = MySQLConnection::connection();

      // Get the unique indexes
      $indexes = $this->_connection->prepareQuery("SHOW INDEX FROM `" . $table . "` WHERE Non_unique = 0", MySQLSelectStatement::STATEMENT)
        ->execute()->getAll();

      // Iterate over each index
      foreach ($indexes as $index)
      {

        // Create the key if it does not exist
        if (!isset($this->_indexes[$key_name = $index->STATISTICS->Key_name]))
        {
          $this->_indexes[$key_name] = array();
        }

        // Store the key's column
        $this->_indexes[$key_name][] = $index->STATISTICS->Column_name;

      }

      // Throw an exception if there is no primary key
      if (empty($this->_indexes['PRIMARY']))
      {
        throw new Exception('Table `' . $table . '` has no primary key.');
      }

      // Get the auto increment column
      $auto_increment = $this->_connection->prepareQuery("SHOW COLUMNS FROM `" . $table . "` WHERE Extra = 'auto_increment'", MySQLSelectStatement::STATEMENT)
        ->execute()->getOne();

      // If there is an auto increment column then store it
      if (0 != $auto_increment->getNumRows())
      {
        $this->_auto_increment = $auto_increment->COLUMNS->Field;
      }

      // Store the data
      $this->_data = $data;

    }

    /**
     * Insert a row with the given data
     *
     * @access public
     */
    public function insert()
    {

      // Prepare the statement
      $stmt = $this->_connection->prepareInsertStatement()
                ->addTable($this->_table);

      // Execute the query
      $stmt = $this->_executeQuery($stmt, __FUNCTION__);

      // Store the new row's auto increment value if there is one
      if (NULL != $this->_auto_increment)
      {
        $this->_data[$this->_auto_increment] = $stmt->getInsertID();
      }

      // Select the row
      $this->select();

    }

    /**
     * Delete the row with the given data
     *
     * @access public
     */
    public function delete()
    {

      // Prepare the statement
      $stmt = $this->_connection->prepareDeleteStatement()
                ->addTable($this->_table)
                ->setLimit(1);

      // Iterate over each primary key
      foreach ($this->_indexes['PRIMARY'] as $key)
      {

        // Throw an exception if the value has not been set
        if (NULL == $this->$key)
        {
          throw new Exception('The primary key has not been set.');
        }

      }

      // Execute the query
      $this->_executeQuery($stmt, __FUNCTION__);

    }

    /**
     * Update the row with the given data
     *
     * @access public
     */
    public function update()
    {

      // Prepare the statement
      $stmt = $this->_connection->prepareUpdateStatement()
                ->addTable($this->_table)
                ->setLimit(1);

      // Iterate over each primary key
      foreach ($this->_indexes['PRIMARY'] as $key)
      {

        // Throw an exception if the value has not been set
        if (NULL == $this->$key)
        {
          throw new Exception('The primary key has not been set.');
        }

      }

      // Execute the query
      $this->_executeQuery($stmt, __FUNCTION__);

    }

    /**
     * Select the row with the given data
     *
     * @access public
     */
    public function select()
    {

      // Iterate over each index
      foreach ($this->_indexes as $index)
      {

        // Set the unique index status to true
        $unique = TRUE;

        // Iterate over each column
        foreach ($index as $column)
        {

          // If the column value has not been set
          if (NULL == $this->$column)
          {

            // Set the unique index status to false
            $unique = FALSE;

            // Break out of the iteration
            break;

          }

        }

        // If a unique index has been set then break out of the iteration
        if ($unique)
        {
          break;
        }

      }

      // Throw an exception a unique index has not been set
      if (!$unique)
      {
        throw new Exception('A unique key has not been set.');
      }

      // Prepare the select statement
      $stmt = $this->_connection->prepareSelectStatement()
                ->addTable($this->_table, '*')
                ->setLimit(1);

      // Get the result
      $stmt = $this->_executeQuery($stmt, __FUNCTION__)->getOne();

      // If there are results then store the data
      if (0 != $stmt->getNumRows())
      {
        $this->_data = (array) current($stmt->getData());
      }

      // Otherwise if there is an auto increment column then unset it
      elseif (NULL != $this->_auto_increment)
      {
        unset($this->_data[$this->_auto_increment]);
      }

      // Free the result
      $stmt->freeResult();

    }

    /**
     * Prepare, bind, and execute the query
     *
     * @access private
     * @param MySQLStatement $stmt
     * @param string $method
     * @return object
     */
    final private function _executeQuery(MySQLStatement $stmt, $method)
    {

      // Throw an exception if the data is empty
      if (empty($this->_data))
      {
        throw new Exception('No data has been set.');
      }

      // Iterate over the data
      foreach ($this->_data as $field => $value)
      {

        // Get the data type
        switch (gettype($value))
        {

          // If an integer
          case ('integer'):
            $type = 'i';
            break;

          // Otherwise if a double
          case ('double'):
            $type = 'd';
            break;

          // Otherwise if a string
          case ('string'):
            $type = 's';
            break;

          // Otherwise if null
          case ('NULL'):
            $type = NULL;
            break;

          // Default
          default:
            continue 2;

        }

        // Store the parameter if not null
        if (NULL != $type)
        {
          $parameters[$field] = $value;
        }

        // If the parent method is not "insert"
        if ('insert' != $method)
        {

          // Iterate over each index
          foreach ($this->_indexes as $key => $index)
          {

            // If the parent method is "select" or the key is primary
            if ('select' == $method || 'PRIMARY' == $key)
            {

              // If the field is an index
              if (in_array($field, $index))
              {

                // If the value is not null
                if (NULL != $type)
                {
                  $stmt->addWhere('`' . $field . '` = ' . $type . ':' . $field);
                }

                // Otherwise
                else
                {
                  $stmt->addWhere('`' . $field . '` IS NULL');
                }

              }

            }

          }

        }

        // Add a SET clause if the parent method is "insert" or if it is "update" and the field is not a primary key
        if ('insert' == $method || ('update' == $method && !isset($this->_indexes['PRIMARY'][$field])))
        {

          // If not NULL
          if (NULL != $type)
          {
            $stmt->addValue($field, $type . ':' . $field);
          }

          // Otherwise
          else
          {
            $stmt->addValue($field, 'NULL');
          }

        }

      }

      // Set the parameters, execute the statement, and return the object
      return $stmt->setParameters($parameters)->execute();

    }

    /**
     * Set the field data
     *
     * @access public
     * @param string $field
     * @param mixed $value
     */
    final public function __set($field, $value)
    {
      $this->_data[$field] = $value;
    }

    /**
     * Get the field data
     *
     * @access public
     * @param string $field
     * @return mixed
     */
    final public function __get($field)
    {
      return isset($this->_data[$field]) ? $this->_data[$field] : NULL;
    }

    /**
     * Set all the field data
     *
     * @access public
     * @param array $data
     */
    final public function setData(array $data)
    {
      $this->_data = $data;
    }

    /**
     * Get all the field data
     *
     * @access public
     * @return array
     */
    final public function getData()
    {
      return $this->_data;
    }

    /**
     * Clear all the field data
     *
     * @access public
     */
    final public function clearData()
    {
      $this->_data = array();
    }

  }

Continue reading Database Class

BBCode Parser

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */

  /**
   * BBCode Parser
   *
   * Parses BBCode in a string
   */
  final class BBCodeParser
  {

    /**
     * Array to contain regular expressions of BB tags
     *
     * @access private
     * @var array $_bb_tags
     */
    private $_bb_tags = 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 $_html_tags
     */
    private $_html_tags = 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 $bb_tag
     * @param string|boolean $html_tag
     * @return BBCodeParser
     */
    public function createTag($bb_tag, $html_tag = FALSE)
    {

      // Emulate the BB tag if an HTML tag has not been specified
      $html_tag = $html_tag ?: $bb_tag;

      // Create a new BB tag regular expression of the form: [tag]Text[/tag]
      $this->_bb_tags[] = '/\[' . $bb_tag. '\]([\x20-\x7E]+?)\[\/' . $bb_tag . '\]/i';

      // Create a new HTML tag replacement of the form <tag>Text</tag>
      $this->_html_tags[] = '<' . $html_tag . '>$1</' . strtok($html_tag, ' ') . '>';

      // Return the BBCodeParser object
      return $this;

    }

    /**
     * Create "[tag=option]Text[/tag] => <tag option="option">Text</tag>" style BB tags
     *
     * @access public
     * @param string $bb_tag
     * @param string $html_tag
     * @return BBCodeParser
     */
    public function createParameterTag($bb_tag)
    {

      // Create a new BB tag regular expression of the form [tag=option]Text[/tag]
      $this->_bb_tags[] = '/\[' . $bb_tag . '=([\x20-\x5A\x5C\x5E-\x7E]+)\]([\x20-\x7E]+?)\[\/' . $bb_tag . '\]/i';

      // Create a new HTML tag replacement of the form <tag option="option">Text</tag>
      $this->_html_tags[] = '<' . $html_tag . '>$2</' . strtok($html_tag, ' ') . '>';

      // Return the BBCodeParser object
      return $this;

    }

    /**
     * Create "[smile] => :)" style BB tags
     *
     * @access public
     * @param string $bb_tag
     * @param string $html_tag
     * @return BBCodeParser
     */
    public function createSpecialTag($bb_tag, $html_tag)
    {

      // Create a new BB tag regular expression of the form [tag] (does not require usual brackets)
      $this->_bb_tags[] = '/' . preg_quote($bb_tag) . '/i';

      // Create a new replacement (does not need to be an HTML tag)
      $this->_html_tags[] = $html_tag;

      // Return the BBCodeParser object
      return $this;

    }

    /**
     * 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->_bb_tags, $this->_html_tags, $string, -1, $count);
      }

      // Return the parsed string
      return $string;

    }

  }

Continue reading BBCode Parser

MySQL Classes

MySQL Connection

<?php

  /**
   * Squiloople Framework
   *
   * LICENSE: Feel free to use and redistribute this code.
   *
   * @author Michael Rushton <michael@squiloople.com>
   * @link http://squiloople.com/
   * @package Squiloople
   * @version 1.0
   * @copyright © 2012 Michael Rushton
   */

  /**
   * MySQLConnection
   *
   * Control a MySQL connection
   */
  final class MySQLConnection
  {

    /**
     * An instance of the class
     *
     * @access private
     * @static
     * @var MySQLConnection $_instance
     */
    private static $_instance;

    /**
     * The mysqli connection resource
     *
     * @access private
     * @var mysqli $_connection
     */
    private $_connection;

    /**
     * Try to connect to the server
     *
     * @access private
     * @param array $connect
     */
    private function __construct(array $connect)
    {

      // Throw an exception if a connection cannot be made
      if (!$this->_connection = call_user_func_array('mysqli_connect', $connect))
      {
        throw new MySQLException($this->getConnectError());
      }

    }

    /**
     * Create and/or return an instance of the object
     *
     * @access public
     * @static
     * @param string $hostname
     * @param string $username
     * @param string $password
     * @param string $database
     * @return MySQLConnection
     */
    public static function connection()
    {

      // Instantiate the object if one does not exist
      if (!isset(self::$_instance))
      {
        self::$_instance = new self(func_get_args());
      }

      // Return the MySQLConnection instance
      return self::$_instance;

    }

    /**
     * Return the connection
     *
     * @access public
     * @return mysqli|boolean
     */
    public function getConnection()
    {
      return $this->_connection;
    }

    /**
     * Select the database
     *
     * @access public
     * @param string $database
     * @return MySQLConnection
     */
    public function setDatabase($database)
    {

      // Throw an exception if the database cannot be selected
      if (!$this->_connection->select_db($database))
      {
        throw new MySQLException($this->getError());
      }

      // Return the MySQLConnection instance
      return $this;

    }

    /**
     * Prepare the query
     *
     * @access public
     * @param string $query
     * @param string $result
     * @param array $vars
     * @return object
     */
    public function prepareQuery($query, $result = '', array $vars = array())
    {

      // Store all the matches to be used for bound parameters
      preg_match_all('/[idsb]:[a-zA-Z0-9_]+/', $query, $refs);

      // Throw an exception if the re-formatted query cannot be prepared
      if (!$stmt = $this->_connection->prepare(preg_replace('/[idsb]:[a-zA-Z0-9_]+/', '?', $query)))
      {
        throw new MySQLException($this->getError());
      }

      // Set the relevant MySQL result class name
      $class = 'MySQL' . ucfirst(strtolower($result)) . 'Result';

      // Instantiate and return the object
      return new $class($stmt, $refs[0], $vars);

    }

    /**
     * Prepare an INSERT statement
     *
     * @access public
     * @return MySQLInsertStatement
     */
    public function prepareInsertStatement()
    {
      return new MySQLInsertStatement;
    }

    /**
     * Prepare a DELETE statement
     *
     * @access public
     * @return MySQLDeleteStatement
     */
    public function prepareDeleteStatement()
    {
      return new MySQLDeleteStatement;
    }

    /**
     * Prepare an UPDATE statement
     *
     * @access public
     * @return MySQLUpdateStatement
     */
    public function prepareUpdateStatement()
    {
      return new MySQLUpdateStatement;
    }

    /**
     * Prepare a SELECT statement
     *
     * @access public
     * @return MySQLSelectStatement
     */
    public function prepareSelectStatement()
    {
      return new MySQLSelectStatement;
    }

    /**
     * Return the connection errno
     *
     * @access public
     * @return integer
     */
    public function getConnectErrno()
    {
      return mysqli_connect_errno();
    }

    /**
     * Return the connection error
     *
     * @access public
     * @return string
     */
    public function getConnectError()
    {
      return mysqli_connect_error();
    }

    /**
     * Return the error
     *
     * @access public
     * @return integer
     */
    public function getErrno()
    {
      return $this->_connection->errno;
    }

    /**
     * Return the error
     *
     * @access public
     * @return string
     */
    public function getError()
    {
      return $this->_connection->error;
    }

    /**
     * Disconnect from the server
     *
     * @access public
     */
    public function disconnect()
    {

      // Throw an exception if the server connection cannot be closed
      if (!$this->_connection->close())
      {
        throw new MySQLException($this->getError());
      }

      // 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 MySQLConnection instance.');
    }

    /**
     * Disconnect from the server
     *
     * @access public
     */
    public function __destruct()
    {
      $this->disconnect();
    }

  }

Continue reading MySQL Classes

Email Address Validation

Email addresses have a local-part and a domain separated by an @ 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 may 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 is as follows:

// Dot-atom

"/^(?!.{65,})([!#-'*+\/-9=?^-~-]+)(?>\.(?1))*$/iD"

Continue reading Email Address Validation