bluetree-service/data

View on GitHub
src/Check/Validator.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

/**
 * contains all methods to validate data
 *
 * @package     BlueData
 * @subpackage  Data
 * @author      MichaƂ Adamiak    <chajr@bluetree.pl>
 * @copyright   bluetree-service
 */

declare(strict_types=1);

namespace BlueData\Check;

use function preg_match;
use function preg_replace;
use function substr;
use function strlen;
use function abs;

class Validator
{
    public const IBAN_CHARS = [
        '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4',
        '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9',
        'A' => '10', 'B' => '11', 'C' => '12', 'D' => '13', 'E' => '14',
        'F' => '15', 'G' => '16', 'H' => '17', 'I' => '18', 'J' => '19',
        'K' => '20', 'L' => '21', 'M' => '22', 'N' => '23', 'O' => '24',
        'P' => '25', 'Q' => '26', 'R' => '27', 'S' => '28', 'T' => '29',
        'U' => '30', 'V' => '31', 'W' => '32', 'X' => '33', 'Y' => '34',
        'Z' => '35'
    ];

    /**
     * array of regular expressions used to validate
     * @var array
     */
    public static $regularExpressions = [
        'string' =>             '#^[\p{L} ]*$#u',
        'letters' =>            '#^[\p{L} _ ,.-]*$#u',
        'letters_extend' =>     '#^[\p{L}_ ,\\.;:-]*$#u',
        'fullchars' =>          '#^[\p{L}\\d_ ,\\.;:/!@\\#$%^&*()+=|\\\{}\\]\\[<>?`~\'"-]*$#u',
        'integer' =>            '#^[\\d]*$#',
        'multinum' =>           '#^[\\d /-]*$#',
        'num_chars' =>          '#^[\p{L}\\d\\.,_ -]*$#u',
        'num_char_extends' =>   '#^[\p{L}\\d_ ,\\.;:-]*$#u',
        'numeric' =>            '#^(-)?[\\d]*$#',
        'float' =>              '#^(-)?[\\d]*((,|\\.){1}[\\d]*){1}$#',
        'rational' =>           '#^(-)?[\\d]*((,|\\.){1}[\\d]*)?$#',
        'mail' =>               '#^[\\w_\.-]*[\\w_]@[\\w_\.-]*\\.[\\w_-]{2,3}$#',
        'url' =>                '#^(http://)?[\\w_-]+\\.[\\w]{2,3}(/)?$#',
        'url_extend' =>         '#^((http|https|ftp|ftps)://)?[\\w_-]+\\.[\\w]{2,3}(/)?$#',
        'url_full' =>           '#^((http|https|ftp|ftps)://)?[\\w_-]+\\.[\\w]{2,3}([\\w_/-]*)?(\\?[\\w&%=+-]*)?$#',
        'price' =>              '#^[\\d]*((,|\\.)[\\d]{0,2})?$#',
        'postcode' =>           '#^[\\d]{2}-[\\d]{3}$#',
        'phone' =>              '#^((\\+)[\\d]{2})?( ?\\( ?[\\d]+ ?\\) ?)?[\\d -]*$#',
        'date2' =>              '#^[\\d]{2}-[\\d]{2}-[\\d]{4}$#',
        'date' =>               '#^[\\d]{4}-[\\d]{2}-[\\d]{2}$#',
        'month' =>              '#^[\\d]{4}-[\\d]{2}$#',
        'datetime' =>           '#^[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:[\\d]{2}$#',
        'jdate' =>              '#^[\\d]{2}/[\\d]{2}/[\\d]{4}$#',                        //time from jquery datepicker
        'jdatetime' =>          '#^[\\d]{2}/[\\d]{2}/[\\d]{4} [\\d]{2}:[\\d]{2}$#',      //time from jquery datepicker
        'time' =>               '#^[\\d]{2}:[\\d]{2}(:[\\d]{2})?$#',
        'hex_color' =>          '/^#[\\da-f]{6}$/i',
        'hex' =>                '/^#[\\da-f]+$/i',
        'hex2' =>               '#^0x[\\da-f]+$#i',
        'octal' =>              '#^0[0-7]+$#',
        'binary' =>             '#^b[0-1]+$#i',
    ];

