You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
756 lines
18 KiB
756 lines
18 KiB
<?php
|
|
/**
|
|
* CodeIgniter
|
|
*
|
|
* An open source application development framework for PHP
|
|
*
|
|
* This content is released under the MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2014-2019 British Columbia Institute of Technology
|
|
* Copyright (c) 2019 CodeIgniter Foundation
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* @package CodeIgniter
|
|
* @author CodeIgniter Dev Team
|
|
* @copyright 2019 CodeIgniter Foundation
|
|
* @license https://opensource.org/licenses/MIT MIT License
|
|
* @link https://codeigniter.com
|
|
* @since Version 4.0.0
|
|
* @filesource
|
|
*/
|
|
|
|
namespace CodeIgniter\Validation;
|
|
|
|
use CodeIgniter\HTTP\RequestInterface;
|
|
use CodeIgniter\Validation\Exceptions\ValidationException;
|
|
use CodeIgniter\View\RendererInterface;
|
|
|
|
/**
|
|
* Validator
|
|
*/
|
|
class Validation implements ValidationInterface
|
|
{
|
|
|
|
/**
|
|
* Files to load with validation functions.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $ruleSetFiles;
|
|
|
|
/**
|
|
* The loaded instances of our validation files.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $ruleSetInstances = [];
|
|
|
|
/**
|
|
* Stores the actual rules that should
|
|
* be ran against $data.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $rules = [];
|
|
|
|
/**
|
|
* The data that should be validated,
|
|
* where 'key' is the alias, with value.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $data = [];
|
|
|
|
/**
|
|
* Any generated errors during validation.
|
|
* 'key' is the alias, 'value' is the message.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $errors = [];
|
|
|
|
/**
|
|
* Stores custom error message to use
|
|
* during validation. Where 'key' is the alias.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $customErrors = [];
|
|
|
|
/**
|
|
* Our configuration.
|
|
*
|
|
* @var \Config\Validation
|
|
*/
|
|
protected $config;
|
|
|
|
/**
|
|
* The view renderer used to render validation messages.
|
|
*
|
|
* @var RendererInterface
|
|
*/
|
|
protected $view;
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Validation constructor.
|
|
*
|
|
* @param \Config\Validation $config
|
|
* @param RendererInterface $view
|
|
*/
|
|
public function __construct($config, RendererInterface $view)
|
|
{
|
|
$this->ruleSetFiles = $config->ruleSets;
|
|
|
|
$this->config = $config;
|
|
|
|
$this->view = $view;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Runs the validation process, returning true/false determining whether
|
|
* validation was successful or not.
|
|
*
|
|
* @param array $data The array of data to validate.
|
|
* @param string $group The pre-defined group of rules to apply.
|
|
* @param string $db_group The database group to use.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function run(array $data = null, string $group = null, string $db_group = null): bool
|
|
{
|
|
$data = $data ?? $this->data;
|
|
|
|
// i.e. is_unique
|
|
$data['DBGroup'] = $db_group;
|
|
|
|
$this->loadRuleSets();
|
|
|
|
$this->loadRuleGroup($group);
|
|
|
|
// If no rules exist, we return false to ensure
|
|
// the developer didn't forget to set the rules.
|
|
if (empty($this->rules))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Need this for searching arrays in validation.
|
|
helper('array');
|
|
|
|
// Run through each rule. If we have any field set for
|
|
// this rule, then we need to run them through!
|
|
foreach ($this->rules as $rField => $rSetup)
|
|
{
|
|
// Blast $rSetup apart, unless it's already an array.
|
|
$rules = $rSetup['rules'] ?? $rSetup;
|
|
|
|
if (is_string($rules))
|
|
{
|
|
$rules = $this->splitRules($rules);
|
|
}
|
|
|
|
$value = dot_array_search($rField, $data);
|
|
|
|
$this->processRules($rField, $rSetup['label'] ?? $rField, $value ?? null, $rules, $data);
|
|
}
|
|
|
|
return ! empty($this->getErrors()) ? false : true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Check; runs the validation process, returning true or false
|
|
* determining whether validation was successful or not.
|
|
*
|
|
* @param mixed $value Value to validation.
|
|
* @param string $rule Rule.
|
|
* @param string[] $errors Errors.
|
|
*
|
|
* @return boolean True if valid, else false.
|
|
*/
|
|
public function check($value, string $rule, array $errors = []): bool
|
|
{
|
|
$this->reset();
|
|
$this->setRule('check', null, $rule, $errors);
|
|
|
|
return $this->run([
|
|
'check' => $value,
|
|
]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Runs all of $rules against $field, until one fails, or
|
|
* all of them have been processed. If one fails, it adds
|
|
* the error to $this->errors and moves on to the next,
|
|
* so that we can collect all of the first errors.
|
|
*
|
|
* @param string $field
|
|
* @param string|null $label
|
|
* @param string $value
|
|
* @param array|null $rules
|
|
* @param array $data // All of the fields to check.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
protected function processRules(string $field, string $label = null, $value, $rules = null, array $data): bool
|
|
{
|
|
// If the if_exist rule is defined...
|
|
if (in_array('if_exist', $rules))
|
|
{
|
|
// and the current field does not exists in the input data
|
|
// we can return true. Ignoring all other rules to this field.
|
|
if (! array_key_exists($field, $data))
|
|
{
|
|
return true;
|
|
}
|
|
// Otherwise remove the if_exist rule and continue the process
|
|
$rules = array_diff($rules, ['if_exist']);
|
|
}
|
|
|
|
if (in_array('permit_empty', $rules))
|
|
{
|
|
if (! in_array('required', $rules) && (is_array($value) ? empty($value) : (trim($value) === '')))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
$rules = array_diff($rules, ['permit_empty']);
|
|
}
|
|
|
|
foreach ($rules as $rule)
|
|
{
|
|
$callable = is_callable($rule);
|
|
$passed = false;
|
|
|
|
// Rules can contain parameters: max_length[5]
|
|
$param = false;
|
|
if (! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
|
|
{
|
|
$rule = $match[1];
|
|
$param = $match[2];
|
|
}
|
|
|
|
// Placeholder for custom errors from the rules.
|
|
$error = null;
|
|
|
|
// If it's a callable, call and and get out of here.
|
|
if ($callable)
|
|
{
|
|
$passed = $param === false ? $rule($value) : $rule($value, $param, $data);
|
|
}
|
|
else
|
|
{
|
|
$found = false;
|
|
|
|
// Check in our rulesets
|
|
foreach ($this->ruleSetInstances as $set)
|
|
{
|
|
if (! method_exists($set, $rule))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$found = true;
|
|
|
|
$passed = $param === false ? $set->$rule($value, $error) : $set->$rule($value, $param, $data, $error);
|
|
break;
|
|
}
|
|
|
|
// If the rule wasn't found anywhere, we
|
|
// should throw an exception so the developer can find it.
|
|
if (! $found)
|
|
{
|
|
throw ValidationException::forRuleNotFound($rule);
|
|
}
|
|
}
|
|
|
|
// Set the error message if we didn't survive.
|
|
if ($passed === false)
|
|
{
|
|
$this->errors[$field] = is_null($error) ? $this->getErrorMessage($rule, $field, $label, $param) : $error;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Takes a Request object and grabs the input data to use from its
|
|
* array values.
|
|
*
|
|
* @param \CodeIgniter\HTTP\RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
|
|
*
|
|
* @return \CodeIgniter\Validation\ValidationInterface
|
|
*/
|
|
public function withRequest(RequestInterface $request): ValidationInterface
|
|
{
|
|
if (in_array($request->getMethod(), ['put', 'patch', 'delete']))
|
|
{
|
|
$this->data = $request->getRawInput();
|
|
}
|
|
else
|
|
{
|
|
$this->data = $request->getVar() ?? [];
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//--------------------------------------------------------------------
|
|
// Rules
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Sets an individual rule and custom error messages for a single field.
|
|
*
|
|
* The custom error message should be just the messages that apply to
|
|
* this field, like so:
|
|
*
|
|
* [
|
|
* 'rule' => 'message',
|
|
* 'rule' => 'message'
|
|
* ]
|
|
*
|
|
* @param string $field
|
|
* @param string|null $label
|
|
* @param string $rules
|
|
* @param array $errors
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setRule(string $field, string $label = null, string $rules, array $errors = [])
|
|
{
|
|
$this->rules[$field] = [
|
|
'label' => $label,
|
|
'rules' => $rules,
|
|
];
|
|
$this->customErrors = array_merge($this->customErrors, [
|
|
$field => $errors,
|
|
]);
|
|
|
|
return $this;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Stores the rules that should be used to validate the items.
|
|
* Rules should be an array formatted like:
|
|
*
|
|
* [
|
|
* 'field' => 'rule1|rule2'
|
|
* ]
|
|
*
|
|
* The $errors array should be formatted like:
|
|
* [
|
|
* 'field' => [
|
|
* 'rule' => 'message',
|
|
* 'rule' => 'message
|
|
* ],
|
|
* ]
|
|
*
|
|
* @param array $rules
|
|
* @param array $errors // An array of custom error messages
|
|
*
|
|
* @return \CodeIgniter\Validation\ValidationInterface
|
|
*/
|
|
public function setRules(array $rules, array $errors = []): ValidationInterface
|
|
{
|
|
$this->customErrors = $errors;
|
|
|
|
foreach ($rules as $field => &$rule)
|
|
{
|
|
if (is_array($rule))
|
|
{
|
|
if (array_key_exists('errors', $rule))
|
|
{
|
|
$this->customErrors[$field] = $rule['errors'];
|
|
unset($rule['errors']);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->rules = $rules;
|
|
|
|
return $this;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns all of the rules currently defined.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getRules(): array
|
|
{
|
|
return $this->rules;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Checks to see if the rule for key $field has been set or not.
|
|
*
|
|
* @param string $field
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasRule(string $field): bool
|
|
{
|
|
return array_key_exists($field, $this->rules);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Get rule group.
|
|
*
|
|
* @param string $group Group.
|
|
*
|
|
* @return string[] Rule group.
|
|
*
|
|
* @throws \InvalidArgumentException If group not found.
|
|
*/
|
|
public function getRuleGroup(string $group): array
|
|
{
|
|
if (! isset($this->config->$group))
|
|
{
|
|
throw ValidationException::forGroupNotFound($group);
|
|
}
|
|
|
|
if (! is_array($this->config->$group))
|
|
{
|
|
throw ValidationException::forGroupNotArray($group);
|
|
}
|
|
|
|
return $this->config->$group;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Set rule group.
|
|
*
|
|
* @param string $group Group.
|
|
*
|
|
* @throws \InvalidArgumentException If group not found.
|
|
*/
|
|
public function setRuleGroup(string $group)
|
|
{
|
|
$rules = $this->getRuleGroup($group);
|
|
$this->rules = $rules;
|
|
|
|
$errorName = $group . '_errors';
|
|
if (isset($this->config->$errorName))
|
|
{
|
|
$this->customErrors = $this->config->$errorName;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the rendered HTML of the errors as defined in $template.
|
|
*
|
|
* @param string $template
|
|
*
|
|
* @return string
|
|
*/
|
|
public function listErrors(string $template = 'list'): string
|
|
{
|
|
if (! array_key_exists($template, $this->config->templates))
|
|
{
|
|
throw ValidationException::forInvalidTemplate($template);
|
|
}
|
|
|
|
return $this->view->setVar('errors', $this->getErrors())
|
|
->render($this->config->templates[$template]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Displays a single error in formatted HTML as defined in the $template view.
|
|
*
|
|
* @param string $field
|
|
* @param string $template
|
|
*
|
|
* @return string
|
|
*/
|
|
public function showError(string $field, string $template = 'single'): string
|
|
{
|
|
if (! array_key_exists($field, $this->getErrors()))
|
|
{
|
|
return '';
|
|
}
|
|
|
|
if (! array_key_exists($template, $this->config->templates))
|
|
{
|
|
throw ValidationException::forInvalidTemplate($template);
|
|
}
|
|
|
|
return $this->view->setVar('error', $this->getError($field))
|
|
->render($this->config->templates[$template]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Loads all of the rulesets classes that have been defined in the
|
|
* Config\Validation and stores them locally so we can use them.
|
|
*/
|
|
protected function loadRuleSets()
|
|
{
|
|
if (empty($this->ruleSetFiles))
|
|
{
|
|
throw ValidationException::forNoRuleSets();
|
|
}
|
|
|
|
foreach ($this->ruleSetFiles as $file)
|
|
{
|
|
$this->ruleSetInstances[] = new $file();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Loads custom rule groups (if set) into the current rules.
|
|
*
|
|
* Rules can be pre-defined in Config\Validation and can
|
|
* be any name, but must all still be an array of the
|
|
* same format used with setRules(). Additionally, check
|
|
* for {group}_errors for an array of custom error messages.
|
|
*
|
|
* @param string|null $group
|
|
*
|
|
* @return array|ValidationException|null
|
|
*/
|
|
public function loadRuleGroup(string $group = null)
|
|
{
|
|
if (empty($group))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (! isset($this->config->$group))
|
|
{
|
|
throw ValidationException::forGroupNotFound($group);
|
|
}
|
|
|
|
if (! is_array($this->config->$group))
|
|
{
|
|
throw ValidationException::forGroupNotArray($group);
|
|
}
|
|
|
|
$this->rules = $this->config->$group;
|
|
|
|
// If {group}_errors exists in the config file,
|
|
// then override our custom errors with them.
|
|
$errorName = $group . '_errors';
|
|
|
|
if (isset($this->config->$errorName))
|
|
{
|
|
$this->customErrors = $this->config->$errorName;
|
|
}
|
|
|
|
return $this->rules;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//--------------------------------------------------------------------
|
|
// Errors
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Checks to see if an error exists for the given field.
|
|
*
|
|
* @param string $field
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasError(string $field): bool
|
|
{
|
|
return array_key_exists($field, $this->getErrors());
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the error(s) for a specified $field (or empty string if not
|
|
* set).
|
|
*
|
|
* @param string $field Field.
|
|
*
|
|
* @return string Error(s).
|
|
*/
|
|
public function getError(string $field = null): string
|
|
{
|
|
if ($field === null && count($this->rules) === 1)
|
|
{
|
|
reset($this->rules);
|
|
$field = key($this->rules);
|
|
}
|
|
|
|
return array_key_exists($field, $this->getErrors()) ? $this->errors[$field] : '';
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Returns the array of errors that were encountered during
|
|
* a run() call. The array should be in the following format:
|
|
*
|
|
* [
|
|
* 'field1' => 'error message',
|
|
* 'field2' => 'error message',
|
|
* ]
|
|
*
|
|
* @return array
|
|
*
|
|
* Excluded from code coverage because that it always run as cli
|
|
*
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function getErrors(): array
|
|
{
|
|
// If we already have errors, we'll use those.
|
|
// If we don't, check the session to see if any were
|
|
// passed along from a redirect_with_input request.
|
|
if (empty($this->errors) && ! is_cli())
|
|
{
|
|
if (isset($_SESSION, $_SESSION['_ci_validation_errors']))
|
|
{
|
|
$this->errors = unserialize($_SESSION['_ci_validation_errors']);
|
|
}
|
|
}
|
|
|
|
return $this->errors ?? [];
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Sets the error for a specific field. Used by custom validation methods.
|
|
*
|
|
* @param string $field
|
|
* @param string $error
|
|
*
|
|
* @return \CodeIgniter\Validation\ValidationInterface
|
|
*/
|
|
public function setError(string $field, string $error): ValidationInterface
|
|
{
|
|
$this->errors[$field] = $error;
|
|
|
|
return $this;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Attempts to find the appropriate error message
|
|
*
|
|
* @param string $rule
|
|
* @param string $field
|
|
* @param string|null $label
|
|
* @param string $param
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getErrorMessage(string $rule, string $field, string $label = null, string $param = null): string
|
|
{
|
|
// Check if custom message has been defined by user
|
|
if (isset($this->customErrors[$field][$rule]))
|
|
{
|
|
$message = $this->customErrors[$field][$rule];
|
|
}
|
|
else
|
|
{
|
|
// Try to grab a localized version of the message...
|
|
// lang() will return the rule name back if not found,
|
|
// so there will always be a string being returned.
|
|
$message = lang('Validation.' . $rule);
|
|
}
|
|
|
|
$message = str_replace('{field}', $label ?? $field, $message);
|
|
$message = str_replace('{param}', $this->rules[$param]['label'] ?? $param, $message);
|
|
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Split rules string by pipe operator.
|
|
*
|
|
* @param string $rules
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function splitRules(string $rules): array
|
|
{
|
|
$non_escape_bracket = '((?<!\\\\)(?:\\\\\\\\)*[\[\]])';
|
|
$pipe_not_in_bracket = sprintf(
|
|
'/\|(?=(?:[^\[\]]*%s[^\[\]]*%s)*(?![^\[\]]*%s))/',
|
|
$non_escape_bracket,
|
|
$non_escape_bracket,
|
|
$non_escape_bracket
|
|
);
|
|
|
|
$_rules = preg_split(
|
|
$pipe_not_in_bracket,
|
|
$rules
|
|
);
|
|
|
|
return array_unique($_rules);
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//--------------------------------------------------------------------
|
|
// Misc
|
|
//--------------------------------------------------------------------
|
|
|
|
/**
|
|
* Resets the class to a blank slate. Should be called whenever
|
|
* you need to process more than one array.
|
|
*
|
|
* @return \CodeIgniter\Validation\ValidationInterface
|
|
*/
|
|
public function reset(): ValidationInterface
|
|
{
|
|
$this->data = [];
|
|
$this->rules = [];
|
|
$this->errors = [];
|
|
$this->customErrors = [];
|
|
|
|
return $this;
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
}
|
|
|