<?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Validator; use Traversable; use Zend\Stdlib\ArrayUtils; abstract class AbstractValidator implements Translator\TranslatorAwareInterface, ValidatorInterface { /** * The value to be validated * * @var mixed */ protected $value; /** * Default translation object for all validate objects * @var Translator\TranslatorInterface */ protected static $defaultTranslator; /** * Default text domain to be used with translator * @var string */ protected static $defaultTranslatorTextDomain = 'default'; /** * Limits the maximum returned length of a error message * * @var int */ protected static $messageLength = -1; protected $abstractOptions = array( 'messages' => array(), // Array of validation failure messages 'messageTemplates' => array(), // Array of validation failure message templates 'messageVariables' => array(), // Array of additional variables available for validation failure messages 'translator' => null, // Translation object to used -> Translator\TranslatorInterface 'translatorTextDomain' => null, // Translation text domain 'translatorEnabled' => true, // Is translation enabled? 'valueObscured' => false, // Flag indicating whether or not value should be obfuscated // in error messages ); /** * Abstract constructor for all validators * A validator should accept following parameters: * - nothing f.e. Validator() * - one or multiple scalar values f.e. Validator($first, $second, $third) * - an array f.e. Validator(array($first => 'first', $second => 'second', $third => 'third')) * - an instance of Traversable f.e. Validator($config_instance) * * @param array|Traversable $options */ public function __construct($options = null) { // The abstract constructor allows no scalar values if ($options instanceof Traversable) { $options = ArrayUtils::iteratorToArray($options); } if (isset($this->messageTemplates)) { $this->abstractOptions['messageTemplates'] = $this->messageTemplates; } if (isset($this->messageVariables)) { $this->abstractOptions['messageVariables'] = $this->messageVariables; } if (is_array($options)) { $this->setOptions($options); } } /** * Returns an option * * @param string $option Option to be returned * @return mixed Returned option * @throws Exception\InvalidArgumentException */ public function getOption($option) { if (array_key_exists($option, $this->abstractOptions)) { return $this->abstractOptions[$option]; } if (isset($this->options) && array_key_exists($option, $this->options)) { return $this->options[$option]; } throw new Exception\InvalidArgumentException("Invalid option '$option'"); } /** * Returns all available options * * @return array Array with all available options */ public function getOptions() { $result = $this->abstractOptions; if (isset($this->options)) { $result += $this->options; } return $result; } /** * Sets one or multiple options * * @param array|Traversable $options Options to set * @throws Exception\InvalidArgumentException If $options is not an array or Traversable * @return AbstractValidator Provides fluid interface */ public function setOptions($options = array()) { if (!is_array($options) && !$options instanceof Traversable) { throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable'); } foreach ($options as $name => $option) { $fname = 'set' . ucfirst($name); $fname2 = 'is' . ucfirst($name); if (($name != 'setOptions') && method_exists($this, $name)) { $this->{$name}($option); } elseif (($fname != 'setOptions') && method_exists($this, $fname)) { $this->{$fname}($option); } elseif (method_exists($this, $fname2)) { $this->{$fname2}($option); } elseif (isset($this->options)) { $this->options[$name] = $option; } else { $this->abstractOptions[$name] = $option; } } return $this; } /** * Returns array of validation failure messages * * @return array */ public function getMessages() { return array_unique($this->abstractOptions['messages'], SORT_REGULAR); } /** * Invoke as command * * @param mixed $value * @return bool */ public function __invoke($value) { return $this->isValid($value); } /** * Returns an array of the names of variables that are used in constructing validation failure messages * * @return array */ public function getMessageVariables() { return array_keys($this->abstractOptions['messageVariables']); } /** * Returns the message templates from the validator * * @return array */ public function getMessageTemplates() { return $this->abstractOptions['messageTemplates']; } /** * Sets the validation failure message template for a particular key * * @param string $messageString * @param string $messageKey OPTIONAL * @return AbstractValidator Provides a fluent interface * @throws Exception\InvalidArgumentException */ public function setMessage($messageString, $messageKey = null) { if ($messageKey === null) { $keys = array_keys($this->abstractOptions['messageTemplates']); foreach ($keys as $key) { $this->setMessage($messageString, $key); } return $this; } if (!isset($this->abstractOptions['messageTemplates'][$messageKey])) { throw new Exception\InvalidArgumentException("No message template exists for key '$messageKey'"); } $this->abstractOptions['messageTemplates'][$messageKey] = $messageString; return $this; } /** * Sets validation failure message templates given as an array, where the array keys are the message keys, * and the array values are the message template strings. * * @param array $messages * @return AbstractValidator */ public function setMessages(array $messages) { foreach ($messages as $key => $message) { $this->setMessage($message, $key); } return $this; } /** * Magic function returns the value of the requested property, if and only if it is the value or a * message variable. * * @param string $property * @return mixed * @throws Exception\InvalidArgumentException */ public function __get($property) { if ($property == 'value') { return $this->value; } if (array_key_exists($property, $this->abstractOptions['messageVariables'])) { $result = $this->abstractOptions['messageVariables'][$property]; if (is_array($result)) { $result = $this->{key($result)}[current($result)]; } else { $result = $this->{$result}; } return $result; } if (isset($this->messageVariables) && array_key_exists($property, $this->messageVariables)) { $result = $this->{$this->messageVariables[$property]}; if (is_array($result)) { $result = $this->{key($result)}[current($result)]; } else { $result = $this->{$result}; } return $result; } throw new Exception\InvalidArgumentException("No property exists by the name '$property'"); } /** * Constructs and returns a validation failure message with the given message key and value. * * Returns null if and only if $messageKey does not correspond to an existing template. * * If a translator is available and a translation exists for $messageKey, * the translation will be used. * * @param string $messageKey * @param string|array|object $value * @return string */ protected function createMessage($messageKey, $value) { if (!isset($this->abstractOptions['messageTemplates'][$messageKey])) { return null; } $message = $this->abstractOptions['messageTemplates'][$messageKey]; $message = $this->translateMessage($messageKey, $message); if (is_object($value) && !in_array('__toString', get_class_methods($value)) ) { $value = get_class($value) . ' object'; } elseif (is_array($value)) { $value = var_export($value, 1); } else { $value = (string) $value; } if ($this->isValueObscured()) { $value = str_repeat('*', strlen($value)); } $message = str_replace('%value%', (string) $value, $message); foreach ($this->abstractOptions['messageVariables'] as $ident => $property) { if (is_array($property)) { $value = $this->{key($property)}[current($property)]; if (is_array($value)) { $value = '[' . implode(', ', $value) . ']'; } } else { $value = $this->$property; } $message = str_replace("%$ident%", (string) $value, $message); } $length = self::getMessageLength(); if (($length > -1) && (strlen($message) > $length)) { $message = substr($message, 0, ($length - 3)) . '...'; } return $message; } /** * @param string $messageKey * @param string $value OPTIONAL * @return void */ protected function error($messageKey, $value = null) { if ($messageKey === null) { $keys = array_keys($this->abstractOptions['messageTemplates']); $messageKey = current($keys); } if ($value === null) { $value = $this->value; } $this->abstractOptions['messages'][$messageKey] = $this->createMessage($messageKey, $value); } /** * Returns the validation value * * @return mixed Value to be validated */ protected function getValue() { return $this->value; } /** * Sets the value to be validated and clears the messages and errors arrays * * @param mixed $value * @return void */ protected function setValue($value) { $this->value = $value; $this->abstractOptions['messages'] = array(); } /** * Set flag indicating whether or not value should be obfuscated in messages * * @param bool $flag * @return AbstractValidator */ public function setValueObscured($flag) { $this->abstractOptions['valueObscured'] = (bool) $flag; return $this; } /** * Retrieve flag indicating whether or not value should be obfuscated in * messages * * @return bool */ public function isValueObscured() { return $this->abstractOptions['valueObscured']; } /** * Set translation object * * @param Translator\TranslatorInterface|null $translator * @param string $textDomain (optional) * @return AbstractValidator * @throws Exception\InvalidArgumentException */ public function setTranslator(Translator\TranslatorInterface $translator = null, $textDomain = null) { $this->abstractOptions['translator'] = $translator; if (null !== $textDomain) { $this->setTranslatorTextDomain($textDomain); } return $this; } /** * Return translation object * * @return Translator\TranslatorInterface|null */ public function getTranslator() { if (! $this->isTranslatorEnabled()) { return null; } if (null === $this->abstractOptions['translator']) { $this->abstractOptions['translator'] = self::getDefaultTranslator(); } return $this->abstractOptions['translator']; } /** * Does this validator have its own specific translator? * * @return bool */ public function hasTranslator() { return (bool) $this->abstractOptions['translator']; } /** * Set translation text domain * * @param string $textDomain * @return AbstractValidator */ public function setTranslatorTextDomain($textDomain = 'default') { $this->abstractOptions['translatorTextDomain'] = $textDomain; return $this; } /** * Return the translation text domain * * @return string */ public function getTranslatorTextDomain() { if (null === $this->abstractOptions['translatorTextDomain']) { $this->abstractOptions['translatorTextDomain'] = self::getDefaultTranslatorTextDomain(); } return $this->abstractOptions['translatorTextDomain']; } /** * Set default translation object for all validate objects * * @param Translator\TranslatorInterface|null $translator * @param string $textDomain (optional) * @return void * @throws Exception\InvalidArgumentException */ public static function setDefaultTranslator( Translator\TranslatorInterface $translator = null, $textDomain = null ) { static::$defaultTranslator = $translator; if (null !== $textDomain) { self::setDefaultTranslatorTextDomain($textDomain); } } /** * Get default translation object for all validate objects * * @return Translator\TranslatorInterface|null */ public static function getDefaultTranslator() { return static::$defaultTranslator; } /** * Is there a default translation object set? * * @return bool */ public static function hasDefaultTranslator() { return (bool) static::$defaultTranslator; } /** * Set default translation text domain for all validate objects * * @param string $textDomain * @return void */ public static function setDefaultTranslatorTextDomain($textDomain = 'default') { static::$defaultTranslatorTextDomain = $textDomain; } /** * Get default translation text domain for all validate objects * * @return string */ public static function getDefaultTranslatorTextDomain() { return static::$defaultTranslatorTextDomain; } /** * Indicate whether or not translation should be enabled * * @param bool $flag * @return AbstractValidator */ public function setTranslatorEnabled($flag = true) { $this->abstractOptions['translatorEnabled'] = (bool) $flag; return $this; } /** * Is translation enabled? * * @return bool */ public function isTranslatorEnabled() { return $this->abstractOptions['translatorEnabled']; } /** * Returns the maximum allowed message length * * @return int */ public static function getMessageLength() { return static::$messageLength; } /** * Sets the maximum allowed message length * * @param int $length */ public static function setMessageLength($length = -1) { static::$messageLength = $length; } /** * Translate a validation message * * @param string $messageKey * @param string $message * @return string */ protected function translateMessage($messageKey, $message) { $translator = $this->getTranslator(); if (!$translator) { return $message; } $translated = $translator->translate( $messageKey, $this->getTranslatorTextDomain() ); if ($translated !== $messageKey) { return $translated; } return $translator->translate( $message, $this->getTranslatorTextDomain() ); } }