    /**
     * standard validate method, use validation from $regularExpressions variable
     *
     * 'string' =>             '#^[\p{L} ]*$#u',
     * 'letters' =>            '#^[\p{L} _ ,.-]*$#u',
     * 'letters_extend' =>     '#^[\p{L}_ ,\\.;:-]*$#u',
     * 'fullchars' =>          '#^[\p{L}\\d_ ,\\.;:/!@\\#$%^&*()+=|\\\{}\\]\\[<>?`~\'"-]*$#u',
     * 'integer' =>            '#^[\\d]*$#',
     * 'multinum' =>           '#^[\\d /-]*$#',
     * 'num_chars' =>          '#^[\p{L}\\d\\.,_ -]*$#u',
     * 'num_char_extends' =>   '#^[\p{L}\\d_ ,\\.;:-]*$#u',
     * 'numeric' =>            '#^(-)?[\\d]*$#',
     * 'float' =>              '#^(-)?[\\d]*((,|\\.){1}[\\d]*){1}$#',
     * 'rational' =>           '#^(-)?[\\d]*((,|\\.){1}[\\d]*)?$#',
     * 'mail' =>               '#^[\\w_\.-]*[\\w_]@[\\w_\.-]*\\.[\\w_-]{2,3}$#',
     * 'url' =>                '#^(http://)?[\\w_-]+\\.[\\w]{2,3}(/)?$#',
     * 'url_extend' =>         '#^((http|https|ftp|ftps)://)?[\\w\\._-]+(/)?$#',
     * 'url_full' =>           '#^((http|https|ftp|ftps)://)?[\\w\\._/-]+(\\?[\\w&%=+-]*)?$#',
     * 'price' =>              '#^[\\d]*((,|\\.)[\\d]{0,2})?$#',
     * 'postcode' =>           '#^[\\d]{2}-[\\d]{3}$#',
     * 'phone' =>              '#^((\\+)[\\d]{2})?( ?\\( ?[\\d]+ ?\\) ?)?[\\d -]*$#',
     * 'date2' =>              '#^[\\d]{2}-[\\d]{2}-[\\d]{4}$#',
     * 'date' =>               '#^[\\d]{4}-[\\d]{2}-[\\d]{2}$#',
     * 'month' =>              '#^[\\d]{4}-[\\d]{2}$#',
     * 'datetime' =>           '#^[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:[\\d]{2}$#',
     * 'jdate' =>              '#^[\\d]{2}/[\\d]{2}/[\\d]{4}$#',                        //time from jquery datepicker
     * 'jdatetime' =>          '#^[\\d]{2}/[\\d]{2}/[\\d]{4} [\\d]{2}:[\\d]{2}$#',      //time from jquery datepicker
     * 'time' =>               '#^[\\d]{2}:[\\d]{2}(:[\\d]{2})?$#',
     * 'hex_color' =>          '/^#[\\da-f]{6}$/i',
     * 'hex' =>                '/^#[\\da-f]+$/i',
     * 'hex2' =>               '#^0x[\\da-f]+$#i',
     * 'octal' =>              '#^0[0-7]+$#',
     * 'binary' =>             '#^b[0-1]+$#i',
     *
     * @param string $value value to check
     * @param string $type validation type
     * @return bool|null if ok return true, of not return false, return null if validation type wasn't founded
     */
    public static function valid(string $value, string $type): ?bool
    {
        if (!isset(self::$regularExpressions[$type])) {
            return null;
        }

        return (bool)preg_match(self::$regularExpressions[$type], $value);
    }

    /**
     * check e-mail address format
     *
     * @param string $address
     * @return bool
     */
    public static function mail(string $address): bool
    {
        return (bool)preg_match(self::$regularExpressions['mail'], $address);
    }

    /**
     * check price format
     *
     * @param int|string $value
     * @return bool
     */
    public static function price($value): bool
    {
        return (bool)preg_match((string)self::$regularExpressions['price'], (string)$value);
    }

    /**
     * check post code format
     *
     * @param string $value
     * @return bool
     */
    public static function postcode(string $value): bool
    {
        return (bool)preg_match(self::$regularExpressions['postcode'], $value);
    }

