This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
/**
* BooleanValidator checks if the attribute value is a boolean value.
*
* Possible boolean values can be configured via the [[trueValue]] and [[falseValue]] properties.
* And the comparison can be either [[strict]] or not.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BooleanValidator extends Validator
{
/**
* @var mixed the value representing true status. Defaults to '1'.
*/
public $trueValue = '1';
/**
* @var mixed the value representing false status. Defaults to '0'.
*/
public $falseValue = '0';
/**
* @var bool whether the comparison to [[trueValue]] and [[falseValue]] is strict.
* When this is true, the attribute value and type must both match those of [[trueValue]] or [[falseValue]].
* Defaults to false, meaning only the value needs to be matched.
*/
public $strict = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} must be either "{true}" or "{false}".');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if ($this->strict) {
$valid = $value === $this->trueValue || $value === $this->falseValue;
} else {
$valid = $value == $this->trueValue || $value == $this->falseValue;
}
if (!$valid) {
return [$this->message, [
'true' => $this->trueValue === true ? 'true' : $this->trueValue,
'false' => $this->falseValue === false ? 'false' : $this->falseValue,
]];
}
return null;
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = [
'trueValue' => $this->trueValue,
'falseValue' => $this->falseValue,
'message' => $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
'true' => $this->trueValue === true ? 'true' : $this->trueValue,
'false' => $this->falseValue === false ? 'false' : $this->falseValue,
]),
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
if ($this->strict) {
$options['strict'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,267 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
/**
* CompareValidator compares the specified attribute value with another value.
*
* The value being compared with can be another attribute value
* (specified via [[compareAttribute]]) or a constant (specified via
* [[compareValue]]. When both are specified, the latter takes
* precedence. If neither is specified, the attribute will be compared
* with another attribute whose name is by appending "_repeat" to the source
* attribute name.
*
* CompareValidator supports different comparison operators, specified
* via the [[operator]] property.
*
* The default comparison function is based on string values, which means the values
* are compared byte by byte. When comparing numbers, make sure to set the [[$type]]
* to [[TYPE_NUMBER]] to enable numeric comparison.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class CompareValidator extends Validator
{
/**
* Constant for specifying the comparison [[type]] by numeric values.
* @since 2.0.11
* @see type
*/
const TYPE_STRING = 'string';
/**
* Constant for specifying the comparison [[type]] by numeric values.
* @since 2.0.11
* @see type
*/
const TYPE_NUMBER = 'number';
/**
* @var string the name of the attribute to be compared with. When both this property
* and [[compareValue]] are set, the latter takes precedence. If neither is set,
* it assumes the comparison is against another attribute whose name is formed by
* appending '_repeat' to the attribute being validated. For example, if 'password' is
* being validated, then the attribute to be compared would be 'password_repeat'.
* @see compareValue
*/
public $compareAttribute;
/**
* @var mixed the constant value to be compared with. When both this property
* and [[compareAttribute]] are set, this property takes precedence.
* @see compareAttribute
*/
public $compareValue;
/**
* @var string the type of the values being compared. The follow types are supported:
*
* - [[TYPE_STRING|string]]: the values are being compared as strings. No conversion will be done before comparison.
* - [[TYPE_NUMBER|number]]: the values are being compared as numbers. String values will be converted into numbers before comparison.
*/
public $type = self::TYPE_STRING;
/**
* @var string the operator for comparison. The following operators are supported:
*
* - `==`: check if two values are equal. The comparison is done is non-strict mode.
* - `===`: check if two values are equal. The comparison is done is strict mode.
* - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
* - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
* - `>`: check if value being validated is greater than the value being compared with.
* - `>=`: check if value being validated is greater than or equal to the value being compared with.
* - `<`: check if value being validated is less than the value being compared with.
* - `<=`: check if value being validated is less than or equal to the value being compared with.
*
* When you want to compare numbers, make sure to also set [[type]] to `number`.
*/
public $operator = '==';
/**
* @var string the user-defined error message. It may contain the following placeholders which
* will be replaced accordingly by the validator:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
* - `{compareValue}`: the value or the attribute label to be compared with
* - `{compareAttribute}`: the label of the attribute to be compared with
* - `{compareValueOrAttribute}`: the value or the attribute label to be compared with
*/
public $message;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
switch ($this->operator) {
case '==':
$this->message = Yii::t('yii', '{attribute} must be equal to "{compareValueOrAttribute}".');
break;
case '===':
$this->message = Yii::t('yii', '{attribute} must be equal to "{compareValueOrAttribute}".');
break;
case '!=':
$this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValueOrAttribute}".');
break;
case '!==':
$this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValueOrAttribute}".');
break;
case '>':
$this->message = Yii::t('yii', '{attribute} must be greater than "{compareValueOrAttribute}".');
break;
case '>=':
$this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValueOrAttribute}".');
break;
case '<':
$this->message = Yii::t('yii', '{attribute} must be less than "{compareValueOrAttribute}".');
break;
case '<=':
$this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValueOrAttribute}".');
break;
default:
throw new InvalidConfigException("Unknown operator: {$this->operator}");
}
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
if ($this->compareValue !== null) {
$compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue;
} else {
$compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
$compareValue = $model->$compareAttribute;
$compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute);
}
if (!$this->compareValues($this->operator, $this->type, $value, $compareValue)) {
$this->addError($model, $attribute, $this->message, [
'compareAttribute' => $compareLabel,
'compareValue' => $compareValue,
'compareValueOrAttribute' => $compareValueOrAttribute,
]);
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if ($this->compareValue === null) {
throw new InvalidConfigException('CompareValidator::compareValue must be set.');
}
if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
return [$this->message, [
'compareAttribute' => $this->compareValue,
'compareValue' => $this->compareValue,
'compareValueOrAttribute' => $this->compareValue,
]];
}
return null;
}
/**
* Compares two values with the specified operator.
* @param string $operator the comparison operator
* @param string $type the type of the values being compared
* @param mixed $value the value being compared
* @param mixed $compareValue another value being compared
* @return bool whether the comparison using the specified operator is true.
*/
protected function compareValues($operator, $type, $value, $compareValue)
{
if ($type === self::TYPE_NUMBER) {
$value = (float) $value;
$compareValue = (float) $compareValue;
} else {
$value = (string) $value;
$compareValue = (string) $compareValue;
}
switch ($operator) {
case '==':
return $value == $compareValue;
case '===':
return $value === $compareValue;
case '!=':
return $value != $compareValue;
case '!==':
return $value !== $compareValue;
case '>':
return $value > $compareValue;
case '>=':
return $value >= $compareValue;
case '<':
return $value < $compareValue;
case '<=':
return $value <= $compareValue;
default:
return false;
}
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ', $form);';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = [
'operator' => $this->operator,
'type' => $this->type,
];
if ($this->compareValue !== null) {
$options['compareValue'] = $this->compareValue;
$compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue;
} else {
$compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
$compareValue = $model->getAttributeLabel($compareAttribute);
$options['compareAttribute'] = Html::getInputId($model, $compareAttribute);
$compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute);
}
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
$options['message'] = $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
'compareAttribute' => $compareLabel,
'compareValue' => $compareValue,
'compareValueOrAttribute' => $compareValueOrAttribute,
]);
return $options;
}
}

View File

@@ -0,0 +1,439 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use DateTime;
use IntlDateFormatter;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\FormatConverter;
/**
* DateValidator verifies if the attribute represents a date, time or datetime in a proper [[format]].
*
* It can also parse internationalized dates in a specific [[locale]] like e.g. `12 мая 2014` when [[format]]
* is configured to use a time pattern in ICU format.
*
* It is further possible to limit the date within a certain range using [[min]] and [[max]].
*
* Additional to validating the date it can also export the parsed timestamp as a machine readable format
* which can be configured using [[timestampAttribute]]. For values that include time information (not date-only values)
* also the time zone will be adjusted. The time zone of the input value is assumed to be the one specified by the [[timeZone]]
* property and the target timeZone will be UTC when [[timestampAttributeFormat]] is `null` (exporting as UNIX timestamp)
* or [[timestampAttributeTimeZone]] otherwise. If you want to avoid the time zone conversion, make sure that [[timeZone]] and
* [[timestampAttributeTimeZone]] are the same.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class DateValidator extends Validator
{
/**
* Constant for specifying the validation [[type]] as a date value, used for validation with intl short format.
* @since 2.0.8
* @see type
*/
const TYPE_DATE = 'date';
/**
* Constant for specifying the validation [[type]] as a datetime value, used for validation with intl short format.
* @since 2.0.8
* @see type
*/
const TYPE_DATETIME = 'datetime';
/**
* Constant for specifying the validation [[type]] as a time value, used for validation with intl short format.
* @since 2.0.8
* @see type
*/
const TYPE_TIME = 'time';
/**
* @var string the type of the validator. Indicates, whether a date, time or datetime value should be validated.
* This property influences the default value of [[format]] and also sets the correct behavior when [[format]] is one of the intl
* short formats, `short`, `medium`, `long`, or `full`.
*
* This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
*
* This property can be set to the following values:
*
* - [[TYPE_DATE]] - (default) for validating date values only, that means only values that do not include a time range are valid.
* - [[TYPE_DATETIME]] - for validating datetime values, that contain a date part as well as a time part.
* - [[TYPE_TIME]] - for validating time values, that contain no date information.
*
* @since 2.0.8
*/
public $type = self::TYPE_DATE;
/**
* @var string the date format that the value being validated should follow.
* This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax).
*
* Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP Datetime class.
* Please refer to <http://php.net/manual/en/datetime.createfromformat.php> on supported formats.
*
* If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details.
* Since version 2.0.8 the default value will be determined from different formats of the formatter class,
* dependent on the value of [[type]]:
*
* - if type is [[TYPE_DATE]], the default value will be taken from [[\yii\i18n\Formatter::dateFormat]],
* - if type is [[TYPE_DATETIME]], it will be taken from [[\yii\i18n\Formatter::datetimeFormat]],
* - and if type is [[TYPE_TIME]], it will be [[\yii\i18n\Formatter::timeFormat]].
*
* Here are some example values:
*
* ```php
* 'MM/dd/yyyy' // date in ICU format
* 'php:m/d/Y' // the same date in PHP format
* 'MM/dd/yyyy HH:mm' // not only dates but also times can be validated
* ```
*
* **Note:** the underlying date parsers being used vary dependent on the format. If you use the ICU format and
* the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed, the [IntlDateFormatter](http://php.net/manual/en/intldateformatter.parse.php)
* is used to parse the input value. In all other cases the PHP [DateTime](http://php.net/manual/en/datetime.createfromformat.php) class
* is used. The IntlDateFormatter has the advantage that it can parse international dates like `12. Mai 2015` or `12 мая 2014`, while the
* PHP parser is limited to English only. The PHP parser however is more strict about the input format as it will not accept
* `12.05.05` for the format `php:d.m.Y`, but the IntlDateFormatter will accept it for the format `dd.MM.yyyy`.
* If you need to use the IntlDateFormatter you can avoid this problem by specifying a [[min|minimum date]].
*/
public $format;
/**
* @var string the locale ID that is used to localize the date parsing.
* This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed.
* If not set, the locale of the [[\yii\base\Application::formatter|formatter]] will be used.
* See also [[\yii\i18n\Formatter::locale]].
*/
public $locale;
/**
* @var string the timezone to use for parsing date and time values.
* This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
* e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
* Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
* If this property is not set, [[\yii\base\Application::timeZone]] will be used.
*/
public $timeZone;
/**
* @var string the name of the attribute to receive the parsing result.
* When this property is not null and the validation is successful, the named attribute will
* receive the parsing result.
*
* This can be the same attribute as the one being validated. If this is the case,
* the original value will be overwritten with the timestamp value after successful validation.
*
* Note, that when using this property, the input value will be converted to a unix timestamp,
* which by definition is in UTC, so a conversion from the [[$timeZone|input time zone]] to UTC
* will be performed. When defining [[$timestampAttributeFormat]] you can control the conversion by
* setting [[$timestampAttributeTimeZone]] to a different value than `'UTC'`.
*
* @see timestampAttributeFormat
* @see timestampAttributeTimeZone
*/
public $timestampAttribute;
/**
* @var string the format to use when populating the [[timestampAttribute]].
* The format can be specified in the same way as for [[format]].
*
* If not set, [[timestampAttribute]] will receive a UNIX timestamp.
* If [[timestampAttribute]] is not set, this property will be ignored.
* @see format
* @see timestampAttribute
* @since 2.0.4
*/
public $timestampAttributeFormat;
/**
* @var string the timezone to use when populating the [[timestampAttribute]]. Defaults to `UTC`.
*
* This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php)
* e.g. `UTC`, `Europe/Berlin` or `America/Chicago`.
* Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones.
*
* If [[timestampAttributeFormat]] is not set, this property will be ignored.
* @see timestampAttributeFormat
* @since 2.0.4
*/
public $timestampAttributeTimeZone = 'UTC';
/**
* @var int|string upper limit of the date. Defaults to null, meaning no upper limit.
* This can be a unix timestamp or a string representing a date time value.
* If this property is a string, [[format]] will be used to parse it.
* @see tooBig for the customized message used when the date is too big.
* @since 2.0.4
*/
public $max;
/**
* @var int|string lower limit of the date. Defaults to null, meaning no lower limit.
* This can be a unix timestamp or a string representing a date time value.
* If this property is a string, [[format]] will be used to parse it.
* @see tooSmall for the customized message used when the date is too small.
* @since 2.0.4
*/
public $min;
/**
* @var string user-defined error message used when the value is bigger than [[max]].
* @since 2.0.4
*/
public $tooBig;
/**
* @var string user-defined error message used when the value is smaller than [[min]].
* @since 2.0.4
*/
public $tooSmall;
/**
* @var string user friendly value of upper limit to display in the error message.
* If this property is null, the value of [[max]] will be used (before parsing).
* @since 2.0.4
*/
public $maxString;
/**
* @var string user friendly value of lower limit to display in the error message.
* If this property is null, the value of [[min]] will be used (before parsing).
* @since 2.0.4
*/
public $minString;
/**
* @var array map of short format names to IntlDateFormatter constant values.
*/
private $_dateFormats = [
'short' => 3, // IntlDateFormatter::SHORT,
'medium' => 2, // IntlDateFormatter::MEDIUM,
'long' => 1, // IntlDateFormatter::LONG,
'full' => 0, // IntlDateFormatter::FULL,
];
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', 'The format of {attribute} is invalid.');
}
if ($this->format === null) {
if ($this->type === self::TYPE_DATE) {
$this->format = Yii::$app->formatter->dateFormat;
} elseif ($this->type === self::TYPE_DATETIME) {
$this->format = Yii::$app->formatter->datetimeFormat;
} elseif ($this->type === self::TYPE_TIME) {
$this->format = Yii::$app->formatter->timeFormat;
} else {
throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type);
}
}
if ($this->locale === null) {
$this->locale = Yii::$app->language;
}
if ($this->timeZone === null) {
$this->timeZone = Yii::$app->timeZone;
}
if ($this->min !== null && $this->tooSmall === null) {
$this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.');
}
if ($this->max !== null && $this->tooBig === null) {
$this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.');
}
if ($this->maxString === null) {
$this->maxString = (string) $this->max;
}
if ($this->minString === null) {
$this->minString = (string) $this->min;
}
if ($this->max !== null && is_string($this->max)) {
$timestamp = $this->parseDateValue($this->max);
if ($timestamp === false) {
throw new InvalidConfigException("Invalid max date value: {$this->max}");
}
$this->max = $timestamp;
}
if ($this->min !== null && is_string($this->min)) {
$timestamp = $this->parseDateValue($this->min);
if ($timestamp === false) {
throw new InvalidConfigException("Invalid min date value: {$this->min}");
}
$this->min = $timestamp;
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if ($this->isEmpty($value)) {
if ($this->timestampAttribute !== null) {
$model->{$this->timestampAttribute} = null;
}
return;
}
$timestamp = $this->parseDateValue($value);
if ($timestamp === false) {
if ($this->timestampAttribute === $attribute) {
if ($this->timestampAttributeFormat === null) {
if (is_int($value)) {
return;
}
} else {
if ($this->parseDateValueFormat($value, $this->timestampAttributeFormat) !== false) {
return;
}
}
}
$this->addError($model, $attribute, $this->message, []);
} elseif ($this->min !== null && $timestamp < $this->min) {
$this->addError($model, $attribute, $this->tooSmall, ['min' => $this->minString]);
} elseif ($this->max !== null && $timestamp > $this->max) {
$this->addError($model, $attribute, $this->tooBig, ['max' => $this->maxString]);
} elseif ($this->timestampAttribute !== null) {
if ($this->timestampAttributeFormat === null) {
$model->{$this->timestampAttribute} = $timestamp;
} else {
$model->{$this->timestampAttribute} = $this->formatTimestamp($timestamp, $this->timestampAttributeFormat);
}
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
$timestamp = $this->parseDateValue($value);
if ($timestamp === false) {
return [$this->message, []];
} elseif ($this->min !== null && $timestamp < $this->min) {
return [$this->tooSmall, ['min' => $this->minString]];
} elseif ($this->max !== null && $timestamp > $this->max) {
return [$this->tooBig, ['max' => $this->maxString]];
}
return null;
}
/**
* Parses date string into UNIX timestamp.
*
* @param string $value string representing date
* @return int|false a UNIX timestamp or `false` on failure.
*/
protected function parseDateValue($value)
{
// TODO consider merging these methods into single one at 2.1
return $this->parseDateValueFormat($value, $this->format);
}
/**
* Parses date string into UNIX timestamp.
*
* @param string $value string representing date
* @param string $format expected date format
* @return int|false a UNIX timestamp or `false` on failure.
*/
private function parseDateValueFormat($value, $format)
{
if (is_array($value)) {
return false;
}
if (strncmp($format, 'php:', 4) === 0) {
$format = substr($format, 4);
} else {
if (extension_loaded('intl')) {
return $this->parseDateValueIntl($value, $format);
}
// fallback to PHP if intl is not installed
$format = FormatConverter::convertDateIcuToPhp($format, 'date');
}
return $this->parseDateValuePHP($value, $format);
}
/**
* Parses a date value using the IntlDateFormatter::parse().
* @param string $value string representing date
* @param string $format the expected date format
* @return int|bool a UNIX timestamp or `false` on failure.
* @throws InvalidConfigException
*/
private function parseDateValueIntl($value, $format)
{
if (isset($this->_dateFormats[$format])) {
if ($this->type === self::TYPE_DATE) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, 'UTC');
} elseif ($this->type === self::TYPE_DATETIME) {
$formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone);
} elseif ($this->type === self::TYPE_TIME) {
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone);
} else {
throw new InvalidConfigException('Unknown validation type set for DateValidator::$type: ' . $this->type);
}
} else {
// if no time was provided in the format string set time to 0 to get a simple date timestamp
$hasTimeInfo = (strpbrk($format, 'ahHkKmsSA') !== false);
$formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $hasTimeInfo ? $this->timeZone : 'UTC', null, $format);
}
// enable strict parsing to avoid getting invalid date values
$formatter->setLenient(false);
// There should not be a warning thrown by parse() but this seems to be the case on windows so we suppress it here
// See https://github.com/yiisoft/yii2/issues/5962 and https://bugs.php.net/bug.php?id=68528
$parsePos = 0;
$parsedDate = @$formatter->parse($value, $parsePos);
if ($parsedDate === false || $parsePos !== mb_strlen($value, Yii::$app ? Yii::$app->charset : 'UTF-8')) {
return false;
}
return $parsedDate;
}
/**
* Parses a date value using the DateTime::createFromFormat().
* @param string $value string representing date
* @param string $format the expected date format
* @return int|bool a UNIX timestamp or `false` on failure.
*/
private function parseDateValuePHP($value, $format)
{
// if no time was provided in the format string set time to 0 to get a simple date timestamp
$hasTimeInfo = (strpbrk($format, 'HhGgisU') !== false);
$date = DateTime::createFromFormat($format, $value, new \DateTimeZone($hasTimeInfo ? $this->timeZone : 'UTC'));
$errors = DateTime::getLastErrors();
if ($date === false || $errors['error_count'] || $errors['warning_count']) {
return false;
}
if (!$hasTimeInfo) {
$date->setTime(0, 0, 0);
}
return $date->getTimestamp();
}
/**
* Formats a timestamp using the specified format.
* @param int $timestamp
* @param string $format
* @return string
*/
private function formatTimestamp($timestamp, $format)
{
if (strncmp($format, 'php:', 4) === 0) {
$format = substr($format, 4);
} else {
$format = FormatConverter::convertDateIcuToPhp($format, 'date');
}
$date = new DateTime();
$date->setTimestamp($timestamp);
$date->setTimezone(new \DateTimeZone($this->timestampAttributeTimeZone));
return $date->format($format);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
/**
* DefaultValueValidator sets the attribute to be the specified default value.
*
* DefaultValueValidator is not really a validator. It is provided mainly to allow
* specifying attribute default values when they are empty.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DefaultValueValidator extends Validator
{
/**
* @var mixed the default value or an anonymous function that returns the default value which will
* be assigned to the attributes being validated if they are empty. The signature of the anonymous function
* should be as follows,
*
* ```php
* function($model, $attribute) {
* // compute value
* return $value;
* }
* ```
*/
public $value;
/**
* @var bool this property is overwritten to be false so that this validator will
* be applied when the value being validated is empty.
*/
public $skipOnEmpty = false;
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
if ($this->isEmpty($model->$attribute)) {
if ($this->value instanceof \Closure) {
$model->$attribute = call_user_func($this->value, $model, $attribute);
} else {
$model->$attribute = $this->value;
}
}
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Model;
/**
* EachValidator validates an array by checking each of its elements against an embedded validation rule.
*
* ```php
* class MyModel extends Model
* {
* public $categoryIDs = [];
*
* public function rules()
* {
* return [
* // checks if every category ID is an integer
* ['categoryIDs', 'each', 'rule' => ['integer']],
* ]
* }
* }
* ```
*
* > Note: This validator will not work with inline validation rules in case of usage outside the model scope,
* e.g. via [[validate()]] method.
*
* > Note: EachValidator is meant to be used only in basic cases, you should consider usage of tabular input,
* using several models for the more complex case.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.4
*/
class EachValidator extends Validator
{
/**
* @var array|Validator definition of the validation rule, which should be used on array values.
* It should be specified in the same format as at [[\yii\base\Model::rules()]], except it should not
* contain attribute list as the first element.
* For example:
*
* ```php
* ['integer']
* ['match', 'pattern' => '/[a-z]/is']
* ```
*
* Please refer to [[\yii\base\Model::rules()]] for more details.
*/
public $rule;
/**
* @var bool whether to use error message composed by validator declared via [[rule]] if its validation fails.
* If enabled, error message specified for this validator itself will appear only if attribute value is not an array.
* If disabled, own error message value will be used always.
*/
public $allowMessageFromRule = true;
/**
* @var bool whether to stop validation once first error among attribute value elements is detected.
* When enabled validation will produce single error message on attribute, when disabled - multiple
* error messages mya appear: one per each invalid value.
* Note that this option will affect only [[validateAttribute()]] value, while [[validateValue()]] will
* not be affected.
* @since 2.0.11
*/
public $stopOnFirstError = true;
/**
* @var Validator validator instance.
*/
private $_validator;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* Returns the validator declared in [[rule]].
* @param Model|null $model model in which context validator should be created.
* @return Validator the declared validator.
*/
private function getValidator($model = null)
{
if ($this->_validator === null) {
$this->_validator = $this->createEmbeddedValidator($model);
}
return $this->_validator;
}
/**
* Creates validator object based on the validation rule specified in [[rule]].
* @param Model|null $model model in which context validator should be created.
* @throws \yii\base\InvalidConfigException
* @return Validator validator instance
*/
private function createEmbeddedValidator($model)
{
$rule = $this->rule;
if ($rule instanceof Validator) {
return $rule;
} elseif (is_array($rule) && isset($rule[0])) { // validator type
if (!is_object($model)) {
$model = new Model(); // mock up context model
}
return Validator::createValidator($rule[0], $model, $this->attributes, array_slice($rule, 1));
}
throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!is_array($value) && !$value instanceof \ArrayAccess) {
$this->addError($model, $attribute, $this->message, []);
return;
}
$validator = $this->getValidator($model); // ensure model context while validator creation
$detectedErrors = $model->getErrors($attribute);
$filteredValue = $model->$attribute;
foreach ($value as $k => $v) {
$model->clearErrors($attribute);
$model->$attribute = $v;
if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) {
$validator->validateAttribute($model, $attribute);
}
$filteredValue[$k] = $model->$attribute;
if ($model->hasErrors($attribute)) {
if ($this->allowMessageFromRule) {
$validationErrors = $model->getErrors($attribute);
$detectedErrors = array_merge($detectedErrors, $validationErrors);
} else {
$model->clearErrors($attribute);
$this->addError($model, $attribute, $this->message, ['value' => $v]);
$detectedErrors[] = $model->getFirstError($attribute);
}
$model->$attribute = $value;
if ($this->stopOnFirstError) {
break;
}
}
}
$model->$attribute = $filteredValue;
$model->clearErrors($attribute);
$model->addErrors([$attribute => $detectedErrors]);
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if (!is_array($value) && !$value instanceof \ArrayAccess) {
return [$this->message, []];
}
$validator = $this->getValidator();
foreach ($value as $v) {
if ($validator->skipOnEmpty && $validator->isEmpty($v)) {
continue;
}
$result = $validator->validateValue($v);
if ($result !== null) {
if ($this->allowMessageFromRule) {
$result[1]['value'] = $v;
return $result;
}
return [$this->message, ['value' => $v]];
}
}
return null;
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* EmailValidator validates that the attribute value is a valid email address.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class EmailValidator extends Validator
{
/**
* @var string the regular expression used to validate the attribute value.
* @see http://www.regular-expressions.info/email.html
*/
public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
/**
* @var string the regular expression used to validate email addresses with the name part.
* This property is used only when [[allowName]] is true.
* @see allowName
*/
public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
/**
* @var bool whether to allow name in the email address (e.g. "John Smith <john.smith@example.com>"). Defaults to false.
* @see fullPattern
*/
public $allowName = false;
/**
* @var bool whether to check whether the email's domain exists and has either an A or MX record.
* Be aware that this check can fail due to temporary DNS problems even if the email address is
* valid and an email would be deliverable. Defaults to false.
*/
public $checkDNS = false;
/**
* @var bool whether validation process should take into account IDN (internationalized domain
* names). Defaults to false meaning that validation of emails containing IDN will always fail.
* Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
* otherwise an exception would be thrown.
*/
public $enableIDN = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->enableIDN && !function_exists('idn_to_ascii')) {
throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is not a valid email address.');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if (!is_string($value)) {
$valid = false;
} elseif (!preg_match('/^(?P<name>(?:"?([^"]*)"?\s)?)(?:\s+)?(?:(?P<open><?)((?P<local>.+)@(?P<domain>[^>]+))(?P<close>>?))$/i', $value, $matches)) {
$valid = false;
} else {
if ($this->enableIDN) {
$matches['local'] = $this->idnToAscii($matches['local']);
$matches['domain'] = $this->idnToAscii($matches['domain']);
$value = $matches['name'] . $matches['open'] . $matches['local'] . '@' . $matches['domain'] . $matches['close'];
}
if (strlen($matches['local']) > 64) {
// The maximum total length of a user name or other local-part is 64 octets. RFC 5322 section 4.5.3.1.1
// http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
$valid = false;
} elseif (strlen($matches['local'] . '@' . $matches['domain']) > 254) {
// There is a restriction in RFC 2821 on the length of an address in MAIL and RCPT commands
// of 254 characters. Since addresses that do not fit in those fields are not normally useful, the
// upper limit on address lengths should normally be considered to be 254.
//
// Dominic Sayers, RFC 3696 erratum 1690
// http://www.rfc-editor.org/errata_search.php?eid=1690
$valid = false;
} else {
$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
if ($valid && $this->checkDNS) {
$valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
}
}
}
return $valid ? null : [$this->message, []];
}
private function idnToAscii($idn)
{
if (PHP_VERSION_ID < 50600) {
// TODO: drop old PHP versions support
return idn_to_ascii($idn);
}
return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = [
'pattern' => new JsExpression($this->pattern),
'fullPattern' => new JsExpression($this->fullPattern),
'allowName' => $this->allowName,
'message' => $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
]),
'enableIDN' => (bool) $this->enableIDN,
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,331 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\db\ActiveQuery;
use yii\db\ActiveRecord;
use yii\db\QueryInterface;
/**
* ExistValidator validates that the attribute value exists in a table.
*
* ExistValidator checks if the value being validated can be found in the table column specified by
* the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
*
* This validator is often used to verify that a foreign key contains a value
* that can be found in the foreign table.
*
* The following are examples of validation rules using this validator:
*
* ```php
* // a1 needs to exist
* ['a1', 'exist']
* // a1 needs to exist, but its value will use a2 to check for the existence
* ['a1', 'exist', 'targetAttribute' => 'a2']
* // a1 and a2 need to exist together, and they both will receive error message
* [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']]
* // a1 and a2 need to exist together, only a1 will receive error message
* ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']]
* // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value)
* ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ExistValidator extends Validator
{
/**
* @var string the name of the ActiveRecord class that should be used to validate the existence
* of the current attribute value. If not set, it will use the ActiveRecord class of the attribute being validated.
* @see targetAttribute
*/
public $targetClass;
/**
* @var string|array the name of the ActiveRecord attribute that should be used to
* validate the existence of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the existence
* of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
* the array value is the name of the database field to search.
*/
public $targetAttribute;
/**
* @var string the name of the relation that should be used to validate the existence of the current attribute value
* This param overwrites $targetClass and $targetAttribute
* @since 2.0.14
*/
public $targetRelation;
/**
* @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value.
* This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
* on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
* is the [[\yii\db\Query|Query]] object that you can modify in the function.
*/
public $filter;
/**
* @var bool whether to allow array type attribute.
*/
public $allowArray = false;
/**
* @var string and|or define how target attributes are related
* @since 2.0.11
*/
public $targetAttributeJunction = 'and';
/**
* @var bool whether this validator is forced to always use master DB
* @since 2.0.14
*/
public $forceMasterDb = true;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
if (!empty($this->targetRelation)) {
$this->checkTargetRelationExistence($model, $attribute);
} else {
$this->checkTargetAttributeExistence($model, $attribute);
}
}
/**
* Validates existence of the current attribute based on relation name
* @param \yii\db\ActiveRecord $model the data model to be validated
* @param string $attribute the name of the attribute to be validated.
*/
private function checkTargetRelationExistence($model, $attribute)
{
$exists = false;
/** @var ActiveQuery $relationQuery */
$relationQuery = $model->{'get' . ucfirst($this->targetRelation)}();
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $relationQuery);
} elseif ($this->filter !== null) {
$relationQuery->andWhere($this->filter);
}
if ($this->forceMasterDb && method_exists($model::getDb(), 'useMaster')) {
$model::getDb()->useMaster(function() use ($relationQuery, &$exists) {
$exists = $relationQuery->exists();
});
} else {
$exists = $relationQuery->exists();
}
if (!$exists) {
$this->addError($model, $attribute, $this->message);
}
}
/**
* Validates existence of the current attribute based on targetAttribute
* @param \yii\base\Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated.
*/
private function checkTargetAttributeExistence($model, $attribute)
{
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
$params = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions = [$this->targetAttributeJunction == 'or' ? 'or' : 'and'];
if (!$this->allowArray) {
foreach ($params as $key => $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
$conditions[] = [$key => $value];
}
} else {
$conditions[] = $params;
}
$targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
$query = $this->createQuery($targetClass, $conditions);
if (!$this->valueExists($targetClass, $query, $model->$attribute)) {
$this->addError($model, $attribute, $this->message);
}
}
/**
* Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
* [[\yii\db\Query::where()|Query::where()]] key-value format.
*
* @param $targetAttribute array|string $attribute the name of the ActiveRecord attribute that should be used to
* validate the existence of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the existence
* of multiple columns at the same time. The array key is the name of the attribute with the value to validate,
* the array value is the name of the database field to search.
* If the key and the value are the same, you can just specify the value.
* @param \yii\base\Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated in the $model
* @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
* @throws InvalidConfigException
*/
private function prepareConditions($targetAttribute, $model, $attribute)
{
if (is_array($targetAttribute)) {
if ($this->allowArray) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
$conditions = [];
foreach ($targetAttribute as $k => $v) {
$conditions[$v] = is_int($k) ? $model->$v : $model->$k;
}
} else {
$conditions = [$targetAttribute => $model->$attribute];
}
$targetModelClass = $this->getTargetClass($model);
if (!is_subclass_of($targetModelClass, 'yii\db\ActiveRecord')) {
return $conditions;
}
/** @var ActiveRecord $targetModelClass */
return $this->applyTableAlias($targetModelClass::find(), $conditions);
}
/**
* @param Model $model the data model to be validated
* @return string Target class name
*/
private function getTargetClass($model)
{
return $this->targetClass === null ? get_class($model) : $this->targetClass;
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if ($this->targetClass === null) {
throw new InvalidConfigException('The "targetClass" property must be set.');
}
if (!is_string($this->targetAttribute)) {
throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.');
}
if (is_array($value) && !$this->allowArray) {
return [$this->message, []];
}
$query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]);
return $this->valueExists($this->targetClass, $query, $value) ? null : [$this->message, []];
}
/**
* Check whether value exists in target table
*
* @param string $targetClass
* @param QueryInterface $query
* @param mixed $value the value want to be checked
* @return bool
*/
private function valueExists($targetClass, $query, $value)
{
$db = $targetClass::getDb();
$exists = false;
if ($this->forceMasterDb && method_exists($db, 'useMaster')) {
$db->useMaster(function ($db) use ($query, $value, &$exists) {
$exists = $this->queryValueExists($query, $value);
});
} else {
$exists = $this->queryValueExists($query, $value);
}
return $exists;
}
/**
* Run query to check if value exists
*
* @param QueryInterface $query
* @param mixed $value the value to be checked
* @return bool
*/
private function queryValueExists($query, $value)
{
if (is_array($value)) {
return $query->count("DISTINCT [[$this->targetAttribute]]") == count($value) ;
}
return $query->exists();
}
/**
* Creates a query instance with the given condition.
* @param string $targetClass the target AR class
* @param mixed $condition query condition
* @return \yii\db\ActiveQueryInterface the query instance
*/
protected function createQuery($targetClass, $condition)
{
/* @var $targetClass \yii\db\ActiveRecordInterface */
$query = $targetClass::find()->andWhere($condition);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
$query->andWhere($this->filter);
}
return $query;
}
/**
* Returns conditions with alias.
* @param ActiveQuery $query
* @param array $conditions array of condition, keys to be modified
* @param null|string $alias set empty string for no apply alias. Set null for apply primary table alias
* @return array
*/
private function applyTableAlias($query, $conditions, $alias = null)
{
if ($alias === null) {
$alias = array_keys($query->getTablesUsedInFrom())[0];
}
$prefixedConditions = [];
foreach ($conditions as $columnName => $columnValue) {
if (strpos($columnName, '(') === false) {
$prefixedColumn = "{$alias}.[[" . preg_replace(
'/^' . preg_quote($alias) . '\.(.*)$/',
'$1',
$columnName) . ']]';
} else {
// there is an expression, can't prefix it reliably
$prefixedColumn = $columnName;
}
$prefixedConditions[$prefixedColumn] = $columnValue;
}
return $prefixedConditions;
}
}

