* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* Validator.inc.php
*
* The Validator class provides a methods for validating input against different criteria.
* All functions return true if the input passes the test.
*
* @author Quinn Comendant
* @version 1.0
*/
class Validator
{
// Known credit card types.
const CC_TYPE_VISA = 1;
const CC_TYPE_MASTERCARD = 2;
const CC_TYPE_AMEX = 3;
const CC_TYPE_DISCOVER = 4;
const CC_TYPE_DINERS = 5;
const CC_TYPE_JCB = 6;
// Validator::validateEmail() return types.
const EMAIL_SUCCESS = 0;
const EMAIL_REGEX_FAIL = 1;
const EMAIL_LENGTH_FAIL = 2;
const EMAIL_MX_FAIL = 3;
// Validator::validatePhone() return types.
const PHONE_SUCCESS = 0;
const PHONE_REGEX_FAIL = 1;
const PHONE_LENGTH_FAIL = 2;
/**
* Check if a value is not empty (the opposite of isEmpty()).
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if form is not empty, false otherwise.
*/
static public function notEmpty($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (is_array($val)) {
if (!empty($val)) {
return true;
} else {
return false;
}
} else {
if ('' != trim((string)$val)) {
return true;
} else {
return false;
}
}
}
/*
* We were using the isEmpty method *wrong* for years and should have been using notEmpty because it is more grammatically correct.
* Because the only use is to ensure a value is not empty, we're simply going to alias this method to notEmpty().
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if form is empty, false otherwise.
*/
static public function isEmpty($val, $type=LOG_DEBUG, $file=null, $line=null)
{
return !self::notEmpty($val, $type, $file, $line);
}
/**
* Check whether input is a string.
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if form is a string, false otherwise.
*/
static public function isString($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ('' === trim((string)$val) || is_string($val)) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input is a number. Allows negative numbers.
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
*/
static public function isNumber($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ('' === trim((string)$val) || is_numeric($val)) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* addError if input is NOT an integer. Don't just use is_int() because the
* data coming from the user is *really* a string.
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if value is an integer
*/
static public function isInteger($val, $negative_ok=false, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
$pattern = $negative_ok ? '/^-?[[:digit:]]+$/' : '/^[[:digit:]]+$/';
if ('' === trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val))) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input is a float. Don't just use is_float() because the
* data coming from the user is *really* a string. Integers will also
* pass this test.
*
* @param string $val The input data to validate.
* @param bool $negative_ok If the value can be unsigned.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if value is a float
*/
static public function isFloat($val, $negative_ok=false, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
$pattern = $negative_ok ? '/^-?[[:digit:]]*(?:\.?[[:digit:]]+)$/' : '/^[[:digit:]]*(?:\.?[[:digit:]]+)$/';
if ('' === trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val))) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input is a Decimal or Fixed type. Check values to be stored in mysql decimal, numeric, num, or fixed types.
* Note: some integers and floats will also pass this test.
* https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
*
* @param string $val The input data to validate.
* @param bool $negative_ok If the value can be unsigned.
* @param int $max Total max number of digits (for mysql max is 65).
* @param int $dec Total max number of digits after the decimal place (for mysql max is 30).
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if value is a float
*/
static public function isDecimal($val, $max=10, $dec=2, $negative_ok=false, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ('' === trim((string)$val)) {
return true;
}
if (!$negative_ok && is_numeric($val) && $val < 0) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
// Get the length of the part after any decimal point, or zero.
$num_parts = explode('.', $val);
$dec_count = sizeof($num_parts) <= 1 ? 0 : mb_strlen(end($num_parts));
// Must be numeric, total digits <= $max, dec digits <= $dec.
if (is_numeric($val) && mb_strlen(str_replace(['-', '.'], '', $val)) <= $max && $dec_count <= $dec) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input is an array.
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if value is a float
*/
static public function isArray($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ((is_string($val) && '' === trim((string)$val)) || is_array($val)) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input matches the specified perl regular expression
* pattern.
*
* @param string $val The input data to validate.
* @param int $regex PREG that the string must match
* @param bool $valid_on_match Set to true to be valid if match, or false to be valid if the match fails.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if value passes regex test
*/
static public function checkRegex($val, $regex, $valid_on_match=true, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ($valid_on_match ? preg_match($regex, $val) : !preg_match($regex, $val)) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Tests if the string length is between specified values. Whitespace excluded for min.
*
* @param string $val The input data to validate.
* @param int $min minimum length of string, inclusive
* @param int $max maximum length of string, inclusive
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if string length is within given boundaries
*/
static public function stringLength($val, $min, $max, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (mb_strlen((string)$val) >= $min && mb_strlen((string)$val) <= $max) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether input is within a valid numeric range.
*
* @param string $val The input data to validate.
* @param int $min minimum value of number, inclusive
* @param int $max maximum value of number, inclusive
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
*/
static public function numericRange($val, $min, $max, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if ('' === trim((string)$val) || (is_numeric($val) && $val >= $min && $val <= $max)) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Validates an email address based on the recommendations in RFC 3696.
* Is more loose than restrictive, to allow the many valid variants of
* email addresses while catching the most common mistakes.
* http://www.faqs.org/rfcs/rfc822.html
* http://www.faqs.org/rfcs/rfc2822.html
* http://www.faqs.org/rfcs/rfc3696.html
* http://www.faqs.org/rfcs/rfc1035.html
*
* @access public
* @param string $val The input data to validate..
* @param bool $strict Run strict tests (check if the domain exists and has an MX record assigned)
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return const One of the constant values: Validator::EMAIL_SUCCESS|Validator::EMAIL_REGEX_FAIL|Validator::EMAIL_LENGTH_FAIL|Validator::EMAIL_MX_FAIL
* @author Quinn Comendant
*/
static public function validateEmail($val, $strict=false, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
require_once 'codebase/lib/Email.inc.php';
$e = new Email();
// Test email address format.
if (!preg_match($e->getParam('regex'), $val, $e_parts)) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return self::EMAIL_REGEX_FAIL;
}
// We have a match! Here are the captured subpatterns, on which further tests are run.
// The part before the @.
$local = $e_parts[2];
// The part after the @.
// If domain is an IP [XXX.XXX.XXX.XXX] strip off the brackets.
$domain = $e_parts[3][0] == '[' ? mb_substr($e_parts[3], 1, -1) : $e_parts[3];
// Test length.
if (mb_strlen($local) > 64 || mb_strlen($domain) > 191) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return self::EMAIL_LENGTH_FAIL;
}
if ($strict && function_exists('getmxrr') && !getmxrr($domain, $hosts_notused)) {
// Strict tests: check if MX record exists.
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return self::EMAIL_MX_FAIL;
}
return self::EMAIL_SUCCESS;
}
/**
* Check whether input is a valid phone number. Notice: it is now set
* to allow characters like - or () or + so people can type in a phone
* number that looks like: +1 (530) 555-1212
*
* @param string $form_name the name of the incoming form variable
*
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool true if no errors found, false otherwise
*/
static public function validatePhone($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (!self::checkRegex($val, '/^[0-9 +().-]*$/', true, $type, $file, $line)) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return self::PHONE_REGEX_FAIL;
}
if (!self::stringLength($val, 0, 25, $type, $file, $line)) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return self::PHONE_LENGTH_FAIL;
}
return self::PHONE_SUCCESS;
}
/**
* Verifies that date can be processed by the strtotime function.
* Empty strings are considered valid. Other values are tested on their return value from strtotime(). Null values will fail.
*
* @param string $val The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
*/
static public function validateStrDate($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (is_string($val) && '' === trim($val)) {
// Don't be too bothered about empty strings.
return true;
}
if (!$val || false === strtotime($val)) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
} else {
return true;
}
}
/*
* Checks if value is a "zero" SQL DATE, DATETIME, or TIMESTAMP value (or simply empty).
*
* @access public
* @param string $val String to check.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if value is an empty date.
* @author Quinn Comendant
* @version 1.0
* @since 19 May 2015 09:57:27
*/
static public function isEmptyDate($val, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (empty($val) || '0000-00-00 00:00:00' == $val || '1000-01-01 00:00:00' == $val || '0000-00-00' == $val || '1000-01-01' == $val || '00:00:00' == $val) {
return true;
}
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
/**
* Verifies credit card number using the Luhn (mod 10) algorithm.
* http://en.wikipedia.org/wiki/Luhn_algorithm
*
* @param string $val The input data to validate.
* @param string $cc_num Card number to verify.
* @param string $cc_type Optional, card type to do specific checks.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
*/
static public function validateCCNumber($val, $cc_type=null, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
// Get rid of any non-digits
$cc_num = preg_replace('/[^\d]/' . $app->getParam('preg_u'), '', $val);
// Perform card-specific checks, if applicable
switch ($cc_type) {
case self::CC_TYPE_VISA :
$regex = '/^4\d{15}$|^4\d{12}$/';
break;
case self::CC_TYPE_MASTERCARD :
$regex = '/^5[1-5]\d{14}$/';
break;
case self::CC_TYPE_AMEX :
$regex = '/^3[47]\d{13}$/';
break;
case self::CC_TYPE_DISCOVER :
$regex = '/^6011\d{12}$/';
break;
case self::CC_TYPE_DINERS :
$regex = '/^30[0-5]\d{11}$|^3[68]\d{12}$/';
break;
case self::CC_TYPE_JCB :
$regex = '/^3\d{15}$|^2131|1800\d{11}$/';
break;
default :
$regex = '/\d{13,}/';
break;
}
if ('' != $regex && !preg_match($regex, $cc_num)) {
// Invalid format.
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
// The Luhn formula works right to left, so reverse the number.
$cc_num = strrev($cc_num);
$luhn_total = 0;
$num = mb_strlen($cc_num);
for ($i=0; $i<$num; $i++) {
// Get each digit.
$digit = mb_substr($cc_num, $i, 1);
// If it's an odd digit, double it.
if ($i / 2 != floor($i / 2)) {
$digit *= 2;
}
// If the result is two digits, add them.
if (mb_strlen($digit) == 2) {
$digit = mb_substr($digit, 0, 1) + mb_substr($digit, 1, 1);
}
// Add the current digit to the $luhn_total.
$luhn_total += $digit;
}
// If the Total is evenly divisible by 10, it's cool!
if ($luhn_total % 10 == 0) {
return true;
} else {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
}
}
/**
* Check whether a file was selected for uploading. If file is missing, it's an error.
*
* @param string $form_name The input data to validate.
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
*/
static public function fileUploaded($form_name, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (!isset($_FILES[$form_name]['name']) || empty($_FILES[$form_name]['name'])) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, 'no _FILES'), $type, $file, $line);
return false;
}
if (is_array($_FILES[$form_name]['name'])) {
foreach($_FILES[$form_name]['name'] as $f) {
if ('' === $f) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($_FILES)), $type, $file, $line);
return false;
}
}
} else {
if ('' === $_FILES[$form_name]['name']) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($_FILES)), $type, $file, $line);
return false;
}
}
return true;
}
/*
* Check if the amount of content sent by the browser exceeds the upload_max_filesize value configured in php.ini.
* http://stackoverflow.com/a/24202363
*
* @access public
* @param const $type A LOG_* constant (see App->logMsg())
* @param string $file Filename to log (usually __FILE__)
* @param int $line Line number to log (usually __LINE__)
* @return bool True if no errors found, false otherwise.
* @author Quinn Comendant
* @version 1.0
* @since 20 Aug 2014 14:44:23
*/
static public function fileUploadSize($type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
$upload_max_filesize = phpIniGetBytes('upload_max_filesize');
if (isset($_SERVER['CONTENT_LENGTH']) && 0 != $upload_max_filesize && $_SERVER['CONTENT_LENGTH'] > $upload_max_filesize) {
$app->logMsg(sprintf('%s (line %s) failed: filesize %s exceeds limit of %s', __METHOD__, __LINE__, $_SERVER['CONTENT_LENGTH'], $upload_max_filesize), $type, $file, $line);
return false;
}
return true;
}
/*
*
*
* @access public
* @param
* @return
* @author Quinn Comendant
* @since 09 Feb 2024 21:03:57
*/
public static function IPAddress($val, $flags=null, $type=LOG_DEBUG, $file=null, $line=null)
{
$app =& App::getInstance();
if (is_string($val) && '' === trim($val)) {
// Don't be too bothered about empty strings.
return true;
}
if (!filter_var($val, FILTER_VALIDATE_IP, $flags)) {
$app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
return false;
} else {
return true;
}
}
} // THE END