    /**
     * check NIP number format
     *
     * @param string $value
     * @return bool
     */
    public static function nip(string $value): bool
    {
        if (!empty($value)) {
            $weights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
            $nip = preg_replace('#[\\s-]#', '', $value);

            return self::processNip($nip, $weights);
        }

        return false;
    }

    /**
     * @param string $nip
     * @param array $weights
     * @return bool
     */
    protected static function processNip(string $nip, array $weights): bool
    {
        if (\is_numeric($nip) && strlen($nip) === 10) {
            $sum = 0;

            foreach ($weights as $key => $val) {
                $sum += $nip[$key] * $val;
            }

            return (string)($sum % 11) === $nip[9];
        }

        return false;
    }

    /**
     * check string length, possibility to set range
     *
     * @param string $value
     * @param null|int $min minimal string length, if null don't check
     * @param null|int $max maximal string length, if null don't check
     * @return bool
     * @example stringLength('asdasdasd', null, 23)
     * @example stringLength('asdasdasd', 3, 23)
     * @example stringLength('asdasdasd', 3)
     */
    public static function stringLength(string $value, ?int $min = null, ?int $max = null): bool
    {
        $length = \mb_strlen($value);

        return self::range($length, $min, $max);
    }

    /**
     * check range on numeric values
     * allows to check decimal, hex, octal an binary values
     *
     * @param int|string|float $value
     * @param mixed $min minimal string length, if null don't check
     * @param mixed $max maximal string length, if null don't check
     * @example range(23423, null, 23)
     * @example range(23423, 3, 23)
     * @example range(23423, 3)
     * @example range(0xd3a743f2ab, 3)
     * @example range('#aaffff', 3)
     * @return bool
     */
    public static function range($value, $min = null, $max = null): bool
    {
        [$value, $min, $max] = self::getProperValues($value, $min, $max);

        return !(($min !== null && $min > $value) || ($max !== null && $max < $value));
    }

    /**
     * check that numeric value is less than 0
     * if less return true
     *
     * @param int $value
     * @return bool
     */
    public static function underZero(int $value): bool
    {
        return $value < 0;
    }

    /**
     * check PESEL number format
     * also set sex of person in $peselSex variable
     *
     * @param mixed $value
     * @return bool
     */
    public static function pesel($value): bool
    {
        $value = preg_replace('#[\\s-]#', '', (string)$value);

        if (!preg_match('#^\d{11}$#', (string)$value)) {
            return false;
        }

        $arrSteps = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
        $intSum = 0;

        foreach ($arrSteps as $key => $step) {
            $intSum += $step * $value[$key];
        }

        $int = 10 - $intSum % 10;
        $intControlNr = ($int === 10) ? 0 : $int;

        return (string)$intControlNr === $value[10];
    }

    /**
     * check REGON number format
     *
     * @param mixed $value
     * @return bool
     */
    public static function regon($value): bool
    {
        $value = preg_replace('#[\\s-]#', '', (string)$value);
        $length = strlen($value);

        if (!($length === 9 || $length === 14)) {
            return false;
        }

        $arrSteps = [8, 9, 2, 3, 4, 5, 6, 7];
        $intSum = 0;

        foreach ($arrSteps as $key => $step) {
            $intSum += $step * $value[$key];
        }

        $int = $intSum % 11;
        $intControlNr = ($int === 10) ? 0 : $int;

        return (string)$intControlNr === $value[8];
    }

    /**
     * check account number format in NRB standard
     *
     * @param mixed $value
     * @return bool
     */
    public static function nrb($value): bool
    {
        $iNRB = preg_replace('#[\\s\- ]#', '', (string)$value);

        if (strlen($iNRB) !== 26) {
            return false;
        }

        $iNRB .= '2521';
        $iNRB = substr($iNRB, 2) . substr($iNRB, 0, 2);
        $iNumSum = 0;
        $aNumWeight = [
            1, 10, 3, 30, 9, 90, 27, 76, 81, 34, 49, 5, 50, 15, 53,
            45, 62, 38, 89, 17, 73, 51, 25, 56, 75, 71, 31, 19, 93, 57
        ];

        foreach ($aNumWeight as $key => $num) {
            $iNumSum += $num * $iNRB[29 - $key];
        }

        return $iNumSum % 97 === 1;
    }

