Commit 6dd2203a by Qiang Xue

Fixes #4566: Added client validation support for image validator

parent 87f58e48
...@@ -184,7 +184,7 @@ function setExecutable($root, $paths) ...@@ -184,7 +184,7 @@ function setExecutable($root, $paths)
function setCookieValidationKey($root, $paths) function setCookieValidationKey($root, $paths)
{ {
foreach ($paths as $file) { foreach ($paths as $file) {
echo " generating cookie validation key $file\n"; echo " generate cookie validation key in $file\n";
$file = $root . '/' . $file; $file = $root . '/' . $file;
$length = 32; $length = 32;
$bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
......
...@@ -168,6 +168,7 @@ Yii Framework 2 Change Log ...@@ -168,6 +168,7 @@ Yii Framework 2 Change Log
- Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) - Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma)
- Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code) - Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code)
- Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp) - Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp)
- Enh #4566: Added client validation support for image validator (Skysplit, qiangxue)
- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
- Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue)
......
...@@ -69,112 +69,49 @@ yii.validation = (function ($) { ...@@ -69,112 +69,49 @@ yii.validation = (function ($) {
} }
}, },
globalFiles: function(files, messages, options) { file: function (attribute, messages, options) {
if (options.message && !files) { var files = getFiles(attribute, messages, options);
pub.addMessage(messages, options.message);
}
if (files.length === 0) {
if (!options.skipOnEmpty) {
pub.addMessage(messages, options.uploadRequired);
}
return false;
}
if (options.maxFiles && options.maxFiles < files.length) {
pub.addMessage(messages, options.tooMany);
}
return true;
},
singleFile: function(file, messages, options) {
var index, ext;
if (options.extensions && options.extensions.length > 0) {
index = file.name.lastIndexOf('.');
if (!~index) {
ext = '';
} else {
ext = file.name.substr(index + 1, file.name.length).toLowerCase();
}
if (!~options.extensions.indexOf(ext)) {
pub.addMessage(messages, options.wrongExtension.replace(/\{file\}/g, file.name));
}
}
if (options.mimeTypes && options.mimeTypes.length > 0) {
if (!~options.mimeTypes.indexOf(file.type)) {
pub.addMessage(messages, options.wrongMimeType.replace(/\{file\}/g, file.name));
}
}
if (options.maxSize && options.maxSize < file.size) {
pub.addMessage(messages, options.tooBig.replace(/\{file\}/g, file.name));
}
if (options.maxSize && options.minSize > file.size) {
pub.addMessage(messages, options.tooSmall.replace(/\{file\}/g, file.name));
}
},
file: function (messages, options, attribute) {
var files = $(attribute.input).get(0).files,
self = this;
if ( !self.globalFiles(files, messages, options) ) {
return;
}
$.each(files, function (i, file) { $.each(files, function (i, file) {
self.singleFile(file, messages, options); validateFile(file, messages, options);
}); });
}, },
image: function (messages, options, deferred, attribute) { image: function (attribute, messages, options, deferred) {
var files = $(attribute.input).get(0).files, var files = getFiles(attribute, messages, options);
self = this;
if ( !self.globalFiles(files, messages, options) ) {
return;
}
$.each(files, function (i, file) { $.each(files, function (i, file) {
// Perform file validation validateFile(file, messages, options);
self.singleFile(file, messages, options);
// Skip image validation if FileReader API is not available // Skip image validation if FileReader API is not available
if (typeof FileReader === "undefined") { if (typeof FileReader === "undefined") {
return; return;
} }
var def = $.Deferred(), var def = $.Deferred(),
fr = new FileReader(), fr = new FileReader(),
img = new Image(); img = new Image();
img.onload = function () { img.onload = function () {
if (options.minWidth && this.width < options.minWidth) { if (options.minWidth && this.width < options.minWidth) {
pub.addMessage(messages, options.underWidth.replace(/\{file\}/g, file.name)); messages.push(options.underWidth.replace(/\{file\}/g, file.name));
} }
if (options.maxWidth && this.width > options.maxWidth) { if (options.maxWidth && this.width > options.maxWidth) {
pub.addMessage(messages, options.overWidth.replace(/\{file\}/g, file.name)); messages.push(options.overWidth.replace(/\{file\}/g, file.name));
} }
if (options.minHeight && this.height < options.minHeight) { if (options.minHeight && this.height < options.minHeight) {
pub.addMessage(messages, options.underHeight.replace(/\{file\}/g, file.name)); messages.push(options.underHeight.replace(/\{file\}/g, file.name));
} }
if (options.maxHeight && this.height > options.maxHeight) { if (options.maxHeight && this.height > options.maxHeight) {
pub.addMessage(messages, options.overHeight.replace(/\{file\}/g, file.name)); messages.push(options.overHeight.replace(/\{file\}/g, file.name));
} }
def.resolve(); def.resolve();
}; };
img.onerror = function () { img.onerror = function () {
pub.addMessage(messages, options.notImage); messages.push(options.notImage);
def.resolve(); def.resolve();
}; };
...@@ -363,5 +300,60 @@ yii.validation = (function ($) { ...@@ -363,5 +300,60 @@ yii.validation = (function ($) {
} }
} }
}; };
function getFiles(attribute, messages, options) {
var files = $(attribute.input).get(0).files;
if (!files) {
messages.push(options.message);
return [];
}
if (files.length === 0) {
if (!options.skipOnEmpty) {
messages.push(options.uploadRequired);
}
return [];
}
if (options.maxFiles && options.maxFiles < files.length) {
messages.push(options.tooMany);
return [];
}
return files;
}
function validateFile(file, messages, options) {
if (options.extensions && options.extensions.length > 0) {
var index, ext;
index = file.name.lastIndexOf('.');
if (!~index) {
ext = '';
} else {
ext = file.name.substr(index + 1, file.name.length).toLowerCase();
}
if (!~options.extensions.indexOf(ext)) {
messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
}
}
if (options.mimeTypes && options.mimeTypes.length > 0) {
if (!~options.mimeTypes.indexOf(file.type)) {
messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name));
}
}
if (options.maxSize && options.maxSize < file.size) {
messages.push(options.tooBig.replace(/\{file\}/g, file.name));
}
if (options.minSize && options.minSize > file.size) {
messages.push(options.tooSmall.replace(/\{file\}/g, file.name));
}
}
return pub; return pub;
})(jQuery); })(jQuery);
...@@ -123,8 +123,6 @@ class FileValidator extends Validator ...@@ -123,8 +123,6 @@ class FileValidator extends Validator
*/ */
public $wrongMimeType; public $wrongMimeType;
protected $_clientOptions;
/** /**
* @inheritdoc * @inheritdoc
...@@ -152,12 +150,16 @@ class FileValidator extends Validator ...@@ -152,12 +150,16 @@ class FileValidator extends Validator
} }
if (!is_array($this->extensions)) { if (!is_array($this->extensions)) {
$this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY); $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) { if ($this->wrongMimeType === null) {
$this->wrongMimeType = Yii::t('yii', 'Only files with these MIME types are allowed: {mimeTypes}.'); $this->wrongMimeType = Yii::t('yii', 'Only files with these MIME types are allowed: {mimeTypes}.');
} }
if (!is_array($this->mimeTypes)) { if (!is_array($this->mimeTypes)) {
$this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY); $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY);
} else {
$this->mimeTypes = array_map('strtolower', $this->mimeTypes);
} }
} }
...@@ -334,22 +336,35 @@ class FileValidator extends Validator ...@@ -334,22 +336,35 @@ class FileValidator extends Validator
*/ */
public function clientValidateAttribute($object, $attribute, $view) public function clientValidateAttribute($object, $attribute, $view)
{ {
ValidationAsset::register($view);
$options = $this->getClientOptions($object, $attribute);
return 'yii.validation.file(attribute, messages, ' . json_encode($options) . ');';
}
/**
* Returns the client side validation options.
* @param \yii\base\Model $object the model being validated
* @param string $attribute the attribute name being validated
* @return array the client side validation options
*/
protected function getClientOptions($object, $attribute)
{
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
if ( $this->message !== null ){ if ( $this->message !== null ){
$options['message'] = Yii::$app->getI18n()->format($this->message, [ $options['message'] = Yii::$app->getI18n()->format($this->message, [
'attribute' => $label, 'attribute' => $label,
], Yii::$app->language); ], Yii::$app->language);
} }
$options['skipOnEmpty'] = $this->skipOnEmpty; $options['skipOnEmpty'] = $this->skipOnEmpty;
if ( !$this->skipOnEmpty ) { if ( !$this->skipOnEmpty ) {
$options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [ $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [
'attribute' => $label, 'attribute' => $label,
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->mimeTypes !== null ) { if ( $this->mimeTypes !== null ) {
$options['mimeTypes'] = $this->mimeTypes; $options['mimeTypes'] = $this->mimeTypes;
$options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [
...@@ -357,7 +372,7 @@ class FileValidator extends Validator ...@@ -357,7 +372,7 @@ class FileValidator extends Validator
'mimeTypes' => join(', ', $this->mimeTypes) 'mimeTypes' => join(', ', $this->mimeTypes)
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->extensions !== null ) { if ( $this->extensions !== null ) {
$options['extensions'] = $this->extensions; $options['extensions'] = $this->extensions;
$options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [
...@@ -365,7 +380,7 @@ class FileValidator extends Validator ...@@ -365,7 +380,7 @@ class FileValidator extends Validator
'extensions' => join(', ', $this->extensions) 'extensions' => join(', ', $this->extensions)
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->minSize !== null ) { if ( $this->minSize !== null ) {
$options['minSize'] = $this->minSize; $options['minSize'] = $this->minSize;
$options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [
...@@ -373,15 +388,15 @@ class FileValidator extends Validator ...@@ -373,15 +388,15 @@ class FileValidator extends Validator
'limit' => $this->minSize 'limit' => $this->minSize
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->maxSize !== null ) { if ( $this->maxSize !== null ) {
$options['maxSize'] = $this->maxSize; $options['maxSize'] = $this->maxSize;
$options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [
'attribute' => $label, 'attribute' => $label,
'limit' => $this->maxSize 'limit' => $this->maxSize
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->maxFiles !== null ) { if ( $this->maxFiles !== null ) {
$options['maxFiles'] = $this->maxFiles; $options['maxFiles'] = $this->maxFiles;
$options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [
...@@ -389,12 +404,7 @@ class FileValidator extends Validator ...@@ -389,12 +404,7 @@ class FileValidator extends Validator
'limit' => $this->maxFiles 'limit' => $this->maxFiles
], Yii::$app->language); ], Yii::$app->language);
} }
ValidationAsset::register($view); return $options;
// Store options for ImageValidator
$this->_clientOptions = $options;
return 'yii.validation.file(messages, ' . json_encode($options) . ', attribute);';
} }
} }
...@@ -134,7 +134,7 @@ class ImageValidator extends FileValidator ...@@ -134,7 +134,7 @@ class ImageValidator extends FileValidator
return [$this->notImage, ['file' => $image->name]]; return [$this->notImage, ['file' => $image->name]];
} }
list($width, $height, $type) = $imageInfo; list($width, $height) = $imageInfo;
if ($width == 0 || $height == 0) { if ($width == 0 || $height == 0) {
return [$this->notImage, ['file' => $image->name]]; return [$this->notImage, ['file' => $image->name]];
...@@ -158,57 +158,64 @@ class ImageValidator extends FileValidator ...@@ -158,57 +158,64 @@ class ImageValidator extends FileValidator
return null; return null;
} }
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function clientValidateAttribute($object, $attribute, $view) public function clientValidateAttribute($object, $attribute, $view)
{ {
parent::clientValidateAttribute($object, $attribute, $view); ValidationAsset::register($view);
$options = $this->getClientOptions($object, $attribute);
return 'yii.validation.image(attribute, messages, ' . json_encode($options) . ', deferred);';
}
/**
* @inheritdoc
*/
protected function getClientOptions($object, $attribute)
{
$options = parent::getClientOptions($object, $attribute);
$label = $object->getAttributeLabel($attribute); $label = $object->getAttributeLabel($attribute);
// Inherit client options from FileValidator if ($this->notImage !== null) {
$options = $this->_clientOptions;
if ( $this->notImage !== null ) {
$options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [
'attribute' => $label 'attribute' => $label
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->minWidth !== null ) { if ($this->minWidth !== null) {
$options['minWidth'] = $this->minWidth; $options['minWidth'] = $this->minWidth;
$options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [
'attribute' => $label, 'attribute' => $label,
'limit' => $this->minWidth 'limit' => $this->minWidth
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->maxWidth !== null ) { if ($this->maxWidth !== null) {
$options['maxWidth'] = $this->maxWidth; $options['maxWidth'] = $this->maxWidth;
$options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [
'attribute' => $label, 'attribute' => $label,
'limit' => $this->maxWidth 'limit' => $this->maxWidth
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->minHeight !== null ) { if ($this->minHeight !== null) {
$options['minHeight'] = $this->minHeight; $options['minHeight'] = $this->minHeight;
$options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [
'attribute' => $label, 'attribute' => $label,
'limit' => $this->maxHeight 'limit' => $this->maxHeight
], Yii::$app->language); ], Yii::$app->language);
} }
if ( $this->maxHeight !== null ) { if ($this->maxHeight !== null) {
$options['maxHeight'] = $this->maxHeight; $options['maxHeight'] = $this->maxHeight;
$options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [
'attribute' => $label, 'attribute' => $label,
'limit' => $this->maxHeight 'limit' => $this->maxHeight
], Yii::$app->language); ], Yii::$app->language);
} }
return 'yii.validation.image(messages, ' . json_encode($options) . ', deferred, attribute);'; return $options;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment