<?php
/**
 * CakeValidationRule.
 *
 * Provides the Model validation logic.
 *
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @package       Cake.Model.Validator
 * @since         CakePHP(tm) v 2.2.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */

App::uses('Validation', 'Utility');

/**
 * CakeValidationRule object. Represents a validation method, error message and
 * rules for applying such method to a field.
 *
 * @package       Cake.Model.Validator
 * @link          https://book.cakephp.org/2.0/en/data-validation.html
 */
class CakeValidationRule {

/**
 * Whether the field passed this validation rule
 *
 * @var mixed
 */
	protected $_valid = true;

/**
 * Holds whether the record being validated exists in datasource or not
 *
 * @var bool
 */
	protected $_recordExists = false;

/**
 * Validation method
 *
 * @var mixed
 */
	protected $_rule = null;

/**
 * Validation method arguments
 *
 * @var array
 */
	protected $_ruleParams = array();

/**
 * Holds passed in options
 *
 * @var array
 */
	protected $_passedOptions = array();

/**
 * The 'rule' key
 *
 * @var mixed
 */
	public $rule = 'blank';

/**
 * The 'required' key
 *
 * @var mixed
 */
	public $required = null;

/**
 * The 'allowEmpty' key
 *
 * @var bool
 */
	public $allowEmpty = null;

/**
 * The 'on' key
 *
 * @var string
 */
	public $on = null;

/**
 * The 'last' key
 *
 * @var bool
 */
	public $last = true;

/**
 * The 'message' key
 *
 * @var string
 */
	public $message = null;

/**
 * Constructor
 *
 * @param array $validator [optional] The validator properties
 */
	public function __construct($validator = array()) {
		$this->_addValidatorProps($validator);
	}

/**
 * Checks if the rule is valid
 *
 * @return bool
 */
	public function isValid() {
		if (!$this->_valid || (is_string($this->_valid) && !empty($this->_valid))) {
			return false;
		}

		return true;
	}

/**
 * Returns whether the field can be left blank according to this rule
 *
 * @return bool
 */
	public function isEmptyAllowed() {
		return $this->skip() || $this->allowEmpty === true;
	}

/**
 * Checks if the field is required according to the `required` property
 *
 * @return bool
 */
	public function isRequired() {
		if (in_array($this->required, array('create', 'update'), true)) {
			if ($this->required === 'create' && !$this->isUpdate() || $this->required === 'update' && $this->isUpdate()) {
				return true;
			}
			return false;
		}

		return $this->required;
	}

/**
 * Checks whether the field failed the `field should be present` validation
 *
 * @param string $field Field name
 * @param array &$data Data to check rule against
 * @return bool
 */
	public function checkRequired($field, &$data) {
		return (
			(!array_key_exists($field, $data) && $this->isRequired() === true) ||
			(
				array_key_exists($field, $data) && (empty($data[$field]) &&
				!is_numeric($data[$field])) && $this->allowEmpty === false
			)
		);
	}

/**
 * Checks if the allowEmpty key applies
 *
 * @param string $field Field name
 * @param array &$data data to check rule against
 * @return bool
 */
	public function checkEmpty($field, &$data) {
		if (empty($data[$field]) && $data[$field] != '0' && $this->allowEmpty === true) {
			return true;
		}
		return false;
	}

/**
 * Checks if the validation rule should be skipped
 *
 * @return bool True if the ValidationRule can be skipped
 */
	public function skip() {
		if (!empty($this->on)) {
			if ($this->on === 'create' && $this->isUpdate() || $this->on === 'update' && !$this->isUpdate()) {
				return true;
			}
		}
		return false;
	}

/**
 * Returns whether this rule should break validation process for associated field
 * after it fails
 *
 * @return bool
 */
	public function isLast() {
		return (bool)$this->last;
	}

/**
 * Gets the validation error message
 *
 * @return string
 */
	public function getValidationResult() {
		return $this->_valid;
	}

/**
 * Gets an array with the rule properties
 *
 * @return array
 */
	protected function _getPropertiesArray() {
		$rule = $this->rule;
		if (!is_string($rule)) {
			unset($rule[0]);
		}
		return array(
			'rule' => $rule,
			'required' => $this->required,
			'allowEmpty' => $this->allowEmpty,
			'on' => $this->on,
			'last' => $this->last,
			'message' => $this->message
		);
	}

/**
 * Sets the recordExists configuration value for this rule,
 * ir refers to whether the model record it is validating exists
 * exists in the collection or not (create or update operation)
 *
 * If called with no parameters it will return whether this rule
 * is configured for update operations or not.
 *
 * @param bool $exists Boolean to indicate if records exists
 * @return bool
 */
	public function isUpdate($exists = null) {
		if ($exists === null) {
			return $this->_recordExists;
		}
		return $this->_recordExists = $exists;
	}

/**
 * Dispatches the validation rule to the given validator method
 *
 * @param string $field Field name
 * @param array &$data Data array
 * @param array &$methods Methods list
 * @return bool True if the rule could be dispatched, false otherwise
 */
	public function process($field, &$data, &$methods) {
		$this->_valid = true;
		$this->_parseRule($field, $data);

		$validator = $this->_getPropertiesArray();
		$rule = strtolower($this->_rule);
		if (isset($methods[$rule])) {
			$this->_ruleParams[] = array_merge($validator, $this->_passedOptions);
			$this->_ruleParams[0] = array($field => $this->_ruleParams[0]);
			$this->_valid = call_user_func_array($methods[$rule], $this->_ruleParams);
		} elseif (class_exists('Validation') && method_exists('Validation', $this->_rule)) {
			$this->_valid = call_user_func_array(array('Validation', $this->_rule), $this->_ruleParams);
		} elseif (is_string($validator['rule'])) {
			$this->_valid = preg_match($this->_rule, $data[$field]);
		} else {
			trigger_error(__d('cake_dev', 'Could not find validation handler %s for %s', $this->_rule, $field), E_USER_WARNING);
			return false;
		}

		return true;
	}

/**
 * Resets internal state for this rule, by default it will become valid
 * and it will set isUpdate() to false
 *
 * @return void
 */
	public function reset() {
		$this->_valid = true;
		$this->_recordExists = false;
	}

/**
 * Returns passed options for this rule
 *
 * @param string|int $key Array index
 * @return array|null
 */
	public function getOptions($key) {
		if (!isset($this->_passedOptions[$key])) {
			return null;
		}
		return $this->_passedOptions[$key];
	}

/**
 * Sets the rule properties from the rule entry in validate
 *
 * @param array $validator [optional]
 * @return void
 */
	protected function _addValidatorProps($validator = array()) {
		if (!is_array($validator)) {
			$validator = array('rule' => $validator);
		}
		foreach ($validator as $key => $value) {
			if (isset($value) || !empty($value)) {
				if (in_array($key, array('rule', 'required', 'allowEmpty', 'on', 'message', 'last'))) {
					$this->{$key} = $validator[$key];
				} else {
					$this->_passedOptions[$key] = $value;
				}
			}
		}
	}

/**
 * Parses the rule and sets the rule and ruleParams
 *
 * @param string $field Field name
 * @param array &$data Data array
 * @return void
 */
	protected function _parseRule($field, &$data) {
		if (is_array($this->rule)) {
			$this->_rule = $this->rule[0];
			$this->_ruleParams = array_merge(array($data[$field]), array_values(array_slice($this->rule, 1)));
		} else {
			$this->_rule = $this->rule;
			$this->_ruleParams = array($data[$field]);
		}
	}

}