View File

@@ -0,0 +1,540 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\helpers\FileHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
use yii\web\UploadedFile;
/**
* FileValidator verifies if an attribute is receiving a valid uploaded file.
*
* Note that you should enable `fileinfo` PHP extension.
*
* @property int $sizeLimit The size limit for uploaded files. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FileValidator extends Validator
{
/**
* @var array|string a list of file name extensions that are allowed to be uploaded.
* This can be either an array or a string consisting of file extension names
* separated by space or comma (e.g. "gif, jpg").
* Extension names are case-insensitive. Defaults to null, meaning all file name
* extensions are allowed.
* @see wrongExtension for the customized message for wrong file type.
*/
public $extensions;
/**
* @var bool whether to check file type (extension) with mime-type. If extension produced by
* file mime-type check differs from uploaded file extension, the file will be considered as invalid.
*/
public $checkExtensionByMimeType = true;
/**
* @var array|string a list of file MIME types that are allowed to be uploaded.
* This can be either an array or a string consisting of file MIME types
* separated by space or comma (e.g. "text/plain, image/png").
* The mask with the special character `*` can be used to match groups of mime types.
* For example `image/*` will pass all mime types, that begin with `image/` (e.g. `image/jpeg`, `image/png`).
* Mime type names are case-insensitive. Defaults to null, meaning all MIME types are allowed.
* @see wrongMimeType for the customized message for wrong MIME type.
*/
public $mimeTypes;
/**
* @var int the minimum number of bytes required for the uploaded file.
* Defaults to null, meaning no limit.
* @see tooSmall for the customized message for a file that is too small.
*/
public $minSize;
/**
* @var int the maximum number of bytes required for the uploaded file.
* Defaults to null, meaning no limit.
* Note, the size limit is also affected by `upload_max_filesize` and `post_max_size` INI setting
* and the 'MAX_FILE_SIZE' hidden field value. See [[getSizeLimit()]] for details.
* @see http://php.net/manual/en/ini.core.php#ini.upload-max-filesize
* @see http://php.net/post-max-size
* @see getSizeLimit
* @see tooBig for the customized message for a file that is too big.
*/
public $maxSize;
/**
* @var int the maximum file count the given attribute can hold.
* Defaults to 1, meaning single file upload. By defining a higher number,
* multiple uploads become possible. Setting it to `0` means there is no limit on
* the number of files that can be uploaded simultaneously.
*
* > Note: The maximum number of files allowed to be uploaded simultaneously is
* also limited with PHP directive `max_file_uploads`, which defaults to 20.
*
* @see http://php.net/manual/en/ini.core.php#ini.max-file-uploads
* @see tooMany for the customized message when too many files are uploaded.
*/
public $maxFiles = 1;
/**
* @var int the minimum file count the given attribute can hold.
* Defaults to 0. Higher value means at least that number of files should be uploaded.
*
* @see tooFew for the customized message when too few files are uploaded.
* @since 2.0.14
*/
public $minFiles = 0;
/**
* @var string the error message used when a file is not uploaded correctly.
*/
public $message;
/**
* @var string the error message used when no file is uploaded.
* Note that this is the text of the validation error message. To make uploading files required,
* you have to set [[skipOnEmpty]] to `false`.
*/
public $uploadRequired;
/**
* @var string the error message used when the uploaded file is too large.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the maximum size allowed (see [[getSizeLimit()]])
* - {formattedLimit}: the maximum size formatted
* with [[\yii\i18n\Formatter::asShortSize()|Formatter::asShortSize()]]
*/
public $tooBig;
/**
* @var string the error message used when the uploaded file is too small.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[minSize]]
* - {formattedLimit}: the value of [[minSize]] formatted
* with [[\yii\i18n\Formatter::asShortSize()|Formatter::asShortSize()]
*/
public $tooSmall;
/**
* @var string the error message used if the count of multiple uploads exceeds limit.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {limit}: the value of [[maxFiles]]
*/
public $tooMany;
/**
* @var string the error message used if the count of multiple uploads less that minFiles.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {limit}: the value of [[minFiles]]
*
* @since 2.0.14
*/
public $tooFew;
/**
* @var string the error message used when the uploaded file has an extension name
* that is not listed in [[extensions]]. You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {extensions}: the list of the allowed extensions.
*/
public $wrongExtension;
/**
* @var string the error message used when the file has an mime type
* that is not allowed by [[mimeTypes]] property.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {mimeTypes}: the value of [[mimeTypes]]
*/
public $wrongMimeType;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', 'File upload failed.');
}
if ($this->uploadRequired === null) {
$this->uploadRequired = Yii::t('yii', 'Please upload a file.');
}
if ($this->tooMany === null) {
$this->tooMany = Yii::t('yii', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.');
}
if ($this->tooFew === null) {
$this->tooFew = Yii::t('yii', 'You should upload at least {limit, number} {limit, plural, one{file} other{files}}.');
}
if ($this->wrongExtension === null) {
$this->wrongExtension = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.');
}
if ($this->tooBig === null) {
$this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.');
}
if ($this->tooSmall === null) {
$this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.');
}
if (!is_array($this->extensions)) {
$this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY);
} else {
$this->extensions = array_map('strtolower', $this->extensions);
}
if ($this->wrongMimeType === null) {
$this->wrongMimeType = Yii::t('yii', 'Only files with these MIME types are allowed: {mimeTypes}.');
}
if (!is_array($this->mimeTypes)) {
$this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY);
} else {
$this->mimeTypes = array_map('strtolower', $this->mimeTypes);
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
if ($this->maxFiles != 1 || $this->minFiles > 1) {
$rawFiles = $model->$attribute;
if (!is_array($rawFiles)) {
$this->addError($model, $attribute, $this->uploadRequired);
return;
}
$files = $this->filterFiles($rawFiles);
$model->$attribute = $files;
if (empty($files)) {
$this->addError($model, $attribute, $this->uploadRequired);
return;
}
$filesCount = count($files);
if ($this->maxFiles && $filesCount > $this->maxFiles) {
$this->addError($model, $attribute, $this->tooMany, ['limit' => $this->maxFiles]);
}
if ($this->minFiles && $this->minFiles > $filesCount) {
$this->addError($model, $attribute, $this->tooFew, ['limit' => $this->minFiles]);
}
foreach ($files as $file) {
$result = $this->validateValue($file);
if (!empty($result)) {
$this->addError($model, $attribute, $result[0], $result[1]);
}
}
} else {
$result = $this->validateValue($model->$attribute);
if (!empty($result)) {
$this->addError($model, $attribute, $result[0], $result[1]);
}
}
}
/**
* Files filter.
* @param array $files
* @return UploadedFile[]
*/
private function filterFiles(array $files)
{
$result = [];
foreach ($files as $fileName => $file) {
if ($file instanceof UploadedFile && $file->error !== UPLOAD_ERR_NO_FILE) {
$result[$fileName] = $file;
}
}
return $result;
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if (!$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE) {
return [$this->uploadRequired, []];
}
switch ($value->error) {
case UPLOAD_ERR_OK:
if ($this->maxSize !== null && $value->size > $this->getSizeLimit()) {
return [
$this->tooBig,
[
'file' => $value->name,
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
],
];
} elseif ($this->minSize !== null && $value->size < $this->minSize) {
return [
$this->tooSmall,
[
'file' => $value->name,
'limit' => $this->minSize,
'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize),
],
];
} elseif (!empty($this->extensions) && !$this->validateExtension($value)) {
return [$this->wrongExtension, ['file' => $value->name, 'extensions' => implode(', ', $this->extensions)]];
} elseif (!empty($this->mimeTypes) && !$this->validateMimeType($value)) {
return [$this->wrongMimeType, ['file' => $value->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
}
return null;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return [$this->tooBig, [
'file' => $value->name,
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
]];
case UPLOAD_ERR_PARTIAL:
Yii::warning('File was only partially uploaded: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_NO_TMP_DIR:
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_CANT_WRITE:
Yii::warning('Failed to write the uploaded file to disk: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_EXTENSION:
Yii::warning('File upload was stopped by some PHP extension: ' . $value->name, __METHOD__);
break;
default:
break;
}
return [$this->message, []];
}
/**
* Returns the maximum size allowed for uploaded files.
*
* This is determined based on four factors:
*
* - 'upload_max_filesize' in php.ini
* - 'post_max_size' in php.ini
* - 'MAX_FILE_SIZE' hidden field
* - [[maxSize]]
*
* @return int the size limit for uploaded files.
*/
public function getSizeLimit()
{
// Get the lowest between post_max_size and upload_max_filesize, log a warning if the first is < than the latter
$limit = $this->sizeToBytes(ini_get('upload_max_filesize'));
$postLimit = $this->sizeToBytes(ini_get('post_max_size'));
if ($postLimit > 0 && $postLimit < $limit) {
Yii::warning('PHP.ini\'s \'post_max_size\' is less than \'upload_max_filesize\'.', __METHOD__);
$limit = $postLimit;
}
if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) {
$limit = $this->maxSize;
}
if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) {
$limit = (int) $_POST['MAX_FILE_SIZE'];
}
return $limit;
}
/**
* {@inheritdoc}
* @param bool $trim
*/
public function isEmpty($value, $trim = false)
{
$value = is_array($value) ? reset($value) : $value;
return !($value instanceof UploadedFile) || $value->error == UPLOAD_ERR_NO_FILE;
}
/**
* Converts php.ini style size to bytes.
*
* @param string $sizeStr $sizeStr
* @return int
*/
private function sizeToBytes($sizeStr)
{
switch (substr($sizeStr, -1)) {
case 'M':
case 'm':
return (int) $sizeStr * 1048576;
case 'K':
case 'k':
return (int) $sizeStr * 1024;
case 'G':
case 'g':
return (int) $sizeStr * 1073741824;
default:
return (int) $sizeStr;
}
}
/**
* Checks if given uploaded file have correct type (extension) according current validator settings.
* @param UploadedFile $file
* @return bool
*/
protected function validateExtension($file)
{
$extension = mb_strtolower($file->extension, 'UTF-8');
if ($this->checkExtensionByMimeType) {
$mimeType = FileHelper::getMimeType($file->tempName, null, false);
if ($mimeType === null) {
return false;
}
$extensionsByMimeType = FileHelper::getExtensionsByMimeType($mimeType);
if (!in_array($extension, $extensionsByMimeType, true)) {
return false;
}
}
if (!in_array($extension, $this->extensions, true)) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.file(attribute, messages, ' . Json::encode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);
$options = [];
if ($this->message !== null) {
$options['message'] = $this->formatMessage($this->message, [
'attribute' => $label,
]);
}
$options['skipOnEmpty'] = $this->skipOnEmpty;
if (!$this->skipOnEmpty) {
$options['uploadRequired'] = $this->formatMessage($this->uploadRequired, [
'attribute' => $label,
]);
}
if ($this->mimeTypes !== null) {
$mimeTypes = [];
foreach ($this->mimeTypes as $mimeType) {
$mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($this->buildMimeTypeRegexp($mimeType)));
}
$options['mimeTypes'] = $mimeTypes;
$options['wrongMimeType'] = $this->formatMessage($this->wrongMimeType, [
'attribute' => $label,
'mimeTypes' => implode(', ', $this->mimeTypes),
]);
}
if ($this->extensions !== null) {
$options['extensions'] = $this->extensions;
$options['wrongExtension'] = $this->formatMessage($this->wrongExtension, [
'attribute' => $label,
'extensions' => implode(', ', $this->extensions),
]);
}
if ($this->minSize !== null) {
$options['minSize'] = $this->minSize;
$options['tooSmall'] = $this->formatMessage($this->tooSmall, [
'attribute' => $label,
'limit' => $this->minSize,
'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize),
]);
}
if ($this->maxSize !== null) {
$options['maxSize'] = $this->maxSize;
$options['tooBig'] = $this->formatMessage($this->tooBig, [
'attribute' => $label,
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
]);
}
if ($this->maxFiles !== null) {
$options['maxFiles'] = $this->maxFiles;
$options['tooMany'] = $this->formatMessage($this->tooMany, [
'attribute' => $label,
'limit' => $this->maxFiles,
]);
}
return $options;
}
/**
* Builds the RegExp from the $mask.
*
* @param string $mask
* @return string the regular expression
* @see mimeTypes
*/
private function buildMimeTypeRegexp($mask)
{
return '/^' . str_replace('\*', '.*', preg_quote($mask, '/')) . '$/';
}
/**
* Checks the mimeType of the $file against the list in the [[mimeTypes]] property.
*
* @param UploadedFile $file
* @return bool whether the $file mimeType is allowed
* @throws \yii\base\InvalidConfigException
* @see mimeTypes
* @since 2.0.8
*/
protected function validateMimeType($file)
{
$fileMimeType = FileHelper::getMimeType($file->tempName);
foreach ($this->mimeTypes as $mimeType) {
if ($mimeType === $fileMimeType) {
return true;
}
if (strpos($mimeType, '*') !== false && preg_match($this->buildMimeTypeRegexp($mimeType), $fileMimeType)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use yii\base\InvalidConfigException;
/**
* FilterValidator converts the attribute value according to a filter.
*
* FilterValidator is actually not a validator but a data processor.
* It invokes the specified filter callback to process the attribute value
* and save the processed value back to the attribute. The filter must be
* a valid PHP callback with the following signature:
*
* ```php
* function foo($value) {
* // compute $newValue here
* return $newValue;
* }
* ```
*
* Many PHP functions qualify this signature (e.g. `trim()`).
*
* To specify the filter, set [[filter]] property to be the callback.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FilterValidator extends Validator
{
/**
* @var callable the filter. This can be a global function name, anonymous function, etc.
* The function signature must be as follows,
*
* ```php
* function foo($value) {
* // compute $newValue here
* return $newValue;
* }
* ```
*/
public $filter;
/**
* @var bool whether the filter should be skipped if an array input is given.
* If true and an array input is given, the filter will not be applied.
*/
public $skipOnArray = false;
/**
* @var bool this property is overwritten to be false so that this validator will
* be applied when the value being validated is empty.
*/
public $skipOnEmpty = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->filter === null) {
throw new InvalidConfigException('The "filter" property must be set.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!$this->skipOnArray || !is_array($value)) {
$model->$attribute = call_user_func($this->filter, $value);
}
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
if ($this->filter !== 'trim') {
return null;
}
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = [];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,221 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\web\UploadedFile;
/**
* ImageValidator verifies if an attribute is receiving a valid image.
*
* @author Taras Gudz <gudz.taras@gmail.com>
* @since 2.0
*/
class ImageValidator extends FileValidator
{
/**
* @var string the error message used when the uploaded file is not an image.
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
*/
public $notImage;
/**
* @var int the minimum width in pixels.
* Defaults to null, meaning no limit.
* @see underWidth for the customized message used when image width is too small.
*/
public $minWidth;
/**
* @var int the maximum width in pixels.
* Defaults to null, meaning no limit.
* @see overWidth for the customized message used when image width is too big.
*/
public $maxWidth;
/**
* @var int the minimum height in pixels.
* Defaults to null, meaning no limit.
* @see underHeight for the customized message used when image height is too small.
*/
public $minHeight;
/**
* @var int the maximum width in pixels.
* Defaults to null, meaning no limit.
* @see overWidth for the customized message used when image height is too big.
*/
public $maxHeight;
/**
* @var string the error message used when the image is under [[minWidth]].
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[minWidth]]
*/
public $underWidth;
/**
* @var string the error message used when the image is over [[maxWidth]].
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[maxWidth]]
*/
public $overWidth;
/**
* @var string the error message used when the image is under [[minHeight]].
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[minHeight]]
*/
public $underHeight;
/**
* @var string the error message used when the image is over [[maxHeight]].
* You may use the following tokens in the message:
*
* - {attribute}: the attribute name
* - {file}: the uploaded file name
* - {limit}: the value of [[maxHeight]]
*/
public $overHeight;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->notImage === null) {
$this->notImage = Yii::t('yii', 'The file "{file}" is not an image.');
}
if ($this->underWidth === null) {
$this->underWidth = Yii::t('yii', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
}
if ($this->underHeight === null) {
$this->underHeight = Yii::t('yii', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
}
if ($this->overWidth === null) {
$this->overWidth = Yii::t('yii', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
}
if ($this->overHeight === null) {
$this->overHeight = Yii::t('yii', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
$result = parent::validateValue($value);
return empty($result) ? $this->validateImage($value) : $result;
}
/**
* Validates an image file.
* @param UploadedFile $image uploaded file passed to check against a set of rules
* @return array|null the error message and the parameters to be inserted into the error message.
* Null should be returned if the data is valid.
*/
protected function validateImage($image)
{
if (false === ($imageInfo = getimagesize($image->tempName))) {
return [$this->notImage, ['file' => $image->name]];
}
list($width, $height) = $imageInfo;
if ($width == 0 || $height == 0) {
return [$this->notImage, ['file' => $image->name]];
}
if ($this->minWidth !== null && $width < $this->minWidth) {
return [$this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]];
}
if ($this->minHeight !== null && $height < $this->minHeight) {
return [$this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]];
}
if ($this->maxWidth !== null && $width > $this->maxWidth) {
return [$this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]];
}
if ($this->maxHeight !== null && $height > $this->maxHeight) {
return [$this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]];
}
return null;
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.image(attribute, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ', deferred);';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = parent::getClientOptions($model, $attribute);
$label = $model->getAttributeLabel($attribute);
if ($this->notImage !== null) {
$options['notImage'] = $this->formatMessage($this->notImage, [
'attribute' => $label,
]);
}
if ($this->minWidth !== null) {
$options['minWidth'] = $this->minWidth;
$options['underWidth'] = $this->formatMessage($this->underWidth, [
'attribute' => $label,
'limit' => $this->minWidth,
]);
}
if ($this->maxWidth !== null) {
$options['maxWidth'] = $this->maxWidth;
$options['overWidth'] = $this->formatMessage($this->overWidth, [
'attribute' => $label,
'limit' => $this->maxWidth,
]);
}
if ($this->minHeight !== null) {
$options['minHeight'] = $this->minHeight;
$options['underHeight'] = $this->formatMessage($this->underHeight, [
'attribute' => $label,
'limit' => $this->minHeight,
]);
}
if ($this->maxHeight !== null) {
$options['maxHeight'] = $this->maxHeight;
$options['overHeight'] = $this->formatMessage($this->overHeight, [
'attribute' => $label,
'limit' => $this->maxHeight,
]);
}
return $options;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
/**
* InlineValidator represents a validator which is defined as a method in the object being validated.
*
* The validation method must have the following signature:
*
* ```php
* function foo($attribute, $params, $validator)
* ```
*
* where `$attribute` refers to the name of the attribute being validated, while `$params` is an array representing the
* additional parameters supplied in the validation rule. Parameter `$validator` refers to the related
* [[InlineValidator]] object and is available since version 2.0.11.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InlineValidator extends Validator
{
/**
* @var string|\Closure an anonymous function or the name of a model class method that will be
* called to perform the actual validation. The signature of the method should be like the following:
*
* ```php
* function foo($attribute, $params, $validator)
* ```
*
* - `$attribute` is the name of the attribute to be validated;
* - `$params` contains the value of [[params]] that you specify when declaring the inline validation rule;
* - `$validator` is a reference to related [[InlineValidator]] object. This parameter is available since version 2.0.11.
*/
public $method;
/**
* @var mixed additional parameters that are passed to the validation method
*/
public $params;
/**
* @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
* The signature of the method should be like the following:
*
* ```php
* function foo($attribute, $params, $validator)
* {
* return "javascript";
* }
* ```
*
* where `$attribute` refers to the attribute name to be validated.
*
* Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
*/
public $clientValidate;
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$method = $this->method;
if (is_string($method)) {
$method = [$model, $method];
}
call_user_func($method, $attribute, $this->params, $this);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
if ($this->clientValidate !== null) {
$method = $this->clientValidate;
if (is_string($method)) {
$method = [$model, $method];
}
return call_user_func($method, $attribute, $this->params, $this);
}
return null;
}
}

View File

@@ -0,0 +1,580 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\IpHelper;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* The validator checks if the attribute value is a valid IPv4/IPv6 address or subnet.
*
* It also may change attribute's value if normalization of IPv6 expansion is enabled.
*
* The following are examples of validation rules using this validator:
*
* ```php
* ['ip_address', 'ip'], // IPv4 or IPv6 address
* ['ip_address', 'ip', 'ipv6' => false], // IPv4 address (IPv6 is disabled)
* ['ip_address', 'ip', 'subnet' => true], // requires a CIDR prefix (like 10.0.0.1/24) for the IP address
* ['ip_address', 'ip', 'subnet' => null], // CIDR prefix is optional
* ['ip_address', 'ip', 'subnet' => null, 'normalize' => true], // CIDR prefix is optional and will be added when missing
* ['ip_address', 'ip', 'ranges' => ['192.168.0.0/24']], // only IP addresses from the specified subnet are allowed
* ['ip_address', 'ip', 'ranges' => ['!192.168.0.0/24', 'any']], // any IP is allowed except IP in the specified subnet
* ['ip_address', 'ip', 'expandIPv6' => true], // expands IPv6 address to a full notation format
* ```
*
* @property array $ranges The IPv4 or IPv6 ranges that are allowed or forbidden. See [[setRanges()]] for
* detailed description.
*
* @author Dmitry Naumenko <d.naumenko.a@gmail.com>
* @since 2.0.7
*/
class IpValidator extends Validator
{
/**
* Negation char.
*
* Used to negate [[ranges]] or [[networks]] or to negate validating value when [[negation]] is set to `true`.
* @see negation
* @see networks
* @see ranges
*/
const NEGATION_CHAR = '!';
/**
* @var array The network aliases, that can be used in [[ranges]].
* - key - alias name
* - value - array of strings. String can be an IP range, IP address or another alias. String can be
* negated with [[NEGATION_CHAR]] (independent of `negation` option).
*
* The following aliases are defined by default:
* - `*`: `any`
* - `any`: `0.0.0.0/0, ::/0`
* - `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8`
* - `multicast`: `224.0.0.0/4, ff00::/8`
* - `linklocal`: `169.254.0.0/16, fe80::/10`
* - `localhost`: `127.0.0.0/8', ::1`
* - `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32`
* - `system`: `multicast, linklocal, localhost, documentation`
*/
public $networks = [
'*' => ['any'],
'any' => ['0.0.0.0/0', '::/0'],
'private' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fd00::/8'],
'multicast' => ['224.0.0.0/4', 'ff00::/8'],
'linklocal' => ['169.254.0.0/16', 'fe80::/10'],
'localhost' => ['127.0.0.0/8', '::1'],
'documentation' => ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', '2001:db8::/32'],
'system' => ['multicast', 'linklocal', 'localhost', 'documentation'],
];
/**
* @var bool whether the validating value can be an IPv6 address. Defaults to `true`.
*/
public $ipv6 = true;
/**
* @var bool whether the validating value can be an IPv4 address. Defaults to `true`.
*/
public $ipv4 = true;
/**
* @var bool whether the address can be an IP with CIDR subnet, like `192.168.10.0/24`.
* The following values are possible:
*
* - `false` - the address must not have a subnet (default).
* - `true` - specifying a subnet is required.
* - `null` - specifying a subnet is optional.
*/
public $subnet = false;
/**
* @var bool whether to add the CIDR prefix with the smallest length (32 for IPv4 and 128 for IPv6) to an
* address without it. Works only when `subnet` is not `false`. For example:
* - `10.0.1.5` will normalized to `10.0.1.5/32`
* - `2008:db0::1` will be normalized to `2008:db0::1/128`
* Defaults to `false`.
* @see subnet
*/
public $normalize = false;
/**
* @var bool whether address may have a [[NEGATION_CHAR]] character at the beginning.
* Defaults to `false`.
*/
public $negation = false;
/**
* @var bool whether to expand an IPv6 address to the full notation format.
* Defaults to `false`.
*/
public $expandIPv6 = false;
/**
* @var string Regexp-pattern to validate IPv4 address
*/
public $ipv4Pattern = '/^(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))$/';
/**
* @var string Regexp-pattern to validate IPv6 address
*/
public $ipv6Pattern = '/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/';
/**
* @var string user-defined error message is used when validation fails due to the wrong IP address format.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*/
public $message;
/**
* @var string user-defined error message is used when validation fails due to the disabled IPv6 validation.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* @see ipv6
*/
public $ipv6NotAllowed;
/**
* @var string user-defined error message is used when validation fails due to the disabled IPv4 validation.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* @see ipv4
*/
public $ipv4NotAllowed;
/**
* @var string user-defined error message is used when validation fails due to the wrong CIDR.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
* @see subnet
*/
public $wrongCidr;
/**
* @var string user-defined error message is used when validation fails due to subnet [[subnet]] set to 'only',
* but the CIDR prefix is not set.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* @see subnet
*/
public $noSubnet;
/**
* @var string user-defined error message is used when validation fails
* due to [[subnet]] is false, but CIDR prefix is present.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* @see subnet
*/
public $hasSubnet;
/**
* @var string user-defined error message is used when validation fails due to IP address
* is not not allowed by [[ranges]] check.
*
* You may use the following placeholders in the message:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* @see ranges
*/
public $notInRange;
/**
* @var array
*/
private $_ranges = [];
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if (!$this->ipv4 && !$this->ipv6) {
throw new InvalidConfigException('Both IPv4 and IPv6 checks can not be disabled at the same time');
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} must be a valid IP address.');
}
if ($this->ipv6NotAllowed === null) {
$this->ipv6NotAllowed = Yii::t('yii', '{attribute} must not be an IPv6 address.');
}
if ($this->ipv4NotAllowed === null) {
$this->ipv4NotAllowed = Yii::t('yii', '{attribute} must not be an IPv4 address.');
}
if ($this->wrongCidr === null) {
$this->wrongCidr = Yii::t('yii', '{attribute} contains wrong subnet mask.');
}
if ($this->noSubnet === null) {
$this->noSubnet = Yii::t('yii', '{attribute} must be an IP address with specified subnet.');
}
if ($this->hasSubnet === null) {
$this->hasSubnet = Yii::t('yii', '{attribute} must not be a subnet.');
}
if ($this->notInRange === null) {
$this->notInRange = Yii::t('yii', '{attribute} is not in the allowed range.');
}
}
/**
* Set the IPv4 or IPv6 ranges that are allowed or forbidden.
*
* The following preparation tasks are performed:
*
* - Recursively substitutes aliases (described in [[networks]]) with their values.
* - Removes duplicates
*
* @property array the IPv4 or IPv6 ranges that are allowed or forbidden.
* See [[setRanges()]] for detailed description.
* @param array $ranges the IPv4 or IPv6 ranges that are allowed or forbidden.
*
* When the array is empty, or the option not set, all IP addresses are allowed.
*
* Otherwise, the rules are checked sequentially until the first match is found.
* An IP address is forbidden, when it has not matched any of the rules.
*
* Example:
*
* ```php
* [
* 'ranges' => [
* '192.168.10.128'
* '!192.168.10.0/24',
* 'any' // allows any other IP addresses
* ]
* ]
* ```
*
* In this example, access is allowed for all the IPv4 and IPv6 addresses excluding the `192.168.10.0/24` subnet.
* IPv4 address `192.168.10.128` is also allowed, because it is listed before the restriction.
*/
public function setRanges($ranges)
{
$this->_ranges = $this->prepareRanges((array) $ranges);
}
/**
* @return array The IPv4 or IPv6 ranges that are allowed or forbidden.
*/
public function getRanges()
{
return $this->_ranges;
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
$result = $this->validateSubnet($value);
if (is_array($result)) {
$result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
return $result;
}
return null;
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
$result = $this->validateSubnet($value);
if (is_array($result)) {
$result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
$this->addError($model, $attribute, $result[0], $result[1]);
} else {
$model->$attribute = $result;
}
}
/**
* Validates an IPv4/IPv6 address or subnet.
*
* @param $ip string
* @return string|array
* string - the validation was successful;
* array - an error occurred during the validation.
* Array[0] contains the text of an error, array[1] contains values for the placeholders in the error message
*/
private function validateSubnet($ip)
{
if (!is_string($ip)) {
return [$this->message, []];
}
$negation = null;
$cidr = null;
$isCidrDefault = false;
if (preg_match($this->getIpParsePattern(), $ip, $matches)) {
$negation = ($matches[1] !== '') ? $matches[1] : null;
$ip = $matches[2];
$cidr = isset($matches[4]) ? $matches[4] : null;
}
if ($this->subnet === true && $cidr === null) {
return [$this->noSubnet, []];
}
if ($this->subnet === false && $cidr !== null) {
return [$this->hasSubnet, []];
}
if ($this->negation === false && $negation !== null) {
return [$this->message, []];
}
if ($this->getIpVersion($ip) === IpHelper::IPV6) {
if ($cidr !== null) {
if ($cidr > IpHelper::IPV6_ADDRESS_LENGTH || $cidr < 0) {
return [$this->wrongCidr, []];
}
} else {
$isCidrDefault = true;
$cidr = IpHelper::IPV6_ADDRESS_LENGTH;
}
if (!$this->validateIPv6($ip)) {
return [$this->message, []];
}
if (!$this->ipv6) {
return [$this->ipv6NotAllowed, []];
}
if ($this->expandIPv6) {
$ip = $this->expandIPv6($ip);
}
} else {
if ($cidr !== null) {
if ($cidr > IpHelper::IPV4_ADDRESS_LENGTH || $cidr < 0) {
return [$this->wrongCidr, []];
}
} else {
$isCidrDefault = true;
$cidr = IpHelper::IPV4_ADDRESS_LENGTH;
}
if (!$this->validateIPv4($ip)) {
return [$this->message, []];
}
if (!$this->ipv4) {
return [$this->ipv4NotAllowed, []];
}
}
if (!$this->isAllowed($ip, $cidr)) {
return [$this->notInRange, []];
}
$result = $negation . $ip;
if ($this->subnet !== false && (!$isCidrDefault || $isCidrDefault && $this->normalize)) {
$result .= "/$cidr";
}
return $result;
}
/**
* Expands an IPv6 address to it's full notation.
*
* For example `2001:db8::1` will be expanded to `2001:0db8:0000:0000:0000:0000:0000:0001`.
*
* @param string $ip the original IPv6
* @return string the expanded IPv6
*/
private function expandIPv6($ip)
{
return IpHelper::expandIPv6($ip);
}
/**
* The method checks whether the IP address with specified CIDR is allowed according to the [[ranges]] list.
*
* @param string $ip
* @param int $cidr
* @return bool
* @see ranges
*/
private function isAllowed($ip, $cidr)
{
if (empty($this->ranges)) {
return true;
}
foreach ($this->ranges as $string) {
list($isNegated, $range) = $this->parseNegatedRange($string);
if ($this->inRange($ip, $cidr, $range)) {
return !$isNegated;
}
}
return false;
}
/**
* Parses IP address/range for the negation with [[NEGATION_CHAR]].
*
* @param $string
* @return array `[0 => bool, 1 => string]`
* - boolean: whether the string is negated
* - string: the string without negation (when the negation were present)
*/
private function parseNegatedRange($string)
{
$isNegated = strpos($string, static::NEGATION_CHAR) === 0;
return [$isNegated, $isNegated ? substr($string, strlen(static::NEGATION_CHAR)) : $string];
}
/**
* Prepares array to fill in [[ranges]].
*
* - Recursively substitutes aliases, described in [[networks]] with their values,
* - Removes duplicates.
*
* @param $ranges
* @return array
* @see networks
*/
private function prepareRanges($ranges)
{
$result = [];
foreach ($ranges as $string) {
list($isRangeNegated, $range) = $this->parseNegatedRange($string);
if (isset($this->networks[$range])) {
$replacements = $this->prepareRanges($this->networks[$range]);
foreach ($replacements as &$replacement) {
list($isReplacementNegated, $replacement) = $this->parseNegatedRange($replacement);
$result[] = ($isRangeNegated && !$isReplacementNegated ? static::NEGATION_CHAR : '') . $replacement;
}
} else {
$result[] = $string;
}
}
return array_unique($result);
}
/**
* Validates IPv4 address.
*
* @param string $value
* @return bool
*/
protected function validateIPv4($value)
{
return preg_match($this->ipv4Pattern, $value) !== 0;
}
/**
* Validates IPv6 address.
*
* @param string $value
* @return bool
*/
protected function validateIPv6($value)
{
return preg_match($this->ipv6Pattern, $value) !== 0;
}
/**
* Gets the IP version.
*
* @param string $ip
* @return int
*/
private function getIpVersion($ip)
{
return IpHelper::getIpVersion($ip);
}
/**
* Used to get the Regexp pattern for initial IP address parsing.
* @return string
*/
private function getIpParsePattern()
{
return '/^(' . preg_quote(static::NEGATION_CHAR, '/') . '?)(.+?)(\/(\d+))?$/';
}
/**
* Checks whether the IP is in subnet range.
*
* @param string $ip an IPv4 or IPv6 address
* @param int $cidr
* @param string $range subnet in CIDR format e.g. `10.0.0.0/8` or `2001:af::/64`
* @return bool
*/
private function inRange($ip, $cidr, $range)
{
return IpHelper::inRange($ip . '/' . $cidr, $range);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$messages = [
'ipv6NotAllowed' => $this->ipv6NotAllowed,
'ipv4NotAllowed' => $this->ipv4NotAllowed,
'message' => $this->message,
'noSubnet' => $this->noSubnet,
'hasSubnet' => $this->hasSubnet,
];
foreach ($messages as &$message) {
$message = $this->formatMessage($message, [
'attribute' => $model->getAttributeLabel($attribute),
]);
}
$options = [
'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv4Pattern)),
'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv6Pattern)),
'messages' => $messages,
'ipv4' => (bool) $this->ipv4,
'ipv6' => (bool) $this->ipv6,
'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())),
'negation' => $this->negation,
'subnet' => $this->subnet,
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,180 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\helpers\Json;
use yii\helpers\StringHelper;
use yii\web\JsExpression;
/**
* NumberValidator validates that the attribute value is a number.
*
* The format of the number must match the regular expression specified in [[integerPattern]] or [[numberPattern]].
* Optionally, you may configure the [[max]] and [[min]] properties to ensure the number
* is within certain range.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class NumberValidator extends Validator
{
/**
* @var bool whether the attribute value can only be an integer. Defaults to false.
*/
public $integerOnly = false;
/**
* @var int|float upper limit of the number. Defaults to null, meaning no upper limit.
* @see tooBig for the customized message used when the number is too big.
*/
public $max;
/**
* @var int|float lower limit of the number. Defaults to null, meaning no lower limit.
* @see tooSmall for the customized message used when the number is too small.
*/
public $min;
/**
* @var string user-defined error message used when the value is bigger than [[max]].
*/
public $tooBig;
/**
* @var string user-defined error message used when the value is smaller than [[min]].
*/
public $tooSmall;
/**
* @var string the regular expression for matching integers.
*/
public $integerPattern = '/^\s*[+-]?\d+\s*$/';
/**
* @var string the regular expression for matching numbers. It defaults to a pattern
* that matches floating numbers with optional exponential part (e.g. -1.23e-10).
*/
public $numberPattern = '/^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$/';
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.')
: Yii::t('yii', '{attribute} must be a number.');
}
if ($this->min !== null && $this->tooSmall === null) {
$this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.');
}
if ($this->max !== null && $this->tooBig === null) {
$this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if ($this->isNotNumber($value)) {
$this->addError($model, $attribute, $this->message);
return;
}
$pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($pattern, StringHelper::normalizeNumber($value))) {
$this->addError($model, $attribute, $this->message);
}
if ($this->min !== null && $value < $this->min) {
$this->addError($model, $attribute, $this->tooSmall, ['min' => $this->min]);
}
if ($this->max !== null && $value > $this->max) {
$this->addError($model, $attribute, $this->tooBig, ['max' => $this->max]);
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if ($this->isNotNumber($value)) {
return [Yii::t('yii', '{attribute} is invalid.'), []];
}
$pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($pattern, StringHelper::normalizeNumber($value))) {
return [$this->message, []];
} elseif ($this->min !== null && $value < $this->min) {
return [$this->tooSmall, ['min' => $this->min]];
} elseif ($this->max !== null && $value > $this->max) {
return [$this->tooBig, ['max' => $this->max]];
}
return null;
}
/*
* @param mixed $value the data value to be checked.
*/
private function isNotNumber($value)
{
return is_array($value)
|| (is_object($value) && !method_exists($value, '__toString'))
|| (!is_object($value) && !is_scalar($value) && $value !== null);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);
$options = [
'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern),
'message' => $this->formatMessage($this->message, [
'attribute' => $label,
]),
];
if ($this->min !== null) {
// ensure numeric value to make javascript comparison equal to PHP comparison
// https://github.com/yiisoft/yii2/issues/3118
$options['min'] = is_string($this->min) ? (float) $this->min : $this->min;
$options['tooSmall'] = $this->formatMessage($this->tooSmall, [
'attribute' => $label,
'min' => $this->min,
]);
}
if ($this->max !== null) {
// ensure numeric value to make javascript comparison equal to PHP comparison
// https://github.com/yiisoft/yii2/issues/3118
$options['max'] = is_string($this->max) ? (float) $this->max : $this->max;
$options['tooBig'] = $this->formatMessage($this->tooBig, [
'attribute' => $label,
'max' => $this->max,
]);
}
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use yii\web\AssetBundle;
/**
* This asset bundle provides the javascript files needed for the [[EmailValidator]]s client validation.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class PunycodeAsset extends AssetBundle
{
public $sourcePath = '@bower/punycode';
public $js = [
'punycode.js',
];
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
* RangeValidator validates that the attribute value is among a list of values.
*
* The range can be specified via the [[range]] property.
* If the [[not]] property is set true, the validator will ensure the attribute value
* is NOT among the specified range.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RangeValidator extends Validator
{
/**
* @var array|\Traversable|\Closure a list of valid values that the attribute value should be among or an anonymous function that returns
* such a list. The signature of the anonymous function should be as follows,
*
* ```php
* function($model, $attribute) {
* // compute range
* return $range;
* }
* ```
*/
public $range;
/**
* @var bool whether the comparison is strict (both type and value must be the same)
*/
public $strict = false;
/**
* @var bool whether to invert the validation logic. Defaults to false. If set to true,
* the attribute value should NOT be among the list of values defined via [[range]].
*/
public $not = false;
/**
* @var bool whether to allow array type attribute.
*/
public $allowArray = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if (!is_array($this->range)
&& !($this->range instanceof \Closure)
&& !($this->range instanceof \Traversable)
) {
throw new InvalidConfigException('The "range" property must be set.');
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
$in = false;
if ($this->allowArray
&& ($value instanceof \Traversable || is_array($value))
&& ArrayHelper::isSubset($value, $this->range, $this->strict)
) {
$in = true;
}
if (!$in && ArrayHelper::isIn($value, $this->range, $this->strict)) {
$in = true;
}
return $this->not !== $in ? null : [$this->message, []];
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
if ($this->range instanceof \Closure) {
$this->range = call_user_func($this->range, $model, $attribute);
}
parent::validateAttribute($model, $attribute);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
if ($this->range instanceof \Closure) {
$this->range = call_user_func($this->range, $model, $attribute);
}
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$range = [];
foreach ($this->range as $value) {
$range[] = (string) $value;
}
$options = [
'range' => $range,
'not' => $this->not,
'message' => $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
]),
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
if ($this->allowArray) {
$options['allowArray'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* RegularExpressionValidator validates that the attribute value matches the specified [[pattern]].
*
* If the [[not]] property is set true, the validator will ensure the attribute value do NOT match the [[pattern]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RegularExpressionValidator extends Validator
{
/**
* @var string the regular expression to be matched with
*/
public $pattern;
/**
* @var bool whether to invert the validation logic. Defaults to false. If set to true,
* the regular expression defined via [[pattern]] should NOT match the attribute value.
*/
public $not = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->pattern === null) {
throw new InvalidConfigException('The "pattern" property must be set.');
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
$valid = !is_array($value) &&
(!$this->not && preg_match($this->pattern, $value)
|| $this->not && !preg_match($this->pattern, $value));
return $valid ? null : [$this->message, []];
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$pattern = Html::escapeJsRegularExpression($this->pattern);
$options = [
'pattern' => new JsExpression($pattern),
'not' => $this->not,
'message' => $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
]),
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,121 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
/**
* RequiredValidator validates that the specified attribute does not have null or empty value.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class RequiredValidator extends Validator
{
/**
* @var bool whether to skip this validator if the value being validated is empty.
*/
public $skipOnEmpty = false;
/**
* @var mixed the desired value that the attribute must have.
* If this is null, the validator will validate that the specified attribute is not empty.
* If this is set as a value that is not null, the validator will validate that
* the attribute has a value that is the same as this property value.
* Defaults to null.
* @see strict
*/
public $requiredValue;
/**
* @var bool whether the comparison between the attribute value and [[requiredValue]] is strict.
* When this is true, both the values and types must match.
* Defaults to false, meaning only the values need to match.
* Note that when [[requiredValue]] is null, if this property is true, the validator will check
* if the attribute value is null; If this property is false, the validator will call [[isEmpty]]
* to check if the attribute value is empty.
*/
public $strict = false;
/**
* @var string the user-defined error message. It may contain the following placeholders which
* will be replaced accordingly by the validator:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
* - `{requiredValue}`: the value of [[requiredValue]]
*/
public $message;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.')
: Yii::t('yii', '{attribute} must be "{requiredValue}".');
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if ($this->requiredValue === null) {
if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty(is_string($value) ? trim($value) : $value)) {
return null;
}
} elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
return null;
}
if ($this->requiredValue === null) {
return [$this->message, []];
}
return [$this->message, [
'requiredValue' => $this->requiredValue,
]];
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$options = [];
if ($this->requiredValue !== null) {
$options['message'] = $this->formatMessage($this->message, [
'requiredValue' => $this->requiredValue,
]);
$options['requiredValue'] = $this->requiredValue;
} else {
$options['message'] = $this->message;
}
if ($this->strict) {
$options['strict'] = 1;
}
$options['message'] = $this->formatMessage($options['message'], [
'attribute' => $model->getAttributeLabel($attribute),
]);
return $options;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
/**
* SafeValidator serves as a dummy validator whose main purpose is to mark the attributes to be safe for massive assignment.
*
* This class is required because of the way in which Yii determines whether a property is safe for massive assignment, that is,
* when a user submits form data to be loaded into a model directly from the POST data, is it ok for a property to be copied.
* In many cases, this is required but because sometimes properties are internal and you do not want the POST data to be able to
* override these internal values (especially things like database row ids), Yii assumes all values are unsafe for massive assignment
* unless a validation rule exists for the property, which in most cases it will. Sometimes, however, an item is safe for massive assigment but
* does not have a validation rule associated with it - for instance, due to no validation being performed, in which case, you use this class
* as a validation rule for that property. Although it has no functionality, it allows Yii to determine that the property is safe to copy.
*
* > Note: [[when]] property is not supported by SafeValidator.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class SafeValidator extends Validator
{
/**
* {@inheritdoc}
*/
public function validateAttributes($model, $attributes = null)
{
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
}
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
/**
* StringValidator validates that the attribute value is of certain length.
*
* Note, this validator should only be used with string-typed attributes.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class StringValidator extends Validator
{
/**
* @var int|array specifies the length limit of the value to be validated.
* This can be specified in one of the following forms:
*
* - an integer: the exact length that the value should be of;
* - an array of one element: the minimum length that the value should be of. For example, `[8]`.
* This will overwrite [[min]].
* - an array of two elements: the minimum and maximum lengths that the value should be of.
* For example, `[8, 128]`. This will overwrite both [[min]] and [[max]].
* @see tooShort for the customized message for a too short string.
* @see tooLong for the customized message for a too long string.
* @see notEqual for the customized message for a string that does not match desired length.
*/
public $length;
/**
* @var int maximum length. If not set, it means no maximum length limit.
* @see tooLong for the customized message for a too long string.
*/
public $max;
/**
* @var int minimum length. If not set, it means no minimum length limit.
* @see tooShort for the customized message for a too short string.
*/
public $min;
/**
* @var string user-defined error message used when the value is not a string.
*/
public $message;
/**
* @var string user-defined error message used when the length of the value is smaller than [[min]].
*/
public $tooShort;
/**
* @var string user-defined error message used when the length of the value is greater than [[max]].
*/
public $tooLong;
/**
* @var string user-defined error message used when the length of the value is not equal to [[length]].
*/
public $notEqual;
/**
* @var string the encoding of the string value to be validated (e.g. 'UTF-8').
* If this property is not set, [[\yii\base\Application::charset]] will be used.
*/
public $encoding;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if (is_array($this->length)) {
if (isset($this->length[0])) {
$this->min = $this->length[0];
}
if (isset($this->length[1])) {
$this->max = $this->length[1];
}
$this->length = null;
}
if ($this->encoding === null) {
$this->encoding = Yii::$app ? Yii::$app->charset : 'UTF-8';
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} must be a string.');
}
if ($this->min !== null && $this->tooShort === null) {
$this->tooShort = Yii::t('yii', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.');
}
if ($this->max !== null && $this->tooLong === null) {
$this->tooLong = Yii::t('yii', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.');
}
if ($this->length !== null && $this->notEqual === null) {
$this->notEqual = Yii::t('yii', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
if (!is_string($value)) {
$this->addError($model, $attribute, $this->message);
return;
}
$length = mb_strlen($value, $this->encoding);
if ($this->min !== null && $length < $this->min) {
$this->addError($model, $attribute, $this->tooShort, ['min' => $this->min]);
}
if ($this->max !== null && $length > $this->max) {
$this->addError($model, $attribute, $this->tooLong, ['max' => $this->max]);
}
if ($this->length !== null && $length !== $this->length) {
$this->addError($model, $attribute, $this->notEqual, ['length' => $this->length]);
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
if (!is_string($value)) {
return [$this->message, []];
}
$length = mb_strlen($value, $this->encoding);
if ($this->min !== null && $length < $this->min) {
return [$this->tooShort, ['min' => $this->min]];
}
if ($this->max !== null && $length > $this->max) {
return [$this->tooLong, ['max' => $this->max]];
}
if ($this->length !== null && $length !== $this->length) {
return [$this->notEqual, ['length' => $this->length]];
}
return null;
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);
$options = [
'message' => $this->formatMessage($this->message, [
'attribute' => $label,
]),
];
if ($this->min !== null) {
$options['min'] = $this->min;
$options['tooShort'] = $this->formatMessage($this->tooShort, [
'attribute' => $label,
'min' => $this->min,
]);
}
if ($this->max !== null) {
$options['max'] = $this->max;
$options['tooLong'] = $this->formatMessage($this->tooLong, [
'attribute' => $label,
'max' => $this->max,
]);
}
if ($this->length !== null) {
$options['is'] = $this->length;
$options['notEqual'] = $this->formatMessage($this->notEqual, [
'attribute' => $label,
'length' => $this->length,
]);
}
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
return $options;
}
}

View File

@@ -0,0 +1,328 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\Model;
use yii\db\ActiveQuery;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveRecord;
use yii\db\ActiveRecordInterface;
use yii\helpers\Inflector;
/**
* UniqueValidator validates that the attribute value is unique in the specified database table.
*
* UniqueValidator checks if the value being validated is unique in the table column specified by
* the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
*
* The following are examples of validation rules using this validator:
*
* ```php
* // a1 needs to be unique
* ['a1', 'unique']
* // a1 needs to be unique, but column a2 will be used to check the uniqueness of the a1 value
* ['a1', 'unique', 'targetAttribute' => 'a2']
* // a1 and a2 need to be unique together, and they both will receive error message
* [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']]
* // a1 and a2 need to be unique together, only a1 will receive error message
* ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']]
* // a1 needs to be unique by checking the uniqueness of both a2 and a3 (using a1 value)
* ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']]
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class UniqueValidator extends Validator
{
/**
* @var string the name of the ActiveRecord class that should be used to validate the uniqueness
* of the current attribute value. If not set, it will use the ActiveRecord class of the attribute being validated.
* @see targetAttribute
*/
public $targetClass;
/**
* @var string|array the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that should be used to
* validate the uniqueness of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the uniqueness
* of multiple columns at the same time. The array values are the attributes that will be
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
*/
public $targetAttribute;
/**
* @var string|array|\Closure additional filter to be applied to the DB query used to check the uniqueness of the attribute value.
* This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]]
* on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query`
* is the [[\yii\db\Query|Query]] object that you can modify in the function.
*/
public $filter;
/**
* @var string the user-defined error message.
*
* When validating single attribute, it may contain
* the following placeholders which will be replaced accordingly by the validator:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* When validating mutliple attributes, it may contain the following placeholders:
*
* - `{attributes}`: the labels of the attributes being validated.
* - `{values}`: the values of the attributes being validated.
*/
public $message;
/**
* @var string
* @since 2.0.9
* @deprecated since version 2.0.10, to be removed in 2.1. Use [[message]] property
* to setup custom message for multiple target attributes.
*/
public $comboNotUnique;
/**
* @var string and|or define how target attributes are related
* @since 2.0.11
*/
public $targetAttributeJunction = 'and';
/**
* @var bool whether this validator is forced to always use master DB
* @since 2.0.14
*/
public $forceMasterDb = true;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->message !== null) {
return;
}
if (is_array($this->targetAttribute) && count($this->targetAttribute) > 1) {
// fallback for deprecated `comboNotUnique` property - use it as message if is set
if ($this->comboNotUnique === null) {
$this->message = Yii::t('yii', 'The combination {values} of {attributes} has already been taken.');
} else {
$this->message = $this->comboNotUnique;
}
} else {
$this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
/* @var $targetClass ActiveRecordInterface */
$targetClass = $this->getTargetClass($model);
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
$rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions = [$this->targetAttributeJunction === 'or' ? 'or' : 'and'];
foreach ($rawConditions as $key => $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
$conditions[] = [$key => $value];
}
$db = $targetClass::getDb();
$modelExists = false;
if ($this->forceMasterDb && method_exists($db, 'useMaster')) {
$db->useMaster(function () use ($targetClass, $conditions, $model, &$modelExists) {
$modelExists = $this->modelExists($targetClass, $conditions, $model);
});
} else {
$modelExists = $this->modelExists($targetClass, $conditions, $model);
}
if ($modelExists) {
if (is_array($targetAttribute) && count($targetAttribute) > 1) {
$this->addComboNotUniqueError($model, $attribute);
} else {
$this->addError($model, $attribute, $this->message);
}
}
}
/**
* @param Model $model the data model to be validated
* @return string Target class name
*/
private function getTargetClass($model)
{
return $this->targetClass === null ? get_class($model) : $this->targetClass;
}
/**
* Checks whether the $model exists in the database.
*
* @param string $targetClass the name of the ActiveRecord class that should be used to validate the uniqueness
* of the current attribute value.
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
* @param Model $model the data model to be validated
*
* @return bool whether the model already exists
*/
private function modelExists($targetClass, $conditions, $model)
{
/** @var ActiveRecordInterface $targetClass $query */
$query = $this->prepareQuery($targetClass, $conditions);
if (!$model instanceof ActiveRecordInterface || $model->getIsNewRecord() || $model->className() !== $targetClass::className()) {
// if current $model isn't in the database yet then it's OK just to call exists()
// also there's no need to run check based on primary keys, when $targetClass is not the same as $model's class
$exists = $query->exists();
} else {
// if current $model is in the database already we can't use exists()
if ($query instanceof \yii\db\ActiveQuery) {
// only select primary key to optimize query
$columnsCondition = array_flip($targetClass::primaryKey());
$query->select(array_flip($this->applyTableAlias($query, $columnsCondition)));
// any with relation can't be loaded because related fields are not selected
$query->with = null;
}
$models = $query->limit(2)->asArray()->all();
$n = count($models);
if ($n === 1) {
// if there is one record, check if it is the currently validated model
$dbModel = reset($models);
$pks = $targetClass::primaryKey();
$pk = [];
foreach ($pks as $pkAttribute) {
$pk[$pkAttribute] = $dbModel[$pkAttribute];
}
$exists = ($pk != $model->getOldPrimaryKey(true));
} else {
// if there is more than one record, the value is not unique
$exists = $n > 1;
}
}
return $exists;
}
/**
* Prepares a query by applying filtering conditions defined in $conditions method property
* and [[filter]] class property.
*
* @param ActiveRecordInterface $targetClass the name of the ActiveRecord class that should be used to validate
* the uniqueness of the current attribute value.
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format
*
* @return ActiveQueryInterface|ActiveQuery
*/
private function prepareQuery($targetClass, $conditions)
{
$query = $targetClass::find();
$query->andWhere($conditions);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
$query->andWhere($this->filter);
}
return $query;
}
/**
* Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
* [[\yii\db\Query::where()|Query::where()]] key-value format.
*
* @param string|array $targetAttribute the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that
* should be used to validate the uniqueness of the current attribute value. You may use an array to validate
* the uniqueness of multiple columns at the same time. The array values are the attributes that will be
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
* @param Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated in the $model
*
* @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
*/
private function prepareConditions($targetAttribute, $model, $attribute)
{
if (is_array($targetAttribute)) {
$conditions = [];
foreach ($targetAttribute as $k => $v) {
$conditions[$v] = is_int($k) ? $model->$v : $model->$k;
}
} else {
$conditions = [$targetAttribute => $model->$attribute];
}
$targetModelClass = $this->getTargetClass($model);
if (!is_subclass_of($targetModelClass, 'yii\db\ActiveRecord')) {
return $conditions;
}
/** @var ActiveRecord $targetModelClass */
return $this->applyTableAlias($targetModelClass::find(), $conditions);
}
/**
* Builds and adds [[comboNotUnique]] error message to the specified model attribute.
* @param \yii\base\Model $model the data model.
* @param string $attribute the name of the attribute.
*/
private function addComboNotUniqueError($model, $attribute)
{
$attributeCombo = [];
$valueCombo = [];
foreach ($this->targetAttribute as $key => $value) {
if (is_int($key)) {
$attributeCombo[] = $model->getAttributeLabel($value);
$valueCombo[] = '"' . $model->$value . '"';
} else {
$attributeCombo[] = $model->getAttributeLabel($key);
$valueCombo[] = '"' . $model->$key . '"';
}
}
$this->addError($model, $attribute, $this->message, [
'attributes' => Inflector::sentence($attributeCombo),
'values' => implode('-', $valueCombo),
]);
}
/**
* Returns conditions with alias.
* @param ActiveQuery $query
* @param array $conditions array of condition, keys to be modified
* @param null|string $alias set empty string for no apply alias. Set null for apply primary table alias
* @return array
*/
private function applyTableAlias($query, $conditions, $alias = null)
{
if ($alias === null) {
$alias = array_keys($query->getTablesUsedInFrom())[0];
}
$prefixedConditions = [];
foreach ($conditions as $columnName => $columnValue) {
if (strpos($columnName, '(') === false) {
$prefixedColumn = "{$alias}.[[" . preg_replace(
'/^' . preg_quote($alias) . '\.(.*)$/',
'$1',
$columnName) . ']]';
} else {
// there is an expression, can't prefix it reliably
$prefixedColumn = $columnName;
}
$prefixedConditions[$prefixedColumn] = $columnValue;
}
return $prefixedConditions;
}
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Json;
use yii\web\JsExpression;
/**
* UrlValidator validates that the attribute value is a valid http or https URL.
*
* Note that this validator only checks if the URL scheme and host part are correct.
* It does not check the remaining parts of a URL.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class UrlValidator extends Validator
{
/**
* @var string the regular expression used to validate the attribute value.
* The pattern may contain a `{schemes}` token that will be replaced
* by a regular expression which represents the [[validSchemes]].
*/
public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(?::\d{1,5})?(?:$|[?\/#])/i';
/**
* @var array list of URI schemes which should be considered valid. By default, http and https
* are considered to be valid schemes.
*/
public $validSchemes = ['http', 'https'];
/**
* @var string the default URI scheme. If the input doesn't contain the scheme part, the default
* scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
* contain the scheme part.
*/
public $defaultScheme;
/**
* @var bool whether validation process should take into account IDN (internationalized
* domain names). Defaults to false meaning that validation of URLs containing IDN will always
* fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
* extension, otherwise an exception would be thrown.
*/
public $enableIDN = false;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->enableIDN && !function_exists('idn_to_ascii')) {
throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
}
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is not a valid URL.');
}
}
/**
* {@inheritdoc}
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
$result = $this->validateValue($value);
if (!empty($result)) {
$this->addError($model, $attribute, $result[0], $result[1]);
} elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
$model->$attribute = $this->defaultScheme . '://' . $value;
}
}
/**
* {@inheritdoc}
*/
protected function validateValue($value)
{
// make sure the length is limited to avoid DOS attacks
if (is_string($value) && strlen($value) < 2000) {
if ($this->defaultScheme !== null && strpos($value, '://') === false) {
$value = $this->defaultScheme . '://' . $value;
}
if (strpos($this->pattern, '{schemes}') !== false) {
$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
} else {
$pattern = $this->pattern;
}
if ($this->enableIDN) {
$value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
return '://' . $this->idnToAscii($matches[1]);
}, $value);
}
if (preg_match($pattern, $value)) {
return null;
}
}
return [$this->message, []];
}
private function idnToAscii($idn)
{
if (PHP_VERSION_ID < 50600) {
// TODO: drop old PHP versions support
return idn_to_ascii($idn);
}
return idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
}
/**
* {@inheritdoc}
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* {@inheritdoc}
*/
public function getClientOptions($model, $attribute)
{
if (strpos($this->pattern, '{schemes}') !== false) {
$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
} else {
$pattern = $this->pattern;
}
$options = [
'pattern' => new JsExpression($pattern),
'message' => $this->formatMessage($this->message, [
'attribute' => $model->getAttributeLabel($attribute),
]),
'enableIDN' => (bool) $this->enableIDN,
];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
if ($this->defaultScheme !== null) {
$options['defaultScheme'] = $this->defaultScheme;
}
return $options;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use yii\web\AssetBundle;
/**
* This asset bundle provides the javascript files for client validation.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ValidationAsset extends AssetBundle
{
public $sourcePath = '@yii/assets';
public $js = [
'yii.validation.js',
];
public $depends = [
'yii\web\YiiAsset',
];
}

View File

@@ -0,0 +1,482 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use Yii;
use yii\base\Component;
use yii\base\NotSupportedException;
/**
* Validator is the base class for all validators.
*
* Child classes should override the [[validateValue()]] and/or [[validateAttribute()]] methods to provide the actual
* logic of performing data validation. Child classes may also override [[clientValidateAttribute()]]
* to provide client-side validation support.
*
* Validator declares a set of [[builtInValidators|built-in validators]] which can
* be referenced using short names. They are listed as follows:
*
* - `boolean`: [[BooleanValidator]]
* - `captcha`: [[\yii\captcha\CaptchaValidator]]
* - `compare`: [[CompareValidator]]
* - `date`: [[DateValidator]]
* - `datetime`: [[DateValidator]]
* - `time`: [[DateValidator]]
* - `default`: [[DefaultValueValidator]]
* - `double`: [[NumberValidator]]
* - `each`: [[EachValidator]]
* - `email`: [[EmailValidator]]
* - `exist`: [[ExistValidator]]
* - `file`: [[FileValidator]]
* - `filter`: [[FilterValidator]]
* - `image`: [[ImageValidator]]
* - `in`: [[RangeValidator]]
* - `integer`: [[NumberValidator]]
* - `match`: [[RegularExpressionValidator]]
* - `required`: [[RequiredValidator]]
* - `safe`: [[SafeValidator]]
* - `string`: [[StringValidator]]
* - `trim`: [[FilterValidator]]
* - `unique`: [[UniqueValidator]]
* - `url`: [[UrlValidator]]
* - `ip`: [[IpValidator]]
*
* For more details and usage information on Validator, see the [guide article on validators](guide:input-validation).
*
* @property array $attributeNames Attribute names. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Validator extends Component
{
/**
* @var array list of built-in validators (name => class or configuration)
*/
public static $builtInValidators = [
'boolean' => 'yii\validators\BooleanValidator',
'captcha' => 'yii\captcha\CaptchaValidator',
'compare' => 'yii\validators\CompareValidator',
'date' => 'yii\validators\DateValidator',
'datetime' => [
'class' => 'yii\validators\DateValidator',
'type' => DateValidator::TYPE_DATETIME,
],
'time' => [
'class' => 'yii\validators\DateValidator',
'type' => DateValidator::TYPE_TIME,
],
'default' => 'yii\validators\DefaultValueValidator',
'double' => 'yii\validators\NumberValidator',
'each' => 'yii\validators\EachValidator',
'email' => 'yii\validators\EmailValidator',
'exist' => 'yii\validators\ExistValidator',
'file' => 'yii\validators\FileValidator',
'filter' => 'yii\validators\FilterValidator',
'image' => 'yii\validators\ImageValidator',
'in' => 'yii\validators\RangeValidator',
'integer' => [
'class' => 'yii\validators\NumberValidator',
'integerOnly' => true,
],
'match' => 'yii\validators\RegularExpressionValidator',
'number' => 'yii\validators\NumberValidator',
'required' => 'yii\validators\RequiredValidator',
'safe' => 'yii\validators\SafeValidator',
'string' => 'yii\validators\StringValidator',
'trim' => [
'class' => 'yii\validators\FilterValidator',
'filter' => 'trim',
'skipOnArray' => true,
],
'unique' => 'yii\validators\UniqueValidator',
'url' => 'yii\validators\UrlValidator',
'ip' => 'yii\validators\IpValidator',
];
/**
* @var array|string attributes to be validated by this validator. For multiple attributes,
* please specify them as an array; for single attribute, you may use either a string or an array.
*/
public $attributes = [];
/**
* @var string the user-defined error message. It may contain the following placeholders which
* will be replaced accordingly by the validator:
*
* - `{attribute}`: the label of the attribute being validated
* - `{value}`: the value of the attribute being validated
*
* Note that some validators may introduce other properties for error messages used when specific
* validation conditions are not met. Please refer to individual class API documentation for details
* about these properties. By convention, this property represents the primary error message
* used when the most important validation condition is not met.
*/
public $message;
/**
* @var array|string scenarios that the validator can be applied to. For multiple scenarios,
* please specify them as an array; for single scenario, you may use either a string or an array.
*/
public $on = [];
/**
* @var array|string scenarios that the validator should not be applied to. For multiple scenarios,
* please specify them as an array; for single scenario, you may use either a string or an array.
*/
public $except = [];
/**
* @var bool whether this validation rule should be skipped if the attribute being validated
* already has some validation error according to some previous rules. Defaults to true.
*/
public $skipOnError = true;
/**
* @var bool whether this validation rule should be skipped if the attribute value
* is null or an empty string.
*/
public $skipOnEmpty = true;
/**
* @var bool whether to enable client-side validation for this validator.
* The actual client-side validation is done via the JavaScript code returned
* by [[clientValidateAttribute()]]. If that method returns null, even if this property
* is true, no client-side validation will be done by this validator.
*/
public $enableClientValidation = true;
/**
* @var callable a PHP callable that replaces the default implementation of [[isEmpty()]].
* If not set, [[isEmpty()]] will be used to check if a value is empty. The signature
* of the callable should be `function ($value)` which returns a boolean indicating
* whether the value is empty.
*/
public $isEmpty;
/**
* @var callable a PHP callable whose return value determines whether this validator should be applied.
* The signature of the callable should be `function ($model, $attribute)`, where `$model` and `$attribute`
* refer to the model and the attribute currently being validated. The callable should return a boolean value.
*
* This property is mainly provided to support conditional validation on the server-side.
* If this property is not set, this validator will be always applied on the server-side.
*
* The following example will enable the validator only when the country currently selected is USA:
*
* ```php
* function ($model) {
* return $model->country == Country::USA;
* }
* ```
*
* @see whenClient
*/
public $when;
/**
* @var string a JavaScript function name whose return value determines whether this validator should be applied
* on the client-side. The signature of the function should be `function (attribute, value)`, where
* `attribute` is an object describing the attribute being validated (see [[clientValidateAttribute()]])
* and `value` the current value of the attribute.
*
* This property is mainly provided to support conditional validation on the client-side.
* If this property is not set, this validator will be always applied on the client-side.
*
* The following example will enable the validator only when the country currently selected is USA:
*
* ```javascript
* function (attribute, value) {
* return $('#country').val() === 'USA';
* }
* ```
*
* @see when
*/
public $whenClient;
/**
* Creates a validator object.
* @param string|\Closure $type the validator type. This can be either:
* * a built-in validator name listed in [[builtInValidators]];
* * a method name of the model class;
* * an anonymous function;
* * a validator class name.
* @param \yii\base\Model $model the data model to be validated.
* @param array|string $attributes list of attributes to be validated. This can be either an array of
* the attribute names or a string of comma-separated attribute names.
* @param array $params initial values to be applied to the validator properties.
* @return Validator the validator
*/
public static function createValidator($type, $model, $attributes, $params = [])
{
$params['attributes'] = $attributes;
if ($type instanceof \Closure || ($model->hasMethod($type) && !isset(static::$builtInValidators[$type]))) {
// method-based validator
$params['class'] = __NAMESPACE__ . '\InlineValidator';
$params['method'] = $type;
} else {
if (isset(static::$builtInValidators[$type])) {
$type = static::$builtInValidators[$type];
}
if (is_array($type)) {
$params = array_merge($type, $params);
} else {
$params['class'] = $type;
}
}
return Yii::createObject($params);
}
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
$this->attributes = (array) $this->attributes;
$this->on = (array) $this->on;
$this->except = (array) $this->except;
}
/**
* Validates the specified object.
* @param \yii\base\Model $model the data model being validated
* @param array|null $attributes the list of attributes to be validated.
* Note that if an attribute is not associated with the validator - it will be
* ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated.
*/
public function validateAttributes($model, $attributes = null)
{
if (is_array($attributes)) {
$newAttributes = [];
$attributeNames = $this->getAttributeNames();
foreach ($attributes as $attribute) {
if (in_array($attribute, $attributeNames, true)) {
$newAttributes[] = $attribute;
}
}
$attributes = $newAttributes;
} else {
$attributes = $this->getAttributeNames();
}
foreach ($attributes as $attribute) {
$skip = $this->skipOnError && $model->hasErrors($attribute)
|| $this->skipOnEmpty && $this->isEmpty($model->$attribute);
if (!$skip) {
if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
$this->validateAttribute($model, $attribute);
}
}
}
}
/**
* Validates a single attribute.
* Child classes must implement this method to provide the actual validation logic.
* @param \yii\base\Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated.
*/
public function validateAttribute($model, $attribute)
{
$result = $this->validateValue($model->$attribute);
if (!empty($result)) {
$this->addError($model, $attribute, $result[0], $result[1]);
}
}
/**
* Validates a given value.
* You may use this method to validate a value out of the context of a data model.
* @param mixed $value the data value to be validated.
* @param string $error the error message to be returned, if the validation fails.
* @return bool whether the data is valid.
*/
public function validate($value, &$error = null)
{
$result = $this->validateValue($value);
if (empty($result)) {
return true;
}
list($message, $params) = $result;
$params['attribute'] = Yii::t('yii', 'the input value');
if (is_array($value)) {
$params['value'] = 'array()';
} elseif (is_object($value)) {
$params['value'] = 'object';
} else {
$params['value'] = $value;
}
$error = $this->formatMessage($message, $params);
return false;
}
/**
* Validates a value.
* A validator class can implement this method to support data validation out of the context of a data model.
* @param mixed $value the data value to be validated.
* @return array|null the error message and the array of parameters to be inserted into the error message.
* ```php
* if (!$valid) {
* return [$this->message, [
* 'param1' => $this->param1,
* 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
* 'mimeTypes' => implode(', ', $this->mimeTypes),
* 'param4' => 'etc...',
* ]];
* }
*
* return null;
* ```
* for this example `message` template can contain `{param1}`, `{formattedLimit}`, `{mimeTypes}`, `{param4}`
*
* Null should be returned if the data is valid.
* @throws NotSupportedException if the validator does not supporting data validation without a model
*/
protected function validateValue($value)
{
throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
}
/**
* Returns the JavaScript needed for performing client-side validation.
*
* Calls [[getClientOptions()]] to generate options array for client-side validation.
*
* You may override this method to return the JavaScript validation code if
* the validator can support client-side validation.
*
* The following JavaScript variables are predefined and can be used in the validation code:
*
* - `attribute`: an object describing the the attribute being validated.
* - `value`: the value being validated.
* - `messages`: an array used to hold the validation error messages for the attribute.
* - `deferred`: an array used to hold deferred objects for asynchronous validation
* - `$form`: a jQuery object containing the form element
*
* The `attribute` object contains the following properties:
* - `id`: a unique ID identifying the attribute (e.g. "loginform-username") in the form
* - `name`: attribute name or expression (e.g. "[0]content" for tabular input)
* - `container`: the jQuery selector of the container of the input field
* - `input`: the jQuery selector of the input field under the context of the form
* - `error`: the jQuery selector of the error tag under the context of the container
* - `status`: status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
*
* @param \yii\base\Model $model the data model being validated
* @param string $attribute the name of the attribute to be validated.
* @param \yii\web\View $view the view object that is going to be used to render views or view files
* containing a model form with this validator applied.
* @return string|null the client-side validation script. Null if the validator does not support
* client-side validation.
* @see getClientOptions()
* @see \yii\widgets\ActiveForm::enableClientValidation
*/
public function clientValidateAttribute($model, $attribute, $view)
{
return null;
}
/**
* Returns the client-side validation options.
* This method is usually called from [[clientValidateAttribute()]]. You may override this method to modify options
* that will be passed to the client-side validation.
* @param \yii\base\Model $model the model being validated
* @param string $attribute the attribute name being validated
* @return array the client-side validation options
* @since 2.0.11
*/
public function getClientOptions($model, $attribute)
{
return [];
}
/**
* Returns a value indicating whether the validator is active for the given scenario and attribute.
*
* A validator is active if
*
* - the validator's `on` property is empty, or
* - the validator's `on` property contains the specified scenario
*
* @param string $scenario scenario name
* @return bool whether the validator applies to the specified scenario.
*/
public function isActive($scenario)
{
return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true));
}
/**
* Adds an error about the specified attribute to the model object.
* This is a helper method that performs message selection and internationalization.
* @param \yii\base\Model $model the data model being validated
* @param string $attribute the attribute being validated
* @param string $message the error message
* @param array $params values for the placeholders in the error message
*/
public function addError($model, $attribute, $message, $params = [])
{
$params['attribute'] = $model->getAttributeLabel($attribute);
if (!isset($params['value'])) {
$value = $model->$attribute;
if (is_array($value)) {
$params['value'] = 'array()';
} elseif (is_object($value) && !method_exists($value, '__toString')) {
$params['value'] = '(object)';
} else {
$params['value'] = $value;
}
}
$model->addError($attribute, $this->formatMessage($message, $params));
}
/**
* Checks if the given value is empty.
* A value is considered empty if it is null, an empty array, or an empty string.
* Note that this method is different from PHP empty(). It will return false when the value is 0.
* @param mixed $value the value to be checked
* @return bool whether the value is empty
*/
public function isEmpty($value)
{
if ($this->isEmpty !== null) {
return call_user_func($this->isEmpty, $value);
}
return $value === null || $value === [] || $value === '';
}
/**
* Formats a mesage using the I18N, or simple strtr if `\Yii::$app` is not available.
* @param string $message
* @param array $params
* @since 2.0.12
* @return string
*/
protected function formatMessage($message, $params)
{
if (Yii::$app !== null) {
return \Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
}
$placeholders = [];
foreach ((array) $params as $name => $value) {
$placeholders['{' . $name . '}'] = $value;
}
return ($placeholders === []) ? $message : strtr($message, $placeholders);
}
/**
* Returns cleaned attribute names without the `!` character at the beginning.
* @return array attribute names.
* @since 2.0.12
*/
public function getAttributeNames()
{
return array_map(function ($attribute) {
return ltrim($attribute, '!');
}, $this->attributes);
}
}