From ed3564c1cf17833d2e8222b71174fc2633726211 Mon Sep 17 00:00:00 2001 From: Tomek Romik Date: Fri, 1 Aug 2014 21:50:03 +0200 Subject: [PATCH] Added client-side image validation --- framework/assets/yii.validation.js | 110 ++++++++++++++++++++---- framework/validators/FileValidator.php | 16 +++- framework/validators/ImageValidator.php | 53 ++++++++++++ 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/framework/assets/yii.validation.js b/framework/assets/yii.validation.js index 69c0742001..82abbe6a3c 100644 --- a/framework/assets/yii.validation.js +++ b/framework/assets/yii.validation.js @@ -68,27 +68,29 @@ yii.validation = (function ($) { pub.addMessage(messages, options.notEqual, value); } }, - - file: function (value, messages, options, attribute) { - var files = $(attribute.input).get(0).files, - index, ext; - + + globalFiles: function(files, messages, options) { if (options.message && !files) { - pub.addMessage(messages, options.message, value); + pub.addMessage(messages, options.message); } - if (!options.skipOnEmpty && files.length == 0) { - pub.addMessage(messages, options.uploadRequired, value); - } else if (files.length == 0) { - return; + if (!options.skipOnEmpty && files.length === 0) { + pub.addMessage(messages, options.uploadRequired); + } else if (files.length === 0) { + return false; } if (options.maxFiles && options.maxFiles < files.length) { pub.addMessage(messages, options.tooMany); } - - $.each(files, function (i, file) { - if (options.extensions && options.extensions.length > 0) { + + return true; + }, + + singleFile: function(file, messages, options) { + var index, ext; + + if (options.extensions && options.extensions.length > 0) { index = file.name.lastIndexOf('.'); if (!~index) { @@ -98,26 +100,100 @@ yii.validation = (function ($) { } if (!~options.extensions.indexOf(ext)) { - messages.push(options.wrongExtension.replace(/\{file\}/g, file.name)); + pub.addMessage(messages, 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)); + pub.addMessage(messages, options.wrongMimeType.replace(/\{file\}/g, file.name)); } } if (options.maxSize && options.maxSize < file.size) { - messages.push(options.tooBig.replace(/\{file\}/g, file.name)); + pub.addMessage(messages, options.tooBig.replace(/\{file\}/g, file.name)); } if (options.maxSize && options.minSize > file.size) { - messages.push(options.tooSmall.replace(/\{file\}/g, file.name)); + 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) { + self.singleFile(file, messages, options); }); }, + + image: function (messages, options, deferred, attribute) { + var files = $(attribute.input).get(0).files, + self = this; + + if ( !self.globalFiles(files, messages, options) ) { + return; + } + + $.each(files, function (i, file) { + // Perform file validation + self.singleFile(file, messages, options); + + // Skip image validation if FileReader API is not available + if (typeof FileReader === "undefined") { + return; + } + + var def = $.Deferred(), + fr = new FileReader(), + img = new Image(); + + img.onload = function () { + if (options.minWidth && this.width < options.minWidth) { + pub.addMessage(messages, options.underWidth.replace(/\{file\}/g, file.name)); + } + + if (options.maxWidth && this.width > options.maxWidth) { + pub.addMessage(messages, options.overWidth.replace(/\{file\}/g, file.name)); + } + + if (options.minHeight && this.height < options.minHeight) { + pub.addMessage(messages, options.underHeight.replace(/\{file\}/g, file.name)); + } + + if (options.maxHeight && this.height > options.maxHeight) { + pub.addMessage(messages, options.overHeight.replace(/\{file\}/g, file.name)); + } + def.resolve(); + }; + + img.onerror = function () { + pub.addMessage(messages, options.notImage); + def.resolve(); + }; + + fr.onload = function () { + img.src = fr.result; + }; + + // Resolve deferred if there was error while reading data + fr.onerror = function () { + def.resolve(); + }; + + fr.readAsDataURL(file); + + deferred.push(def); + }); + + }, number: function (value, messages, options) { if (options.skipOnEmpty && pub.isEmpty(value)) { diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index 9900a88b76..eff6fa8d04 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -122,6 +122,8 @@ class FileValidator extends Validator * - {mimeTypes}: the value of [[mimeTypes]] */ public $wrongMimeType; + + protected $_clientOptions; /** @@ -342,12 +344,15 @@ class FileValidator extends Validator $options['skipOnEmpty'] = $this->skipOnEmpty; if ( !$this->skipOnEmpty ) { - $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [], Yii::$app->language); + $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [ + 'attribute' => $label, + ], Yii::$app->language); } if ( $this->mimeTypes !== null ) { $options['mimeTypes'] = $this->mimeTypes; $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ + 'attribute' => $label, 'mimeTypes' => join(', ', $this->mimeTypes) ], Yii::$app->language); } @@ -355,6 +360,7 @@ class FileValidator extends Validator if ( $this->extensions !== null ) { $options['extensions'] = $this->extensions; $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ + 'attribute' => $label, 'extensions' => join(', ', $this->extensions) ], Yii::$app->language); } @@ -362,6 +368,7 @@ class FileValidator extends Validator if ( $this->minSize !== null ) { $options['minSize'] = $this->minSize; $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + 'attribute' => $label, 'limit' => $this->minSize ], Yii::$app->language); } @@ -369,6 +376,7 @@ class FileValidator extends Validator if ( $this->maxSize !== null ) { $options['maxSize'] = $this->maxSize; $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + 'attribute' => $label, 'limit' => $this->maxSize ], Yii::$app->language); } @@ -376,12 +384,16 @@ class FileValidator extends Validator if ( $this->maxFiles !== null ) { $options['maxFiles'] = $this->maxFiles; $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ + 'attribute' => $label, 'limit' => $this->maxFiles ], Yii::$app->language); } ValidationAsset::register($view); - return 'yii.validation.file(value, messages, ' . json_encode($options) . ', attribute);'; + // Store options for ImageValidator + $this->_clientOptions = $options; + + return 'yii.validation.file(messages, ' . json_encode($options) . ', attribute);'; } } diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php index 0f84b4de48..989fa67ed4 100644 --- a/framework/validators/ImageValidator.php +++ b/framework/validators/ImageValidator.php @@ -158,4 +158,57 @@ class ImageValidator extends FileValidator return null; } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + parent::clientValidateAttribute($object, $attribute, $view); + + $label = $object->getAttributeLabel($attribute); + + // Inherit options from FileValidator + $options = $this->_clientOptions; + + if ( $this->notImage !== null ) { + $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ + 'attribute' => $label + ], Yii::$app->language); + } + + if ( $this->minWidth !== null ) { + $options['minWidth'] = $this->minWidth; + $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ + 'attribute' => $label, + 'limit' => $this->minWidth + ], Yii::$app->language); + } + + if ( $this->maxWidth !== null ) { + $options['maxWidth'] = $this->maxWidth; + $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ + 'attribute' => $label, + 'limit' => $this->maxWidth + ], Yii::$app->language); + } + + if ( $this->minHeight !== null ) { + $options['minHeight'] = $this->minHeight; + $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ + 'attribute' => $label, + 'limit' => $this->maxHeight + ], Yii::$app->language); + } + + if ( $this->maxHeight !== null ) { + $options['maxHeight'] = $this->maxHeight; + $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ + 'attribute' => $label, + 'limit' => $this->maxHeight + ], Yii::$app->language); + } + + return 'yii.validation.image(messages, ' . json_encode($options) . ', deferred, attribute);'; + } }