<?php
namespace Lia\Utility;
/**
* Simple class for validating user inputs
*/
class InputValidator {
protected array $rules = [];
/**
*
* @param $rules_list either string separated by bars: `rule1|rule2|rule3` or `array<string rule_name, value rule_paramaters>`
* @param $input_value the value being validated
* @param $input_name the name of the input being validated (*built-in rules don't use this*)
* @param $ref a variable whose value will be set to `$input_value` and may be changed by validation functions
*/
public function validate(array|string $rules_list, mixed $input_value, ?string $input_name = null, mixed &$ref = null): bool {
if (is_string($rules_list)){
$rules_array = array_fill_keys(explode("|", $rules_list), null);
//var_dump($rules_array);
foreach ($rules_array as $key=>$nullvalue){
$parts = explode(":", $key);
if (count($parts)==1)continue;
unset($rules_array[$key]);
$rules_array[array_shift($parts)] = implode(':',$parts);
}
} else {
$rules_array = $rules_list;
}
foreach ($rules_array as $rule_name => $rule_options){
$is_valid = false;
if (is_int($rule_name)){
$rule_name = $rule_options;
$rule_options = null;
}
if (isset($this->rules[$rule_name])){
$callable = $this->rules[$rule_name];
$is_valid = $callable($input_value, $input_name, $rule_name, $rule_options);
} else if (method_exists($this, $mname="is_".$rule_name)){
$is_valid = $this->$mname($input_value, $rule_options, $input_name, $rule_name);
} else {
throw new \Lia\Exception(\Lia\Exception::VALIDATION_RULE_DOESNT_EXIST, $rule_name);
}
if ($is_valid !== true)return false;
}
$ref = $input_value;
return true;
}
/**
* Add a rule
*
* @param string $rule_name
* @param $callable `function(mixed $value_to_validate, string $name_of_input, string $rule_name, ?mixed $rule_options): bool;` Your function returns true if the input is valid
*
*/
public function add_rule(string $rule_name, callable $callable, bool $overwrite_existing = false){
if ($this->has_rule($rule_name)){
if ($overwrite_existing === false){
throw new \Lia\Exception(\Lia\Exception::VALIDATION_RULE_EXISTS, $rule_name);
}
}
$this->rules[$rule_name] = $callable;
}
public function has_rule(string $rule_name): bool {
if (isset($this->rules[$rule_name])
|| method_exists($this, 'is_'.$rule_name)){
return true;
}
return false;
}
public function get_failure_message(string $rule_name): string|bool {
if (!method_exists($this, 'is_'.$rule_name)){
throw new \Lia\Exception(\Lia\Exception::VALIDATION_METHOD_NOT_EXISTS, $rule_name);
}
$rm = new \ReflectionMethod($this, 'is_'.$rule_name);
$doc = $rm->getDocComment();
$pos = strpos($doc,"@failmsg");
if ($pos === false) return false;
$msg = substr($doc, $pos + strlen("@failmsg"));
$msg = substr($msg, 0, strpos($msg, "\n"));
return trim($msg);
}
/** $value must be a string per `is_string($value)` */
public function is_string(mixed $value){
if (is_string($value))return true;
return false;
}
/** $value must be a number per `is_numeric($value)` */
public function is_number(mixed $value){
return is_numeric($value);
}
/** $value must be an int. Typecasts to int if value is a string containing int. */
public function is_int(mixed &$value){
if (is_int($value))return true;
else if (is_string($value)){
$intval = (int)$value;
$stringval = (string)$intval;
if ($stringval === $value){
$value = $intval;
return true;
}
}
return false;
}
/** $value must be alphanumeric, per `ctype_alnum($value)` */
public function is_alnum(mixed $value){
return ctype_alnum($value);
}
/** $value must be in the $target_values list. $target_values are comma separated */
public function is_oneof(mixed $value, string $target_values){
$options = explode(",", $target_values);
if (in_array($value, $options))return true;
return false;
}
/** $value must be a string longer than $length */
public function is_maxlen(mixed $value, string $length){
$length = (int)$length;
if (strlen($value) <= $length)return true;
return false;
}
/** $value must be >= $intval */
public function is_minval(mixed $value, string $intval){
$length = (int)$intval;
if ($value >= $intval)return true;
return false;
}
/** (UNTESTED) $value must not contain html or htmlspecialchars. If you pass 'strip', then `strip_tags()` & `htmlspecialchars()` will modify value.
*/
public function is_nohtml(mixed &$value, string $should_strip){
if ($should_strip == 'strip'){
$value = strip_tags($value);
$value = htmlspecialchars($value);
return true;
}
$stripped = strip_tags($value);
$stripped = htmlspecialchars($value);
if ($stripped === $value)return true;
return false;
}
/** (UNTESTED) $value must only contain safe html tags. If you pass 'strip', then value is set to `strip_tags($value, ALLOWED_TAGS)`. (*no script or style. See function definition for full list*)
*
* @failmsg Some disallowed HTML tags were present. h1-h6, p, div, span, summary/details, small, strike, and other basic formatting tags are available.
*/
public function is_safehtml(mixed &$value, string $should_strip){
$safe_tags = [
'div','span','p','br','ul','ol','li','pre',
'h1','h2','h3','h4','h5','h6',
'address','blockquote','bold','strong',
'i','center','code','hr','a','details',
'summary','small','strike','sub','sup','u',
];
$stripped = strip_tags($value, $safe_tags);
if ($should_strip == 'strip'){
$value = $stripped;
return true;
}
if ($value == $stripped)return true;
return false;
}
}