    /**
     * check account number format in IBAN standard
     *
     * @param mixed $value
     * @return bool
     */
    public static function iban($value): bool
    {
        $values = '';
        $mod = 0;
        $remove = [' ', '-', '_', '.', ',','/', '|'];
        $cleared = \str_replace($remove, '', $value);
        $temp = \strtoupper($cleared);

        $firstChar = $temp[0] <= '9';
        $secondChar = $temp[1] <= '9';

        if ($firstChar && $secondChar) {
            $temp = 'PL' . $temp;
        }

        $temp = substr($temp, 4) . substr($temp, 0, 4);

        foreach (\str_split($temp) as $val) {
            $values .= self::IBAN_CHARS[$val];
        }

        $sum = strlen($values);
        for ($i = 0; $i < $sum; $i += 6) {
            $separated = $mod . substr($values, $i, 6);
            $mod = (int)($separated) % 97;
        }

        return $mod === 1;
    }

    /**
     * check URL address
     *
     * @param string $url
     * @param int|null $type if 1 check protocols also, if 2 check with GET parameters
     * @return bool
     */
    public static function url(string $url, ?int $type = null): bool
    {
        switch ($type) {
            case 1:
                $regType = self::$regularExpressions['url_extend'];
                break;

            case 2:
                $regType = self::$regularExpressions['url_full'];
                break;

            default:
                $regType = self::$regularExpressions['url'];
                break;
        }

        return (bool)preg_match($regType, $url);
    }

    /**
     * check phone number format
     * eg +48 ( 052 ) 131 231-2312
     *
     * @param mixed $phone
     * @return bool
     */
    public static function phone($phone): bool
    {
        return (bool)preg_match(self::$regularExpressions['phone'], $phone);
    }

    /**
     * check step of value
     *
     * @param int|float $value
     * @param int|float $step step to check
     * @param int|float $default default value (0)
     * @return bool
     * @example step(15, 5, 5) true
     * @example step(12, 5) false
     */
    public static function step($value, $step, $default = 0): bool
    {
        if (
            !self::valid((string)$step, 'rational')
            || !self::valid((string)$default, 'rational')
            || !self::valid((string)$value, 'rational')
        ) {
            return false;
        }

        return !((abs($value) - abs($default)) % $step);
    }

    /**
     * @param mixed $value
     * @param mixed $min
     * @param mixed $max
     * @return array
     */
    protected static function getProperValues($value, $min, $max): array
    {
        if (self::isHex($min, $max)) {
            $value = hexdec(str_replace('#', '', $value));
            $min = hexdec(str_replace('#', '', $min));
            $max = hexdec(str_replace('#', '', $max));
        }

        if (self::isOctal($min, $max)) {
            $value = octdec($value);
            $min = octdec($min);
            $max = octdec($max);
        }

        if (self::isBin($min, $max)) {
            $value = bindec($value);
            $min = bindec($min);
            $max = bindec($max);
        }

        return [$value, $min, $max];
    }

    /**
     * @param mixed $min
     * @param mixed $max
     * @return bool
     */
    protected static function isHex($min, $max): bool
    {
        return (self::validKey('hex', $min) || self::validKey('hex2', $min))
            && (self::validKey('hex', $max) || self::validKey('hex2', $max));
    }

    /**
     * @param mixed $min
     * @param mixed $max
     * @return bool
     */
    protected static function isOctal($min, $max): bool
    {
        return self::validKey('octal', $min) && self::validKey('octal', $max);
    }

    /**
     * @param mixed $min
     * @param mixed $max
     * @return bool
     */
    protected static function isBin($min, $max): bool
    {
        return self::validKey('binary', $min) && self::validKey('binary', $max);
    }

    /**
     * @param string $key
     * @param int|float|double $value
     * @return int
     */
    protected static function validKey($key, $value): int
    {
        return preg_match(self::$regularExpressions[$key], (string)$value);
    }
}