e107_handlers/validator_class.php
<?php
/*
* e107 website system
*
* Copyright (C) 2008-2009 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* Handler - general purpose validation functions
*
* $Source: /cvs_backup/e107_0.8/e107_handlers/validator_class.php,v $
* $Revision$
* $Date$
* $Author$
*
*/
if (!defined('e107_INIT'))
{
exit;
}
// List of error numbers which may be returned from validation
define('ERR_MISSING_VALUE', '01');
define('ERR_UNEXPECTED_VALUE', '02');
define('ERR_INVALID_CHARS', '03');
define('ERR_TOO_SHORT', '04');
define('ERR_TOO_LONG', '05');
define('ERR_DUPLICATE', '06');
define('ERR_DISALLOWED_TEXT', '07');
define('ERR_DISALLOWED_TEXT_EXACT_MATCH', '23');
define('ERR_FIELD_DISABLED', '08');
define('ERR_INVALID_WORD', '09');
define('ERR_PASSWORDS_DIFFERENT', '10');
define('ERR_BANNED_EMAIL', '11');
define('ERR_INVALID_EMAIL', '12');
define('ERR_ARRAY_EXPECTED', '13');
define('ERR_BANNED_USER', '14');
define('ERR_FIELDS_DIFFERENT', '15');
define('ERR_CODE_ERROR', '16');
define('ERR_TOO_LOW', '17');
define('ERR_TOO_HIGH', '18');
define('ERR_GENERIC', '19'); // This requires coder-defined error text
define('ERR_IMAGE_TOO_WIDE', '20');
define('ERR_IMAGE_TOO_HIGH', '21');
// Default error messages
e107::includeLan(e_LANGUAGEDIR . e_LANGUAGE . '/admin/lan_validator.php');
/**
* Validator class - used by e_model and its child classes
*
* @package e107
* @category e107_handlers
* @version 1.0
* @author SecretR
* @copyright Copyright (c) 2009, e107 Inc.
*/
class e_validator
{
/**
* @var integer Unknown error code
*/
const ERR_UNKNOWN = 0;
/**
* @var integer Value not found error code
*/
const ERR_MISSING_VALUE = 101;
/**
* @var integer Unexpected value type error code (bad rule)
*/
const ERR_UNEXPECTED_VALUE = 102;
/**
* @var integer Invalid characters error code
*/
const ERR_INVALID_CHARS = 103;
/**
* @var integer Invalid email error code
*/
const ERR_INVALID_EMAIL = 104;
/**
* @var integer Field doesn't match error code
*/
const ERR_FIELDS_MATCH = 105;
/**
* @var integer String too short error code
*/
const ERR_TOO_SHORT = 131;
/**
* @var integer String too long error code
*/
const ERR_TOO_LONG = 132;
/**
* @var integer Number too low error code
*/
const ERR_TOO_LOW = 133;
/**
* @var integer Number too high error code
*/
const ERR_TOO_HIGH = 134;
/**
* @var integer Array count too low error code
*/
const ERR_ARRCOUNT_LOW = 135;
/**
* @var integer Array count high error code
*/
const ERR_ARRCOUNT_HIGH = 136;
/**
* @var integer Type of integer expected error code
*/
const ERR_INT_EXPECTED = 151;
/**
* @var integer Type of float expected error code
*/
const ERR_FLOAT_EXPECTED = 152;
/**
* @var integer Instance type expected error code
*/
const ERR_INSTANCEOF_EXPECTED = 153;
/**
* @var integer Array type expected error code
*/
const ERR_ARRAY_EXPECTED = 154;
/**
* @var integer Generic (empty value) error code
*/
const ERR_GENERIC = 191;
/**
* @var integer File not exists or not a file error code
*/
const ERR_NOT_FILE = 201;
/**
* @var integer File not writable error code
*/
const ERR_WRITABLE_FILE = 202;
/**
* @var integer File exceeds allowed file size error code
*/
const ERR_SIZEMIN_FILE = 203;
/**
* @var integer File lower than minimal file size error code
*/
const ERR_SIZEMAX_FILE = 204;
/**
* Required rules - Used by validate method
*
* Structure: array(type, condition, field title LAN[, condition help, validation error message]);
*
* @example $_required_rules['download_category_id'] = array('int', '1', 'Download Category', 'choose category')
*
* Validation array structure:
* - type | condition =
* - regex | regex string
* - email | no condition required
* - int/integer | number range in format 'min:max'
* - float | number range in format 'min:max'
* - str/string | number string length range in format 'min:max'
* - required | no condition required
* - callback | string function name or array(class name|object, method) (static call)
* - instanceof | string class name
* - enum | (string) values separated by '#' e.g. 'match1#match2#match3'
* - array | array count range in format 'min:max'
* - compare | string field_name, value should be in format field_name => array('value1', 'value1')
* if value1 === value1, field_name => value1 will be added to $_valid_data array
* - field title LAN =
* human readable field (data key) name
* - [optional] condition help =
* can be used for both inline field help and validation error message
* - [optional] validation error message =
* if empty condition help will be used
*
* @var array
*/
protected $_required_rules = array();
/**
* Check data only if exist/non-empty
*
* @var array
*/
protected $_optional_rules = array();
/**
* Contains validation error codes in format 'field=>error_code'
* @var array
*/
protected $_validation_results = array();
/**
* Stores validated data (only after successful {@link validateField()} call
* @var array
*/
protected $_valid_data = array();
/**
* Stores validate check result
* @var boolean
*/
protected $_is_valid_data = true;
/**
* eMessage handler namespace
*
* @var string
*/
protected $_message_stack = 'validator';
/**
* Constructore
* @param string [optional] $message_stack [optional] eMessage handler namespace
* @param array [optional] $rules validation rules
* @param array [optional] $optrules optional validation rules
*/
public function __construct($message_stack = '', $rules = array(), $optrules = array())
{
$this->setMessageStack($message_stack)
->setRules($rules)
->setOptionalRules($optrules);
}
/**
* Set message stack
*
* @param string $mstack
* @return e_validator
*/
public function setMessageStack($mstack)
{
if (!$mstack)
{
$mstack = 'validator';
}
$this->_message_stack = $mstack;
return $this;
}
/**
* @param array $rules
* @return e_validator
*/
public function setRules($rules)
{
$this->_required_rules = $rules;
return $this;
}
/**
* @param array $rules
* @return e_validator
*/
public function setOptionalRules($rules)
{
$this->_optional_rules = $rules;
return $this;
}
/**
* Add successfully validated data to the valid array
*
* @param string $field_name
* @param mixed $value
* @return e_validator
*/
protected function addValidData($field_name, $value)
{
$this->_valid_data[$field_name] = $value;
return $this;
}
/**
* @return array
*/
public function getValidData()
{
return $this->_valid_data;
}
/**
* Validate data
*
* @param array $data
* @param boolean $availableOnly check only against available data if true
* @return boolean
*/
function validate($data, $availableOnly = false)
{
$this->reset();
$rules = array_merge(array_keys($this->_required_rules), array_keys($this->_optional_rules));
// no rules, no check
if (!$rules)
{
$this->setIsValidData(true);
$this->_valid_data = $data;
return true;
}
$fieldList = $rules;
if ($availableOnly)
{
$fieldList = array_keys($data);
}
foreach ($rules as $field_name)
{
if (!in_array($field_name, $fieldList))
{
continue;
}
$value = varset($data[$field_name], null);
$required = $this->isRequiredField($field_name);
if (($required || $this->isOptionalField($field_name)) && !$this->validateField($field_name, $value, $required))
{
$this->setIsValidData(false);
$this->addValidateMessage($this->getFieldName($field_name, $required), $this->getErrorCode($field_name), $this->getFieldMessage($field_name, $value, $required));
continue;
}
}
return $this->_is_valid_data;
}
/**
* Check if field is required
*
* @param string $name
* @return boolean
*/
function isRequiredField($name)
{
return isset($this->_required_rules[$name]);
}
/**
* Check if there is optional rule for this field
*
* @param string $name
* @return boolean
*/
function isOptionalField($name)
{
return isset($this->_optional_rules[$name]);
}
/**
* Retrieve help for the required field
*
* @param string $name
* @return string
*/
function getFieldHelp($name, $required = true, $default = '')
{
if ($required)
{
$msg = (isset($this->_required_rules[$name][3]) ? $this->_required_rules[$name][3] : $default);
}
else
{
$msg = (isset($this->_optional_rules[$name][3]) ? $this->_optional_rules[$name][3] : $default);
}
return defset($msg, $msg);
}
/**
* Retrieve validation error message for the required field
*
* @param string $name
* @param mixed $value
* @return string
*/
function getFieldMessage($name, $value = '', $required = true)
{
if ($required)
{
if (!isset($this->_required_rules[$name][4]))
{
$msg = $this->getFieldHelp($name);
}
else
{
$msg = $this->_required_rules[$name][4];
}
}
else
{
if (!isset($this->_optional_rules[$name][4]))
{
$msg = $this->getFieldHelp($name, false);
}
else
{
$msg = $this->_optional_rules[$name][4];
}
}
return ($msg ? defset($msg, $msg) : '');
}
/**
* @param string $name
* @return string
*/
function getFieldName($name, $required = true)
{
if ($required)
{
$msg = (isset($this->_required_rules[$name][2]) ? $this->_required_rules[$name][2] : $name);
}
else
{
$msg = (isset($this->_optional_rules[$name][2]) ? $this->_optional_rules[$name][2] : $name);
}
return defset($msg, $msg);
}
/**
* Validate single field
*
* @param string $name
* @param string $value
* @param boolean $required
* @return boolean
*/
function validateField($name, $value, $required = true)
{
if ($required)
{
$type = $this->_required_rules[$name][0];
$cond = $this->_required_rules[$name][1];
}
else
{
if (empty($value))
{
switch ($this->_optional_rules[$name][0])
{
case 'int':
case 'integer':
$value = 0;
break;
case 'float':
$value = 0.00;
break;
case 'array':
$value = array();
break;
default:
$value = '';
break;
}
$this->addValidData($name, $value);
return true;
}
$type = $this->_optional_rules[$name][0];
$cond = $this->_optional_rules[$name][1];
}
switch ($type)
{
case 'required':
if (empty($value))
{
$this->addValidateResult($name, self::ERR_GENERIC);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'email':
if (!check_email($value))
{
$this->addValidateResult($name, self::ERR_INVALID_EMAIL);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'regexp':
case 'regex':
if (!preg_match($cond, $value))
{
$this->addValidateResult($name, self::ERR_INVALID_CHARS);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'callback':
if (!call_user_func($cond, $value))
{
$this->addValidateResult($name, self::ERR_INVALID_CHARS);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'instanceof':
if (!(is_object($value) && $value instanceof $cond))
{
$this->addValidateResult($name, self::ERR_INSTANCEOF_EXPECTED);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'int':
case 'integer':
if (!preg_match('/^-?[\d]+$/', $value)) // negative values support
{
$this->addValidateResult($name, self::ERR_INT_EXPECTED);
return false;
}
// BC! Will be removed after we replace '-' with ':' separator!
$tmp = $this->parseMinMax($cond);
if (is_numeric($tmp[0]) && (integer) $tmp[0] > (integer) $value)
{
$this->addValidateResult($name, self::ERR_TOO_LOW);
return false;
}
if (is_numeric(varset($tmp[1])) && (integer) $tmp[1] < (integer) $value)
{
$this->addValidateResult($name, self::ERR_TOO_HIGH);
return false;
}
$this->addValidData($name, (int) $value);
return true;
break;
case 'str':
case 'string':
case 'text':
case 'varchar':
$tmp = $this->parseMinMax($cond);
$length = e107::getParser()->ustrlen($value);
if (is_numeric($tmp[0]) && (integer) $tmp[0] > $length)
{
$this->addValidateResult($name, self::ERR_TOO_SHORT);
return false;
}
if ('varchar' === $type && !varset($tmp[1]))
{
$tmp[1] = 255;
}
if (is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length)
{
$this->addValidateResult($name, self::ERR_TOO_LONG);
return false;
}
$this->addValidData($name, (string) $value);
return true;
break;
case 'set':
case 'enum':
$tmp = array_map('trim', explode(',', $cond));
if (!$value || !in_array($value, $tmp))
{
$this->addValidateResult($name, self::ERR_FIELDS_MATCH);
return false;
}
$this->addValidData($name, (string) $value);
return true;
break;
case 'float':
$value = e107::getParser()->toNumber($value);
if (!is_numeric($value))
{
$this->addValidateResult($name, self::ERR_FLOAT_EXPECTED);
return false;
}
$tmp = $this->parseMinMax($cond);
if (is_numeric($tmp[0]) && (float) $tmp[0] > (float) $value)
{
$this->addValidateResult($name, self::ERR_TOO_LOW);
return false;
}
if (is_numeric(varset($tmp[1])) && (float) $tmp[1] < (float) $value)
{
$this->addValidateResult($name, self::ERR_TOO_HIGH);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'array':
if (!is_array($value))
{
$this->addValidateResult($name, self::ERR_ARRAY_EXPECTED);
return false;
}
$length = count($value);
$tmp = $this->parseMinMax($cond);
if (is_numeric($tmp[0]) && (integer) $tmp[0] > $length)
{
$this->addValidateResult($name, self::ERR_ARRCOUNT_LOW);
return false;
}
if (is_numeric(varset($tmp[1])) && (float) $tmp[1] < $length)
{
$this->addValidateResult($name, self::ERR_ARRCOUNT_HIGH);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'file': // TODO - type image - validate dimensions?
parse_str($cond, $params);
$path = e107::getParser()->replaceConstants(varset($params['base']) . $value);
if (!$value || !is_file($path))
{
$this->addValidateResult($name, self::ERR_NOT_FILE);
return false;
}
if (!empty($params['writable']) && !is_writable($path))
{
$this->addValidateResult($name, self::ERR_WRITABLE_FILE);
return false;
}
if (!empty($params['size']))
{
$tmp = $this->parseMinMax($cond);
$fs = filesize($path);
if (!$fs || (integer) $tmp[0] > $fs)
{
$this->addValidateResult($name, self::ERR_SIZEMIN_FILE);
return false;
}
elseif (is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $fs)
{
$this->addValidateResult($name, self::ERR_SIZEMAX_FILE);
return false;
}
}
if (is_numeric(varset($params['maxlen'])) && (integer) $params['maxlen'] < e107::getParser()->ustrlen($value))
{
$this->addValidateResult($name, self::ERR_TOO_LONG);
return false;
}
$this->addValidData($name, $value);
return true;
break;
case 'compare':
if (!is_array($value))
{
$this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE);
return false;
}
if (!($value[0] && $value[1] && $value[0] == $value[1]))
{
$this->addValidateResult($name, self::ERR_FIELDS_MATCH);
return false;
}
// check length
if ($cond)
{
$tmp = $this->parseMinMax($cond);
$length = e107::getParser()->ustrlen($value[0]);
if (is_numeric($tmp[0]) && (integer) $tmp[0] > $length)
{
$this->addValidateResult($name, self::ERR_TOO_SHORT);
return false;
}
if (is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length)
{
$this->addValidateResult($name, self::ERR_TOO_LONG);
return false;
}
}
$this->addValidData($name, $value[0]);
return true;
break;
case 'compare_strict':
if (!is_array($value))
{
$this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE);
return false;
}
if (!($value[0] && $value[1] && $value[0] === $value[1]))
{
$this->addValidateResult($name, self::ERR_FIELDS_MATCH);
return false;
}
// check length
if ($cond)
{
$tmp = $this->parseMinMax($cond);
$length = e107::getParser()->ustrlen($value[0]);
if (is_numeric($tmp[0]) && (integer) $tmp[0] > $length)
{
$this->addValidateResult($name, self::ERR_TOO_SHORT);
return false;
}
if (is_numeric(varset($tmp[1])) && (integer) $tmp[1] < $length)
{
$this->addValidateResult($name, self::ERR_TOO_LONG);
return false;
}
}
$this->addValidData($name, $value[0]);
return true;
break;
default:
$this->addValidateResult($name, self::ERR_UNEXPECTED_VALUE);
return false;
break;
}
}
// moved to e_parse
// public function toNumber($value)
// {
// $larr = localeconv();
// $search = array(
// $larr['decimal_point'],
// $larr['mon_decimal_point'],
// $larr['thousands_sep'],
// $larr['mon_thousands_sep'],
// $larr['currency_symbol'],
// $larr['int_curr_symbol']
// );
// $replace = array('.', '.', '', '', '', '');
// return str_replace($search, $replace, $value);
// }
/**
* @param $string
* @return array
*/
protected function parseMinMax($string)
{
return explode(':', $this->_convertConditionBC($string), 2);
}
/**
* @param $condition
* @return array|mixed|string|string[]|null
*/
private function _convertConditionBC($condition)
{
// BC! Will be removed after we replace '-' with ':' separator!
if (strpos($condition, ':') === false)
{
return preg_replace('/^([0-9]+)-([0-9]+)$/', '$1:$2', $condition);
}
return $condition;
}
/**
* Add validation error to validate result stack
*
* @param string $field_title
* @param string $err_code
* @param string $err_message
* @param string $custom
* @return e_validator
*/
function addValidateMessage($field_title, $err_code = 0, $err_message = '', $custom = '')
{
$tp = e107::getParser();
$lanVars = array(
'x' => $field_title,
'y' => $err_code,
'z' => $this->getErrorByCode($err_code)
);
if ($custom)
{
e107::getMessage()->addStack(sprintf($err_message, $err_code, $field_title), $this->_message_stack, (true === $custom ? E_MESSAGE_ERROR : $custom));
return $this;
}
//Additional message
$lan = LAN_VALIDATE_FAILMSG;
$dbgmsg = false;
if ($err_message)
{
$lan = (!$field_title || strpos($err_message, '[x]') !== false ? '' : '[x] - ') . $err_message; // custom, e.g. // default '<strong>"%1$s"</strong> field error: Custom error message. '
$dbgmsg = LAN_VALIDATE_FAILMSG;
}
//Core message
/*
$msg = sprintf(
$lan, // default '<strong>"%1$s"</strong> validation error: [#%2$d] %3$s. '
$field_title,
$err_code,
$this->getErrorByCode($err_code)
);
*/
//NEW - removes need for using sprintf()
$msg = $tp->lanVars($lan, $lanVars, true); // '[x] validation error: [y] [z].'
if ($dbgmsg && defset('e107_DEBUG_LEVEL'))
{
e107::getMessage()->addDebug($tp->lanVars($dbgmsg, $lanVars));
/*
e107::getMessage()->addDebug(sprintf(
$dbgmsg,
$field_title,
$err_code,
$this->getErrorByCode($err_code)
));
*/
}
e107::getMessage()->addStack($msg, $this->_message_stack, E_MESSAGE_ERROR);
return $this;
}
/**
* Get validate message array
*
* @param boolean $clear
* @return array
*/
function getValidateMessages($clear = true)
{
return e107::getMessage()->getAll($this->_message_stack, true, $clear);
}
/**
* Render validate messages
*
* @param boolean $session merge with session messages
* @param boolean $clear
* @return string
*/
function renderValidateMessages($session = false, $clear = true)
{
return e107::getMessage()->render($this->_message_stack, $session, $clear);
}
/**
* @param boolean $session clear session messages as well, default true
* @return e_validator
*/
function clearValidateMessages($session = true)
{
e107::getMessage()->reset(false, $this->_message_stack, $session);
return $this;
}
/**
* Add validate error code for a field
*
* @param string $name
* @param integer $code
* @return e_validator
*/
function addValidateResult($name, $code)
{
$this->_validation_results[$name] = $code;
return $this;
}
/**
* Get validate result array
*
* @param boolean $clear
* @return array
*/
function getValidateResults($clear = true)
{
return $this->_validation_results;
}
/**
* Get validate result for a field
*
* @param string $field
* @param mixed $default
* @return integer error code
*/
function getErrorCode($field, $default = 0)
{
return (isset($this->_validation_results[$field]) ? $this->_validation_results[$field] : $default);
}
/**
* Get error string by given error code
*
* @param string $error_code
* @return integer error code
*/
function getErrorByCode($error_code)
{
$lan = 'LAN_VALIDATE_' . $error_code;
return defset($lan, $lan);
}
/**
* @return e_validator
*/
function clearValidateResults()
{
$this->_validation_results = array();
return $this;
}
/**
* @return boolean
*/
function isValid()
{
return empty($this->_is_valid_data);
}
/**
* Set validation status
*
* @param boolean $status
* @return e_validator
*/
public function setIsValidData($status)
{
$this->_is_valid_data = (boolean) $status;
return $this;
}
/**
* Reset object validate result data
* @return e_validator
*/
function reset()
{
$this->setIsValidData(true);
$this->_valid_data = array();
$this->clearValidateResults()
->clearValidateMessages();
return $this;
}
}
/*
The validator functions use an array of parameters for each variable to be validated.
The index of the parameter array is the destination field name.
Possible processing options:
'srcname' - specifies the array index of the source data, where its different to the destination index
'dbClean' - method for preparing the value to write to the DB (done as final step before returning). Options are:
- 'toDB' - passes final value through $tp->toDB()
- 'intval' - converts to an integer
- 'image' - checks image for size
- 'avatar' - checks an image in the avatars directory
'stripTags' - strips HTML tags from the value (not an error if there are some)
'minLength' - minimum length (in utf-8 characters) for the string
'maxLength' - minimum length (in utf-8 characters) for the string
'minVal' - lowest allowed value for numerics
'maxVal' - highest allowed value for numerics
'longTrim' - if set, and the string exceeds maxLength, its trimmed
'enablePref' - value is processed only if the named $pref evaluates to true; otherwise any input is discarded without error
'dataType' - selects special processing methods:
1 - array of numerics (e.g. class membership)
In general, only define an option if its to be used
*/
/* [ Berckoff ]
* Added "public static " to each method as the parser generates errors (and methods are called statically everywhere)
*/
/**
*
*/
class validatorClass
{
// Passed an array of 'source' fields and an array of definitions to validate. The definition may include the name of a validation function.
// Returns three arrays - one of validated results, one of failed fields and one of errors corresponding to the failed fields
// Normally processes only those source fields it finds (and for which it has a definition). If $addDefaults is true, sets defaults for those that have
// ...one and aren't otherwise defined.
/**
* @param $sourceFields
* @param $definitions
* @param $addDefaults
* @return array|array[]
*/
public static function validateFields(&$sourceFields, &$definitions, $addDefaults = false)
{
$tp = e107::getParser();
$pref = e107::getPref();
$ret = array('data' => array(), 'failed' => array(), 'errors' => array());
foreach ($definitions as $dest => $defs)
{
$errNum = 0; // Start with no error
if (!is_array($defs)) //default rule - dbClean -> toDB
{
$defs = array('dbClean', ($defs ? $defs : 'toDB'));
}
$src = varset($defs['srcName'], $dest); // Set source field name
if (!isset($sourceFields[$src]))
{
if ($addDefaults)
{
if (isset($defs['default']))
{
$ret['data'] = $defs['default']; // Set default value if one is specified
} //...otherwise don't add the value at all
}
else
{
if (!vartrue($defs['fieldOptional']))
{
$ret['errors'][$dest] = ERR_MISSING_VALUE; // No source value
}
}
}
else
{ // Got a field we want, and some data to validate here
$value = $sourceFields[$src];
if (!$errNum && isset($defs['enablePref']))
{ // Only process this field if a specified pref enables it
if (!vartrue($pref[$defs['enablePref']]))
{
continue; // Just loop to the next field - ignore this one.
}
}
if (!$errNum && isset($defs['stripTags']))
{
$newValue = trim(strip_tags($value));
if ($newValue <> $value)
{
$errNum = ERR_INVALID_CHARS;
}
$value = $newValue;
}
if (!$errNum && isset($defs['stripChars']))
{
$newValue = trim(preg_replace($defs['stripChars'], "", $value));
if ($newValue <> $value)
{
//echo "Invalid: {$newValue} :: {$value}<br />";
$errNum = ERR_INVALID_CHARS;
}
$value = $newValue;
}
if (!$errNum && isset($defs['minLength']) && ($tp->ustrlen($value) < $defs['minLength']))
{
if ($value == '')
{
if (!vartrue($defs['fieldOptional']))
{
$errNum = ERR_MISSING_VALUE;
}
}
else
{
$errNum = ERR_TOO_SHORT;
}
}
if (!$errNum && isset($defs['maxLength']) && $tp->ustrlen($value) > $defs['maxLength'])
{
if (vartrue($defs['longtrim']))
{
$value = substr($value, 0, $defs['maxLength']);
}
else
{
$errNum = ERR_TOO_LONG;
}
}
if (!$errNum && isset($defs['minVal']) && ($value < $defs['minVal']))
{
$errNum = ERR_TOO_LOW;
}
if (!$errNum && isset($defs['maxVal']) && ($value < $defs['maxVal']))
{
$errNum = ERR_TOO_HIGH;
}
if (!$errNum && isset($defs['fixedBlock']))
{
$newValue = $tp->ustrtolower($value);
$temp = explode(',', $defs['fixedBlock']);
foreach ($temp as $t)
{
if ($newValue == $tp->ustrtolower($t))
{
$errNum = ERR_INVALID_WORD;
break;
}
}
}
if (!$errNum && isset($defs['dataType']))
{
switch ($defs['dataType'])
{
case 1 : // Assumes we've passed an array variable to be turned into a comma-separated list of integers
if (is_array($value))
{
$temp = array();
foreach ($value as $v)
{
$v = trim($v);
if (is_numeric($v))
{
$temp[] = (int) $v;
}
}
$value = implode(',', array_unique($temp));
}
else
{
$errNum = ERR_ARRAY_EXPECTED;
}
break;
case 2 : // Assumes we're processing a dual password field - array name for second value is one more than for first
$src2 = substr($src, 0, -1) . (substr($src, -1, 1) + 1);
if (!isset($sourceFields[$src2]) || ($sourceFields[$src2] != $value))
{
$errNum = ERR_PASSWORDS_DIFFERENT;
}
break;
default :
$errNum = ERR_CODE_ERROR; // Pick up bad values
}
}
if (!$errNum)
{
if (isset($defs['dbClean']))
{
switch ($defs['dbClean'])
{
case 'toDB' :
$value = $tp->toDB($value);
break;
case 'intval' :
$value = (int) $value;
break;
case 'avatar' : // Special case of an image - may be found in the avatars directory
if (preg_match('#[0-9\._]#', $value))
{
if (strpos('-upload-', $value) === 0)
{
$img = e_AVATAR_UPLOAD . str_replace('-upload-', '', $value); // Its a user-uploaded image
}
elseif (strpos($value, '//') !== false)
{
$img = $value; // Its a remote image
}
else
{
$img = e_AVATAR_DEFAULT . $value; // Its a server-stored image
}
}
// Deliberately fall through into normal image processing
case 'image' : // File is an image name. $img may be set if we fall through from 'avatar' option - its the 'true' path to the image
if (!isset($img) && isset($defs['imagePath']))
{
$img = $defs['imagePath'] . $value;
}
$img = varset($img, $value);
//XXX There should be no size limits - as image sizes are handled by thumb.php
// if ($size = getimagesize($img))
// {
// echo "Image {$img} size: {$size[0]} x {$size[1]}<br />";
// if (isset($defs['maxWidth']) && $size[0] > $defs['maxWidth'])
// { // Image too wide
// $errNum = ERR_IMAGE_TOO_WIDE;
// }
// if (isset($defs['maxHeight']) && $size[1] > $defs['maxHeight'])
// { // Image too high
// $errNum = ERR_IMAGE_TOO_HIGH;
// }
// }
// else
// {
// echo "Image {$img} not found or cannot size - original value {$value}<br />";
// }
unset($img);
break;
default :
echo "Invalid dbClean method: {$defs['dbClean']}<br />"; // Debug message
}
}
$ret['data'][$dest] = $value; // Success!!
}
}
if ($errNum)
{ // error to report
$ret['errors'][$dest] = $errNum;
if ($defs['dataType'] == 2)
{
$ret['failed'][$dest] = str_repeat('*', strlen($sourceFields[$src])); // Save value with error - obfuscated
}
else
{
$ret['failed'][$dest] = $sourceFields[$src]; // Save value with error
}
}
}
return $ret;
}
/*
// Validate data against a DB table
// Inspects the passed array of user data (not necessarily containing all possible fields) and validates against the DB where appropriate.
// Just skips over fields for which we don't have a validation routine without an error
// The target array is as returned from validateFields(), so has 'data', 'failed' and 'errors' first-level sub-arrays
// All the 'vetting methods' begin 'vet', and don't overlap with validateFields(), so the same definition array may be used for both
// Similarly, error numbers don't overlap with validateFields()
// Typically checks for unacceptable duplicates, banned users etc
// Any errors are reflected by updating the passed array.
// Returns TRUE if all data validates, FALSE if any field fails to validate. Checks all fields which are present, regardless
// For some things we need to know the user_id of the data being validated, so may return an error if that isn't specified
Parameters:
'vetMethod' - see list below. To use more than one method, specify comma-separated
'vetParam' - possible parameter for some vet methods
Valid 'vetMethod' values (use comma separated list for multiple vetting):
0 - Null method
1 - Check for duplicates - field name in table must be the same as array index unless 'dbFieldName' specifies otherwise
2 - Check against the comma-separated wordlist in the $pref named in vetParam['signup_disallow_text']
3 - Check email address against remote server, only if option enabled
*/
/**
* @param $targetData
* @param $definitions
* @param $targetTable
* @param $userID
* @return bool
*/
public static function dbValidateArray(&$targetData, &$definitions, $targetTable, $userID = 0)
{
$pref = e107::getPref();
$u_sql = e107::getDb('u_sql');
$allOK = true;
$userID = (int) $userID; // Precautionary
$errMsg = '';
if (!$targetTable)
{
return false;
}
foreach ($targetData['data'] as $f => $v)
{
$errMsg = '';
if (isset($definitions[$f]))
{
$options = $definitions[$f]; // Validation options to use
if (!vartrue($options['fieldOptional']) || ($v != ''))
{
$toDo = explode(',', $options['vetMethod']);
foreach ($toDo as $vm)
{
switch ($vm)
{
case 0 : // Shouldn't get this - just do nothing if we do
break;
case 1 : // Check for duplicates.
if ($v == '')
{
$errMsg = ERR_MISSING_VALUE;
break;
}
$field = varset($options['dbFieldName'], $f);
// XXX: Different implementations due to missing API for preventing SQL injections
if ($u_sql instanceof e_db_mysql)
{
$v = $u_sql->escape($v);
$count = (int) $u_sql->count($targetTable, "(*)", "WHERE `{$f}`='$v' AND `user_id` != " . $userID);
}
else
{
$u_sql->select(
$targetTable,
"COUNT(*)",
"`{$f}`=:value AND `user_id` != :userID",
[
'value' => $v,
'userID' => $userID,
]
);
$row = $u_sql->fetch('num');
$count = (int) $row[0];
}
if ($count)
{
$errMsg = ERR_DUPLICATE;
}
// echo "Duplicate check: {$f} = {$v} Result: {$temp}<br />";
break;
case 2 : // Check against $pref
if (isset($options['vetParam']) && !empty($pref[$options['vetParam']]))
{
$tmp = explode(",", $pref[$options['vetParam']]);
foreach ($tmp as $disallow)
{
$disTrim = (string) trim($disallow);
if ('!' == substr($disTrim, -1) && $v == str_replace('!', '', $disallow))
{ // Exact match search (noticed with exclamation mark in the end of the word)
$errMsg = ERR_DISALLOWED_TEXT_EXACT_MATCH;
}
elseif (stripos($v, $disTrim) !== false)
{ // Wild card search
$errMsg = ERR_DISALLOWED_TEXT;
}
}
unset($tmp);
}
break;
case 3 : // Check email address against remote server
/* if (vartrue($pref['signup_remote_emailcheck']))
{
require_once(e_HANDLER."mail_validation_class.php");
list($adminuser,$adminhost) = split ("@", SITEADMINEMAIL);
$validator = new email_validation_class;
$validator->localuser= $adminuser;
$validator->localhost= $adminhost;
$validator->timeout=3;
// $validator->debug=1;
// $validator->html_debug=1;
if($validator->ValidateEmailBox(trim($v)) != 1)
{
$errMsg = ERR_INVALID_EMAIL;
}
}
*/
break;
default :
echo 'Invalid vetMethod: ' . $options['vetMethod'] . '<br />'; // Really a debug aid - should never get here
}
if ($errMsg)
{
break;
} // Just trap first error
}
// Add in other validation methods here
}
}
if ($errMsg)
{ // Update the error
$targetData['errors'][$f] = $errMsg;
$targetData['failed'][$f] = $v;
unset($targetData['data'][$f]); // Remove the valid entry
$allOK = false;
}
}
return $allOK;
}
// Given a comma-separated string of required fields, and an array of data, adds an error message for each field which doesn't already have an entry.
// Returns TRUE if no changes (which doesn't mean there are no errors - other routines may have found them). FALSE if new errors
/**
* @param $fieldList
* @param $target
* @return bool
*/
public static function checkMandatory($fieldList, &$target)
{
$fields = explode(',', $fieldList);
$allOK = true;
foreach ($fields as $f)
{
if (!isset($target['data'][$f]) && !isset($target['errors'][$f]))
{
$allOK = false;
$targetData['errors'][$f] = ERR_MISSING_VALUE;
}
}
return $allOK;
}
// Adds the _FIELD_TYPES array to the data, ready for saving in the DB.
// $fieldList is the standard definition array
/**
* @param $fieldList
* @param $target
* @param $auxList
* @return void
*/
public static function addFieldTypes($fieldList, &$target, $auxList = false)
{
$target['_FIELD_TYPES'] = array(); // We should always want to recreate the array, even if it exists
foreach ($target['data'] as $k => $v)
{
if (isset($fieldList[$k]) && isset($fieldList[$k]['fieldType']))
{
$target['_FIELD_TYPES'][$k] = $fieldList[$k]['fieldType'];
}
elseif (is_array($auxList) && isset($auxList[$k]))
{
$target['_FIELD_TYPES'][$k] = $auxList[$k];
}
}
}
// Given two arrays, returns an array of those elements in $input which are different from the corresponding element in $refs.
// If $addMissing == TRUE, includes any element in $input for which there isn't a corresponding element in $refs
/**
* @param $input
* @param $refs
* @param $addMissing
* @return array
*/
public static function findChanges(&$input, &$refs, $addMissing = false)
{
$ret = array();
foreach ($input as $k => $v)
{
if (array_key_exists($k, $refs))
{
if ($refs[$k] != $v)
{
$ret[$k] = $v;
}
}
else
{
if ($addMissing)
{
$ret[$k] = $v;
}
}
}
return $ret;
}
// Given a vetted array of variables, generates a list of errors using the specified format string.
// %n is the error number (as stored on the array)
// %t is the corresponding error message, made by concatenating $constPrefix and the error number to form a constant (e.g. $constPrefix = 'USER_ERROR_')
// %v calls up the entered value
// %f is the field name
// %x is the 'nice name' - possible if parameter list passed. Otherwise field name added
// $EOL is inserted after all messages except the last.
// If $EOL is an empty string, returns an array of messages.
/**
* @param $vars
* @param $constPrefix
* @param $format
* @param $EOL
* @param $niceNames
* @return array|string
*/
public static function makeErrorList($vars, $constPrefix, $format = '%n - %x %t: %v', $EOL = '<br />', $niceNames = null)
{
if (count($vars['errors']) == 0)
{
return '';
}
$eList = array();
$checkNice = ($niceNames != null) && is_array($niceNames);
foreach ($vars['errors'] as $f => $n)
{
$curLine = $format;
$curLine = str_replace('%n', $n, $curLine);
if (($n == ERR_GENERIC) && isset($vars['errortext'][$f]))
{
$curLine = str_replace('%t', $vars['errortext'][$f], $curLine); // Coder-defined specific error text
}
else
{
$curLine = str_replace('%t', constant($constPrefix . $n), $curLine); // Standard messages
}
if (empty($vars['failed'][$f]))
{
$vars['failed'][$f] = LAN_VALIDATE_191;
// print_a($vars['failed']);
}
$curLine = str_replace('%v', filter_var($vars['failed'][$f], FILTER_SANITIZE_SPECIAL_CHARS), $curLine);
$curLine = str_replace('%f', $f, $curLine);
if ($checkNice & isset($niceNames[$f]['niceName']))
{
$curLine = str_replace('%x', $niceNames[$f]['niceName'], $curLine);
}
else
{
$curLine = str_replace('%x', $f, $curLine); // Just use the field name
}
$eList[] = $curLine;
}
if ($EOL == '')
{
return $eList;
}
return implode($EOL, $eList);
}
}