<?php
################################################################################
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- #
## --------------------------------------------------------------------------- #
## ApPHP DataValidator Basic version 1.0.2 (24.04.2012) #
## Developed by: ApPHP <hide@address.com> #
## License: GNU GPL v.2 #
## Site: http://www.apphp.com/php-datavalidator/ #
## Copyright: ApPHP DataValidator (c) 2012. All rights reserved. #
## #
################################################################################
/**
* Main class, accepts validation types, runs validation
*/
class Validator
{
/**
* indicates whether validation runs on after error or stops after first one
* @var bool
*/
private $stopIfErrorFlag = false;
/**
* contains all found errors
* @var array
*/
private $errorArray = array();
/**
* @property $validationArray array
* contains object of validation types
* @var array
*/
private $validationArray = array();
/**
* whether we have errors
* @var int
*/
private $hasErrorStatus = 0;
/**
* @var ValidatorDataContainer
*/
private $dataContainer;
/**
* add an object to be validated
* @param ValidatorType $type
*/
public function AddType(ValidatorType $type) {
$this->validationArray[ ] = $type;
}
/**
* launch validation
*/
public function Validate() {
// looking for a data source
if ( empty( $this->dataContainer ) ) {
$this->AddDataContainer($_REQUEST);
}
foreach ( $this->validationArray as $type ) {
// make sure it's a right class
if ( !is_a($type, 'ValidatorAnyType') ) {
throw new Exception( 'Only objects of ValidatorAnyType child classes are to be sent to validation' );
}
//finding the value to validate
$type->SetValue($this->dataContainer->GetValue($type->GetName()));
//launching current validation
if ( $this->_AllowToProceed() ) {
$type->Validate();
$this->_CollectError($type);
}
}
}
/**
* Getter for errorArray, consists of ValidatorError objects
* @return array
*/
public function GetErrorArray() {
return $this->errorArray;
}
/**
* Setter for stopIfErrorFlag
* @param $stopIfErrorFlag
*/
public function SetStopIfErrorFlag($stopIfErrorFlag) {
$this->stopIfErrorFlag = $stopIfErrorFlag;
}
/**
* Getter for hasErrorStatus
* @return int
*/
public function GetHasErrorStatus() {
return $this->hasErrorStatus;
}
/**
* @param array|Object $data
*/
public function AddDataContainer($data) {
if ( !empty( $this->dataContainer ) ) {
$this->dataContainer->AddContainer($data);
} else {
$this->dataContainer = new ValidatorDataContainer( $data );
}
}
/**
* an array [$key => $value] of validated data only
* @return array
*/
public function GetValidatedArray() {
$result = array();
foreach ( $this->validationArray as $type ) {
$result[ $type->GetName() ] = $type->GetValue();
}
return $result;
}
/**
* if stopIfErrorFlag is set and errors found, validation will be stopped
* @return bool
*/
private function _AllowToProceed() {
if ( !empty( $this->stopIfErrorFlag ) && !empty( $this->errorArray ) ) {
return false;
}
return true;
}
/**
* Collects errors from a validatorType into one for all errorArray
* @param ValidatorType $type
*/
private function _CollectError(ValidatorType $type) {
if ( $type->GetHasError() ) {
$this->hasErrorStatus = 1;
$this->errorArray = array_merge($this->errorArray, $type->GetErrorArray());
}
}
}
/**
* Stores data as array(key=>value), gets an array or object with data from user or uses $_REQUEST by default
*/
class ValidatorDataContainer
{
/**
* array where values for validation will be looked for
* @var #Fget_object_vars|#V_REQUEST|array|?
*/
private $dataContainer = array();
/**
* accepts any count of data sources (arrays or objects). Keeps them till is manually cleaned. If values in
* different sources have same keys, later added rewrites earler added
* @param #func_get_args ?
*/
public function __construct() {
$args = func_get_args();
if ( !empty( $args ) ) $args = $this->_MergeArrays($args);
$this->_AddContainer($args);
}
/*
* set custom array or $_REQUEST will be used, pass to it any count of arrays or objects. Previous container will
* be merged with new values
* @param #func_get_args ?
*/
public function AddContainer() {
$args = func_get_args();
if ( !empty( $args ) ) $args = $this->_MergeArrays($args);
$this->_AddContainer($args);
}
/**
* gets a value from data array by name
* @param $name
* @return mixed
* @throws Exception if wrong name
*/
public function GetValue($name) {
if ( empty( $this->dataContainer ) || !isset( $this->dataContainer[ $name ] ) ) {
throw new Exception( "No such field '$name' in data container" );
}
return $this->dataContainer[ $name ];
}
/**
* Checks if field exists
* @param $name
* @return bool
*/
public function IfFieldExists($name) {
if ( isset( $this->dataContainer[ $name ] ) ) {
return true;
}
return false;
}
/**
* getter for current data array
* @return array
*/
public function GetContainerArray() {
return $this->dataContainer;
}
/**
* merges new data with existing in container. If contaner and data are empty takes $_REQUEST
* @param array $data
*/
private function _AddContainer(array $data = array()) {
$this->dataContainer = ( !empty( $this->dataContainer ) ) ? array_merge($data, $this->dataContainer) : $data;
if ( empty( $this->dataContainer ) && empty( $data ) ) $this->dataContainer = $_REQUEST;
}
/**
* if object is sent, make it an array
* @param $data
* @return array
*/
private function _ParseObject(&$data) {
if ( is_object($data) ) {
return get_object_vars($data);
}
return $data;
}
/**
*
* @param array $args
* @return array
*/
private function _MergeArrays(array $args) {
$dataSource = array();
foreach ( $args as $arg ) {
$arg = $this->_ParseObject($arg);
$dataSource = array_merge($dataSource, $arg);
}
return $dataSource;
}
}
/**
* Validator types must be validated, give error messages, be able to show there description.
*/
interface ValidatorType
{
public function Validate();
public function GetHasError();
public function GetErrorArray();
public function ToString();
}
/**
* Validator type parent
*/
class ValidatorAnyType
{
/**
* if subtype needed, it should be set from validation type static public properties which starts with "subtype"
* @var string
*/
protected $subtype;
/**
* name for field in array
* @var
*/
protected $name;
/**
* name for error messages - if needed. $name will be used if var is empty
* @var string
*/
protected $userFriendlyName;
/**
* value for validation, taken from a data source by name
* @var mixed
*/
protected $value;
/**
* if field can be empty (null, "", 'null', 0)
* @var null
*/
protected $canBeNullFlag = null;
/**
* states if we run an emptiness check
* @var int
*/
protected $canBeNullFlagIsSet = 0;
/**
* if any errors has got while validating this type
* @var
*/
private $hasError;
/**
* all errors for this type
* @var array
*/
private $errorArray = array();
/**
* @param $name - by what name look for field
* @param string $subtype - might be empty, but needed for string and numbers
* @param string $userFriendlyName - might be empty, used for error messages
*/
protected function __construct($name, $subtype = '', $userFriendlyName = '') {
if ( empty( $name ) ) {
throw new Exception( 'Field name should not be empty' );
}
$this->name = $name;
$this->subtype = $subtype;
$this->userFriendlyName = !empty( $userFriendlyName ) ? $userFriendlyName : $name;
}
/**
* getter for a has errors flag
* @return mixed
*/
public function GetHasError() {
return $this->hasError;
}
/**
* Getter for errorArray
* @return array
*/
public function GetErrorArray() {
return $this->errorArray;
}
public function GetName() {
return $this->name;
}
public function SetValue($value) {
$this->value = $value;
}
public function GetValue() {
return $this->value;
}
/**
* setter for can be null flag, that is if value can be empty. Empty is null, "", "null", 0
* @param $canBeNullFlag
*/
public function SetCanBeNullFlag($canBeNullFlag) {
$this->canBeNullFlag = $canBeNullFlag;
$this->canBeNullFlagIsSet = 1;
}
/**
* if string can be empty (null, "", "null", 0)
* @return bool
*/
protected function _EmptinessCheck() {
//if no check for emptyness
if ( !$this->canBeNullFlagIsSet ) {
return;
}
// if empty and can be empty - consider checked
if ( $this->canBeNullFlag && ( $this->value === null || $this->value === 'null' || $this->value === '' || $this->value === 0 ) ) {
return true;
}
// if empty and can't be empty - error
if ( !$this->canBeNullFlag && ( $this->value === null || $this->value === 'null' || $this->value === '' || $this->value === 0 ) ) {
$this->_HasError(new ValidatorError ( 'CAN_NOT_BE_NULL', $this->name, array('fieldName' => $this->userFriendlyName) ));
return true;
}
return false;
}
/**
* actions for error - add error to array, set status hasError
* @param ValidatorError $error
*/
protected function _HasError(ValidatorError $error) {
$this->errorArray[ ] = $error;
$this->hasError = 1;
}
}
/**
* Strings for validation
*/
class ValidatorTypeString extends ValidatorAnyType implements ValidatorType
{
/**
* subtypes
* @var string
*/
public static $subtypeAlphabetic = 'alphabetic';
public static $subtypeAlphanumeric = 'alphanumeric';
/**
* additional options
* @var null
*/
private $minLen = null;
private $maxLen = null;
private $spacesAllowedFlag = null;
private $pointingAllowedFlag = null;
/**
* @param $name string name of field in a container
* @param $subtype string subtype, use public static parameters of this class named starting with "subtype"
* @param string $userFriendlyName name used in user-friendly error messages, if not set $name will be used
*/
function __construct($name, $subtype, $userFriendlyName = '') {
if ( empty( $subtype ) ) {
throw new Exception( "Subtype for string $name can not be empty" );
}
parent::__construct($name, $subtype, $userFriendlyName);
}
/**
* Validates current string
* @return mixed
*/
public function Validate() {
// if field empty, no use to check any more. Can have errors thou
if ( $this->_EmptinessCheck() ) {
return;
}
$this->_ValidateString();
$this->_ValidateSubtype();
}
/**
* setter if set string will be checked on maximum length, if no set - will not be checked
* @param $maxLen
*/
public function SetMaxLen($maxLen) {
$this->maxLen = $maxLen;
}
/**
* setter if set string will be checked on minimum length, if no set - will not be checked
* @param $minLen
*/
public function SetMinLen($minLen) {
$this->minLen = $minLen;
}
/**
* setter if set pointing presence or absence will be checked, if not set - will not be checked
* @param $pointingAllowedFlag
*/
public function SetPointingAllowedFlag($pointingAllowedFlag) {
$this->pointingAllowedFlag = $pointingAllowedFlag;
}
/**
* setter if set spaces presence or absence will be checked, if not set - will not be checked
* @param $spacesAllowedFlag
*/
public function SetSpacesAllowedFlag($spacesAllowedFlag) {
$this->spacesAllowedFlag = $spacesAllowedFlag;
}
/**
* forms string with current validation type parameters
* @return string
*/
public function ToString() {
$toString = "String, subtype = '{$this->subtype}', value = '{$this->value}'";
$toString .= ( $this->canBeNullFlag !== null ) ? ", canBeNullFlag = {$this->canBeNullFlag}" : '';
$toString .= ( $this->maxLen !== null ) ? ", maxLen = {$this->maxLen}" : '';
$toString .= ( $this->minLen !== null ) ? ", minLen = {$this->minLen}" : '';
$toString .= ( $this->pointingAllowedFlag !== null ) ? ", pointingAllowedFlag = {$this->pointingAllowedFlag}" : '';
$toString .= ( $this->spacesAllowedFlag !== null ) ? ", spacesAllowedFlag = {$this->spacesAllowedFlag}" : '';
return $toString;
}
/**
* validates stated subtypes together with pointing and spaces properties
* @return mixed
* @throws Exception
*/
private function _ValidateSubtype() {
switch ( $this->subtype ) {
case self::$subtypeAlphabetic:
// any text
if ( $this->spacesAllowedFlag && $this->pointingAllowedFlag ) {
if ( !preg_match('/^[\p{L}\p{P}\p{S} ]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => 'alphabetic') ));
}
}
// one word, any sign
if ( !$this->spacesAllowedFlag && $this->pointingAllowedFlag ) {
if ( !preg_match('/^[\p{L}\p{P}\p{S}]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'ALPHABETIC_SPACES_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
// spaces allowed, but no pointing
if ( $this->spacesAllowedFlag && !$this->pointingAllowedFlag ) {
if ( !preg_match('/^[\p{L} ]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'ALPHABETIC_POINTING_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
// one word, no pointing
if ( !$this->spacesAllowedFlag && !$this->pointingAllowedFlag ) {
if ( !preg_match('/^[\p{L}]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'ALPHABETIC_SPACES_POINTING_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
break;
case self::$subtypeAlphanumeric:
// any text
if ( $this->spacesAllowedFlag && $this->pointingAllowedFlag ) {
return;
}
// one word, any sign
if ( !$this->spacesAllowedFlag && $this->pointingAllowedFlag ) {
if ( strpos($this->value, ' ') !== false ) {
$this->_HasError(new ValidatorError ( 'SPACES_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
// spaces allowed, but no pointing
if ( $this->spacesAllowedFlag && !$this->pointingAllowedFlag ) {
if ( !preg_match('/^[\w\d\s]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'POINTING_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
// one word, no pointing
if ( !$this->spacesAllowedFlag && !$this->pointingAllowedFlag ) {
if ( !preg_match('/^[\w\d]+$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'SPACES_POINTING_NOT_ALLOWED', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
break;
default:
throw new Exception( "Unknown subtype for string {$this->userFriendlyName}" );
}
}
/**
* validates on length etc
* @return mixed
*/
private function _ValidateString() {
// is string?
if ( !is_string($this->value) ) {
$this->_HasError(new ValidatorError ( 'WRONG_TYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredType' => 'string') ));
return;
}
//minlen
if ( $this->minLen !== null && mb_strlen($this->value) < $this->minLen ) {
$this->_HasError(new ValidatorError ( 'LENGTH_LESS_THEN_MIN', $this->name, array('fieldName' => $this->userFriendlyName,
'minLength' => $this->minLen) ));
}
//maxlen
if ( $this->maxLen !== null && mb_strlen($this->value) > $this->maxLen ) {
$this->_HasError(new ValidatorError ( 'LENGTH_MORE_THEN_MAX', $this->name, array('fieldName' => $this->userFriendlyName,
'maxLength' => $this->maxLen) ));
}
}
}
/**
* Numbers for validation
*/
class ValidatorTypeNumeric extends ValidatorAnyType implements ValidatorType
{
/**
* SUBTYPES
* @var string
*/
public static $subtypeInt = 'int';
public static $subtypeFloat = 'float';
public static $subtypeNumeric = 'numeric';
/**
* Additional params
* @var null
*/
private $min = null;
private $max = null;
/**
* create validation type for number
* @param $name string name of key in $dataContainer
* @param $subtype string to set subtype use parameters of class whose name starts with "subtype"
* @param string $userFriendlyName friendly name will be used in error messages, if not set $name will be used
*/
function __construct($name, $subtype, $userFriendlyName = '') {
if ( empty( $subtype ) ) {
throw new Exception( "Subtype for number $name can not be empty" );
}
parent::__construct($name, $subtype, $userFriendlyName);
}
public function Validate() {
// if field empty, no use to check any more. Can have errors thou
if ( $this->_EmptinessCheck() ) {
return;
}
// subtype
$this->_CheckNumericSubtype($this->userFriendlyName, $this->subtype, $this->value);
//MIN
if ( $this->min !== null ) {
if ( $this->value < $this->min ) {
$this->_HasError(new ValidatorError ( 'LESS_THEN_MIN', $this->name, array('fieldName' => $this->userFriendlyName,
'min' => $this->min) ));
}
}
//MAX
if ( $this->max !== null ) {
if ( $this->value > $this->max ) {
$this->_HasError(new ValidatorError ( 'MORE_THEN_MAX', $this->name, array('fieldName' => $this->userFriendlyName,
'max' => $this->max) ));
}
}
}
/**
* check for exact type
*/
private function _CheckNumericSubtype() {
switch ( $this->subtype ) {
//int
case self::$subtypeInt:
if ( !is_int($this->value) ) {
$this->_HasError(new ValidatorError ( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => self::$subtypeInt) ));
}
break;
//float
case self::$subtypeFloat:
if ( !is_float($this->value) ) {
$this->_HasError(new ValidatorError ( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => self::$subtypeFloat) ));
}
break;
//numeric
case self::$subtypeNumeric:
if ( !is_numeric($this->value) ) {
$this->_HasError(new ValidatorError ( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => self::$subtypeNumeric) ));
}
break;
//unknown
default:
$this->_HasError('UNKNOWN_SUBTYPE', array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => $this->subtype));
}
}
/**
* setter for max, if not set will not be checked
* @param $max
*/
public function SetMax($max) {
$this->max = $max;
}
/**
* setter for min if not set will not be checked
* @param $min
*/
public function SetMin($min) {
$this->min = $min;
}
/**
* returns a toString descriptions of properties for current validation type
* @return string
*/
public function ToString() {
$toString = "Numeric, subtype = '{$this->subtype}', value = '{$this->value}', 'userFriendlyName' = '$this->userFriendlyName'";
$toString .= ( $this->canBeNullFlag !== null ) ? ", canBeNullFlag = $this->canBeNullFlag" : '';
$toString .= ( $this->max !== null ) ? ", max = $this->max" : '';
$toString .= ( $this->min !== null ) ? ", min = $this->min" : '';
return $toString;
}
}
/**
* validator for email
*/
class ValidatorTypeEmail extends ValidatorAnyType implements ValidatorType
{
/**
* create a validation for email
* @param $name string key name for a value in $dataContainer
* @param string $userFriendlyName used in error messages, if not set $name will be used
*/
public function __construct($name, $userFriendlyName = '') {
parent::__construct($name, '', $userFriendlyName);
}
/**
* starts current type validation
*/
public function Validate() {
// if field empty, no use to check any more. Can have errors thou
if ( $this->_EmptinessCheck() ) {
return;
}
// if string
if ( !is_string($this->value) ) {
$this->_HasError(new ValidatorError ( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->value,
'requiredSubtype' => 'string') ));
}
// if correct email
if ( !preg_match('/^[A-Za-z0-9](([a-zA-Z0-9_\.\-]+)*)@[a-z0-9.]+[a-z]{2,6}$/', $this->value, $matches) ) {
$this->_HasError(new ValidatorError ( 'INVALID_EMAIL', $this->name, array('fieldName' => $this->userFriendlyName) ));
}
}
/**
* returns a toString descriptions of properties for current validation type
* @return string
*/
public function ToString() {
return "Email value = '$this->value', userFriendlyName = '$this->userFriendlyName'";
}
}
/**
* validator for URL
*/
class ValidatorTypeUrl extends ValidatorAnyType implements ValidatorType
{
/**
* @param $name string name of key in $dataContainer
* @param string $userFriendlyName used in error messages, if not set $name will be used
*/
public function __construct($name, $userFriendlyName = '') {
parent::__construct($name, '', $userFriendlyName);
}
/**
* starts current type validation
*/
public function Validate() {
// if field empty, no use to check any more. Can have errors thou
if ( $this->_EmptinessCheck() ) {
return;
}
if ( !is_string($this->value) ) {
$this->_HasError(new ValidatorError( 'WRONG_SUBTYPE', $this->name, array('fieldName' => $this->userFriendlyName,
'requiredSubtype' => 'string') ));
}
if ( !preg_match('/^((http|https|ftp):\/\/|www)[a-zA-Z0-9\-\._]+\/?[a-zA-Z0-9_\.\-\?\+\/~=&#;,
<:]*[a-z0-9\/]{1}$/si',
$this->value, $matches)
) {
$this->_HasError(new ValidatorError ( 'INVALID_URL', $this->name ));
}
}
/**
* returns description
* @return string
*/
public function ToString() {
return "URL value = '{$this->value}', 'userFriendlyName' = '{$this->userFriendlyName}'";
}
}
/**
* user friendly errors while validation
*/
class ValidatorError
{
/**
* which error
* @var
*/
private $errType;
/**
* current error data
* @var array
*/
private $data;
/**
* name of the field error belongs to
* @var
*/
private $fieldName;
/**
* error types
* @var array
*/
static $errorTypeArray = array(
'DEFAULT' => 'Error',
'NO_FIELD' => 'No such field: %fieldName%',
'EMPTY_FIELD' => 'Empty required field: "%fieldName%"',
'WRONG_SUBTYPE' => 'Wrong subtype of field: "%fieldName%". Required: %requiredSubtype%',
'WRONG_TYPE' => 'Wrong type of field: "%fieldName%". Required: %requiredType%',
'UNKNOWN_SUBTYPE' => 'Field "%fieldName%" is checked for an unknown subtype: %requiredSubtype%',
'LESS_THEN_MIN' => 'Value of field "%fieldName%" can not be less then %min%',
'MORE_THEN_MAX' => 'Value of field "%fieldName%" can not be more then %max%',
'LENGTH_LESS_THEN_MIN' => 'Length of field "%fieldName%" should be more then %minLength%',
'LENGTH_MORE_THEN_MAX' => 'Length of field "%fieldName%" should be less then %maxLength%',
'SPACES_NOT_ALLOWED' => 'Field "%fieldName%" should contain only one world',
'POINTING_NOT_ALLOWED' => 'Field "%fieldName%" contains wrong symbols: only letters, digits and spaces are allowed',
'SPACES_POINTING_NOT_ALLOWED' => 'Field "%fieldName%" contains wrong symbols: only letters and digits are allowed',
'ALPHABETIC_SPACES_NOT_ALLOWED' => 'Field "%fieldName%" should contain only one world',
'ALPHABETIC_POINTING_NOT_ALLOWED' => 'Field "%fieldName%" contains wrong symbols: only letters and spaces are allowed',
'ALPHABETIC_SPACES_POINTING_NOT_ALLOWED' => 'Field "%fieldName%" contains wrong symbols: only letters are allowed',
'INVALID_EMAIL' => 'Email is not valid',
'INVALID_URL' => 'URL is not valid',
'CAN_NOT_BE_NULL' => 'Field "%fieldName%" can not be null',
);
public function __construct($errType, $fieldName, array $additionalData = array()) {
$this->errType = $errType;
$this->data = $additionalData;
$this->fieldName = $fieldName;
}
/**
* Form standard errors in a user friendly view
* @return string
*/
public function ToString() {
if ( isset( self::$errorTypeArray[ $this->errType ] ) ) {
$error = self::$errorTypeArray[ $this->errType ];
foreach ( $this->data as $infoName => $infoValue ) {
$error = str_replace("%$infoName%", $infoValue, $error);
}
return $error;
}
if ( isset( self::$errorTypeArray[ 'DEFAULT' ] ) ) {
return self::$errorTypeArray[ 'DEFAULT' ];
}
return 'Unknown error';
}
/**
* getter for error type
* @return mixed
*/
public function GetErrType() {
return $this->errType;
}
/**
* getter for current error data
* @return array
*/
public function GetErrData() {
return $this->data;
}
/**
* getter for name of field current error belongs to
*/
public function GetFieldName() {
return $this->fieldName;
}
}
?>