<?php
/**
* @package Param
* @author Domenico Pontari <hide@address.com>
* @copyright Copyright (c) 2009, Domenico Pontari
* @license http://opensource.org/licenses/bsd-license.php New and Simplified BSD licenses
* @version 1.0
*
* This class allow you to define a parameter or a list of parameters that must be
* read from $_POST or $_GET variables. Features are:
* 1. a param type can be an array or an object, not only a scalar value
* 2. you can define custom function to set restriction for the values
* 3. you can define a type casting for the param
* 4. SQL INJECTION management
*
* It follows a factory pattern: each param type
* must be an extended class of "param" base class
*/
/**#@+
* param types
*/
define ('PARAM_DIM_SINGLE', 'single');
define ('PARAM_DIM_ARRAY', 'array');
define ('PARAM_DIM_ASSOCIATIVE', 'associative array');
define ('PARAM_DIM_OBJECT', 'object');
define ('PARAM_TYPE_BOOL', 'boolean');
define ('PARAM_TYPE_INT', 'integer');
define ('PARAM_TYPE_STRING', 'string');
define ('PARAM_TYPE_AUTO', 'auto');
define ('PARAM_CTRL_INPUT', 'input');
define ('PARAM_INPUT_GET', 'get');
define ('PARAM_INPUT_POST', 'post');
define ('PARAM_INPUT_DEFAULT_VALUE', 'default value'); // value defined in the "value" attribute of "paramVars" class
/**#@-*/
/**
* required library
* @link http://www.phpclasses.org/package/5969-PHP-Tokenizer-split-strings-into-tokens.html
*/
require_once ('tokenizer.php');
/**
* @package Param
*/
class param {
/**
* @var string inclusion path for extended classes
*/
static $includePath = '';
protected $vars = false;
function get() {return $this->vars;}
/**
* @param paramVars|array
*/
static function register ($vars) {
if (!is_a($vars, 'paramVars')) $vars = new paramVars ($vars);
if ($vars->refClass !== false) {
$className = $vars->refClass . "Param";
if (!class_exists($className)) {
include_once ($includePath . "param_$vars->refClass");
if (!class_exists($className)) trigger_error ('unableToFindParamClassExtension', E_USER_ERROR);
}
} else $className = 'param';
$param = new $className ($vars);
if ($param->get() === false) return false;
return $param;
}
function __construct ($vars) {
$this->set($vars);
if ($this->vars->startGettingFromInput) $this->setValueFromURL();
}
/**
* @return void
*/
function set($vars) {
$this->vars = $vars;
}
protected function getVar ($varName) {
if (!isset($this->vars->$varName)) trigger_error ('paramValueNotSetted');
return $this->vars->$varName;
}
protected function setVar ($varName, $varValue) {
$this->vars->$varName = $varValue;
}
function __get ($varName) {
switch ($varName) {
case 'value':
case 'name':
$result = $this->getVar ($varName);
break;
default:
trigger_error ('paramAttributeNotFound', E_USER_ERROR);
}
return $result;
}
function __set ($varName, $varValue) {
switch ($varName) {
case 'value':
$result = $this->setVar ($varName, $varValue);
break;
default:
trigger_error ('unableToSetThisAttribute', E_USER_ERROR);
}
}
function getDefaultValue () {
return $this->vars->defaultValue;
}
/**
* @return string|false if no valid input is found false will be returned
*
* if PARAM_INPUT_DEFAULT_VALUE is found it will return false as well
*/
function getInputStringFromURL () {
$result = false;
$defaultValue = $this->getDefaultValue();
foreach ($this->vars->inputList as $input) {
if (($input == PARAM_INPUT_POST) && (isset($_POST[$this->vars->name])))
$result = $_POST[$this->vars->name];
if (($input == PARAM_INPUT_GET) && (isset($_GET[$this->vars->name])))
$result = $_GET[$this->vars->name];
if (($input == PARAM_INPUT_DEFAULT_VALUE) && (isset($defaultValue)))
return false;
if ($result !== false) {
if (self::sqlInjectionAttempted($result)) trigger_error ('sqlInjectionDetected', E_USER_ERROR);
return $result;
}
}
return $result;
}
function setValueFromURL () {
$inputString = $this->getInputStringFromURL();
if ($inputString !== false)
$this->vars->value = $this->stringToValue($inputString);
elseif (in_array(PARAM_INPUT_DEFAULT_VALUE, $this->vars->inputList)) {
$defaultValue = $this->getDefaultValue();
if (isset($defaultValue)) $this->vars->value = $defaultValue;
}
}
protected function stringToValue ($string) {
$value = self::splitInputString ($string, $this->vars->dimension);
$value = self::castType ($value, $this->vars->type);
if (isset($this->vars->maxLen)) {
self::checkMaxLen ($value, $this->vars->maxLen, $newValue);
$value = $newValue;
}
$value = $this->filter($value);
if (!$this->validate($value)) trigger_error ('errorValidatingParam', E_USER_ERROR);
return $value;
}
/**
* This function tell you if this param value is a default value so that
* you can omit it to define current state
*/
function isInIrrelevantState () {
$defaultValue = $this->getDefaultValue();
if (!isset($defaultValue)) return false;
if ($this->vars->value === $defaultValue) return true;
return false;
}
/**
* Function to be extended
* @return mixed filtered value
*/
function filter ($value) {return $value;}
/**
* Function to be extended
* @return bool
*/
function validate ($value) {return true;}
static protected function splitInputString ($string, $dimension) {
$result = $string;
switch ($dimension) {
case PARAM_DIM_OBJECT:
case PARAM_DIM_ASSOCIATIVE:
$result = json_decode ($result);
break;
case PARAM_DIM_ARRAY:
$result = explode(",", $result);
break;
}
return $result;
}
/**
* @param mixed
* @param int
* @param mixed cutted values
* @return bool
*/
static protected function checkMaxLen ($value, $maxLen, &$cuttedValue = NULL) {
if (!is_scalar ($value)) {
foreach ($value as $el)
if (!checkMaxLen ($el, $maxLen)) return false;
return true;
}
$oversize = (strlen($value) > $maxLen);
if (gettype($value) != 'string') {
$module = '1';
for ($i = 1; $i < $maxLen; $i++) $module .= '0';
$module = intval($module);
$cuttedValue = $value % $module;
} else $cuttedValue = substr($value, 0, $maxLen);
return !$oversize;
}
/**
* Used to detect SQL injection attempted
* Criteria:
* 1. found ('select', 'update', 'delete', 'insert') AND ('from')
* 2. found ('execute', 'exec', 'cast') AND parenthesis
* @return bool
*/
static protected function sqlInjectionAttempted ($string) {
if (empty($string)) return false;
$string = strtolower($string);
$separators = array (',', '"', "'", "(", ")", " ");
$tokenizer = new tokenizer ();
$tokenizer->setLimits ($separators);
$tokenizer->tokenize($string);
$tokens = $tokenizer->getTokens(true);
// First check
$wordFound = false;
foreach ($tokens as $token) {
$wordFound = in_array($token, array('select', 'update', 'delete', 'insert'));
if ($wordFound == true) break;
}
if (($wordFound)&&(in_array('from', $tokens))) return true;
// Second check
$wordFound = false;
foreach ($tokens as $token) {
$wordFound = in_array($token, array('execute', 'exec', 'cast'));
if ($wordFound == true) break;
}
if (($wordFound)&&(in_array('(', $tokens))) return true;
return false;
}
static protected function castType ($value, $type) {
if (!is_scalar($value)) {
foreach ($value as $name => $el)
$value[$name] = self::castType ($el, $type);
return $value;
}
if (($type == PARAM_TYPE_BOOL) || ($type == PARAM_TYPE_AUTO)) {
if ($value === 'false') $value = false;
if ($value === 'true') $value = true;
}
switch ($type) {
case PARAM_TYPE_AUTO:
if (is_numeric($value)) $type = PARAM_TYPE_INT;
if (empty($value) || is_bool($value)) $type = PARAM_TYPE_BOOL;
default:
if (!settype ($value, $type)) trigger_error ('unableToCastParam', E_USER_ERROR);
}
return $value;
}
}
/**
* @package Param
*/
class paramVars {
public $name;
public $type = PARAM_TYPE_AUTO;
public $value;
public $dimension = PARAM_DIM_SINGLE;
public $description = '';
public $startGettingFromInput = true;
/**
* @var string|false class name where are defined custom functions. If false
* base "param" class will be used
*/
public $refClass = false;
/**
* @var int you can decide if this param must be retrieve from $_GET, $_POST or both
* the order in the array is preserved: if something is found for the first input type
* the others are not followed
*/
public $inputList = array(PARAM_INPUT_POST, PARAM_INPUT_GET, PARAM_INPUT_DEFAULT_VALUE);
public $maxLen;
public $defaultValue;
public $defaultValueDescription = '';
/**
* @var string HTML control type
*/
public $controlType = PARAM_CTRL_INPUT;
/**
* @param array an associative array with custom values
*/
function __construct ($array = array()) {
if (!is_array($array)) trigger_error ('varsToDefineParamMustBeAnArray', E_USER_ERROR);
if (empty($array)) return;
foreach ($this as $var => $value) {
if (isset($array[$var])) $this->$var = $array[$var];
}
}
}
?>