From ba31ee618f219a046677322df13baab6e78840a0 Mon Sep 17 00:00:00 2001 From: Ragazzo Date: Wed, 15 May 2013 14:55:02 +0400 Subject: [PATCH 01/15] partial response added, new code-style applied --- yii/web/Response.php | 74 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/yii/web/Response.php b/yii/web/Response.php index 54b7f6e2a0..2d2d230609 100644 --- a/yii/web/Response.php +++ b/yii/web/Response.php @@ -35,28 +35,86 @@ class Response extends \yii\base\Response */ public function sendFile($fileName, $content, $mimeType = null, $terminate = true) { - if ($mimeType === null && ($mimeType = FileHelper::getMimeType($fileName)) === null) { - $mimeType = 'application/octet-stream'; + if ($mimeType === null) { + if (($mimeType = CFileHelper::getMimeTypeByExtension($fileName)) === null) { + $mimeType='text/plain'; + } } + + $fileSize = (function_exists('mb_strlen') ? mb_strlen($content,'8bit') : strlen($content)); + $contentStart = 0; + $contentEnd = $fileSize - 1; + + if (isset($_SERVER['HTTP_RANGE'])) { + header('Accept-Ranges: bytes'); + + //client sent us a multibyte range, can not hold this one for now + if (strpos(',',$_SERVER['HTTP_RANGE']) !== false) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + ob_start(); + Yii::app()->end(0,false); + ob_end_clean(); + exit(0); + } + + $range = str_replace('bytes=','',$_SERVER['HTTP_RANGE']); + + //range requests starts from "-", so it means that data must be dumped the end point. + if ($range[0] === '-') { + $contentStart = $fileSize - substr($range,1); + } else { + $range = explode('-',$range); + $contentStart = $range[0]; + $contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize; + } + + /* Check the range and make sure it's treated according to the specs. + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ + // End bytes can not be larger than $end. + $contentEnd = ($contentEnd > $fileSize) ? $fileSize : $contentEnd; + + // Validate the requested range and return an error if it's not correct. + $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); + + if ($wrongContentStart) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + ob_start(); + Yii::app()->end(0,false); + ob_end_clean(); + exit(0); + } + + header('HTTP/1.1 206 Partial Content'); + header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); + } else { + header('HTTP/1.1 200 OK'); + } + + $length = $contentEnd - $contentStart + 1; // Calculate new content length + header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header("Content-type: $mimeType"); - if (ob_get_length() === false) { - header('Content-Length: ' . (function_exists('mb_strlen') ? mb_strlen($content, '8bit') : strlen($content))); - } + header('Content-Length: '.$length); header("Content-Disposition: attachment; filename=\"$fileName\""); header('Content-Transfer-Encoding: binary'); + $content = function_exists('mb_substr') ? mb_substr($content,$contentStart,$length) : substr($content,$contentStart,$length); if ($terminate) { // clean up the application first because the file downloading could take long time // which may cause timeout of some resources (such as DB connection) - Yii::app()->end(0, false); + ob_start(); + Yii::app()->end(0,false); + ob_end_clean(); echo $content; exit(0); - } else { - echo $content; } + else + echo $content; } /** From 04563cb76a5b34e6676d8ea9a1257e93ea08b2ed Mon Sep 17 00:00:00 2001 From: Ragazzo Date: Wed, 15 May 2013 14:57:12 +0400 Subject: [PATCH 02/15] code style fix --- yii/web/Response.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yii/web/Response.php b/yii/web/Response.php index 2d2d230609..1bcb54e4ab 100644 --- a/yii/web/Response.php +++ b/yii/web/Response.php @@ -112,9 +112,9 @@ class Response extends \yii\base\Response ob_end_clean(); echo $content; exit(0); - } - else + } else { echo $content; + } } /** From 959cff3e9b872b7a8b23a94a9e41959a932088a4 Mon Sep 17 00:00:00 2001 From: Ragazzo Date: Wed, 15 May 2013 15:22:01 +0400 Subject: [PATCH 03/15] string helper fixed, mime-type reverted --- yii/web/Response.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/yii/web/Response.php b/yii/web/Response.php index 1bcb54e4ab..0f166f473f 100644 --- a/yii/web/Response.php +++ b/yii/web/Response.php @@ -10,6 +10,7 @@ namespace yii\web; use Yii; use yii\helpers\FileHelper; use yii\helpers\Html; +use yii\helpers\StringHelper; /** * @author Qiang Xue @@ -36,12 +37,12 @@ class Response extends \yii\base\Response public function sendFile($fileName, $content, $mimeType = null, $terminate = true) { if ($mimeType === null) { - if (($mimeType = CFileHelper::getMimeTypeByExtension($fileName)) === null) { - $mimeType='text/plain'; + if (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null) { + $mimeType='application/octet-stream'; } } - $fileSize = (function_exists('mb_strlen') ? mb_strlen($content,'8bit') : strlen($content)); + $fileSize = StringHelper::strlen($content); $contentStart = 0; $contentEnd = $fileSize - 1; @@ -49,7 +50,7 @@ class Response extends \yii\base\Response header('Accept-Ranges: bytes'); //client sent us a multibyte range, can not hold this one for now - if (strpos(',',$_SERVER['HTTP_RANGE']) !== false) { + if (strpos(',', $_SERVER['HTTP_RANGE']) !== false) { header('HTTP/1.1 416 Requested Range Not Satisfiable'); header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); ob_start(); @@ -58,13 +59,13 @@ class Response extends \yii\base\Response exit(0); } - $range = str_replace('bytes=','',$_SERVER['HTTP_RANGE']); + $range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']); //range requests starts from "-", so it means that data must be dumped the end point. if ($range[0] === '-') { - $contentStart = $fileSize - substr($range,1); + $contentStart = $fileSize - substr($range, 1); } else { - $range = explode('-',$range); + $range = explode('-', $range); $contentStart = $range[0]; $contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize; } @@ -102,7 +103,7 @@ class Response extends \yii\base\Response header('Content-Length: '.$length); header("Content-Disposition: attachment; filename=\"$fileName\""); header('Content-Transfer-Encoding: binary'); - $content = function_exists('mb_substr') ? mb_substr($content,$contentStart,$length) : substr($content,$contentStart,$length); + $content = StringHelper::strlen($content); if ($terminate) { // clean up the application first because the file downloading could take long time From 8a868c9798ad0431bf3b35e9e79ed2e899b51dd0 Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 15 May 2013 20:34:55 +0600 Subject: [PATCH 04/15] Logger: getTag()/setTag() error fixed. --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 832a89053c..13fcf4a183 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,7 @@ nbproject Thumbs.db # composer vendor dir -/yii/vendor \ No newline at end of file +/yii/vendor + +# composer itself is not needed +composer.phar From 8a5e1f4f5fc48c228f2ad3b9fb2b6db3bd307272 Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 15 May 2013 21:46:02 +0600 Subject: [PATCH 05/15] Fixes #143. UrlValidator and EmailValidator IDN support. --- composer.json | 17 ++++++++++++++++- composer.lock | 12 +++++++++++- yii/assets.php | 6 ++++++ yii/assets/yii.validation.js | 26 +++++++++++++++++++++++--- yii/validators/EmailValidator.php | 21 ++++++++++++++++++--- yii/validators/UrlValidator.php | 14 +++++++++++++- yii/widgets/ActiveField.php | 9 +++++++++ 7 files changed, 96 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index e9c3927414..e89c27b4c6 100644 --- a/composer.json +++ b/composer.json @@ -69,11 +69,26 @@ "bin": [ "yii/yiic" ], + "repositories": [ + { + "type": "package", + "package": { + "name": "bestiejs/punycode.js", + "version": "1.2.1", + "source": { + "url": "git://github.com/bestiejs/punycode.js.git", + "type": "git", + "reference": "1.2.1" + } + } + } + ], "require": { "php": ">=5.3.0", "michelf/php-markdown": "1.3", "twig/twig": "1.12.*", "smarty/smarty": "3.1.*", - "ezyang/htmlpurifier": "v4.5.0" + "ezyang/htmlpurifier": "v4.5.0", + "bestiejs/punycode.js": "1.2.1" } } diff --git a/composer.lock b/composer.lock index 1cae3d4a04..18a46ee0f8 100644 --- a/composer.lock +++ b/composer.lock @@ -3,8 +3,18 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "7d46ce9c4d8d5f4ecae1611ea8f0b49c", + "hash": "a8f949e337a229a4cfb41496a0071ef6", "packages": [ + { + "name": "bestiejs/punycode.js", + "version": "1.2.1", + "source": { + "type": "git", + "url": "git://github.com/bestiejs/punycode.js.git", + "reference": "1.2.1" + }, + "type": "library" + }, { "name": "ezyang/htmlpurifier", "version": "v4.5.0", diff --git a/yii/assets.php b/yii/assets.php index 7ee177d895..f2b0f942e5 100644 --- a/yii/assets.php +++ b/yii/assets.php @@ -42,4 +42,10 @@ return array( ), 'depends' => array('yii'), ), + 'punycode' => array( + 'sourcePath' => __DIR__ . '/vendor/bestiejs/punycode.js', + 'js' => array( + 'punycode.min.js', + ), + ), ); diff --git a/yii/assets/yii.validation.js b/yii/assets/yii.validation.js index 5fa8492da6..d173e9b5df 100644 --- a/yii/assets/yii.validation.js +++ b/yii/assets/yii.validation.js @@ -110,9 +110,18 @@ yii.validation = (function ($) { return; } - var valid = value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)); + var valid = true; - if (!valid) { + if (options.idn) { + var regexp = /^(.*)@(.*)$/, matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]); + } + } + + if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) { messages.push(options.message); } }, @@ -126,7 +135,18 @@ yii.validation = (function ($) { value = options.defaultScheme + '://' + value; } - if (!value.match(options.pattern)) { + var valid = true; + + if (options.idn) { + var regexp = /^([^:]+):\/\/([^\/]+)(.*)?/, matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3]; + } + } + + if (!valid || !value.match(options.pattern)) { messages.push(options.message); } }, diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index dce6c37a5f..00e9d407ba 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -47,6 +47,12 @@ class EmailValidator extends Validator * Defaults to false. */ public $checkPort = false; + /** + * @var boolean whether validation process should take into account IDN (internationalized domain + * names). Defaults to false meaning that validation of emails containing IDN will always fail. + */ + public $idn = false; + /** * Initializes the validator. @@ -81,10 +87,18 @@ class EmailValidator extends Validator public function validateValue($value) { // make sure string length is limited to avoid DOS attacks - $valid = is_string($value) && strlen($value) <= 254 - && (preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value)); + if (!is_string($value) || strlen($value) >= 255) { + return false; + } + if (($atPosition = strpos($value, '@')) === false) { + return false; + } + $domain = rtrim(substr($value, $atPosition + 1), '>'); + if ($this->idn) { + $value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain); + } + $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); if ($valid) { - $domain = rtrim(substr($value, strpos($value, '@') + 1), '>'); if ($this->checkMX && function_exists('checkdnsrr')) { $valid = checkdnsrr($domain, 'MX'); } @@ -111,6 +125,7 @@ class EmailValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), + 'idn' => (boolean)$this->idn, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index 6917d011b4..9c3c878d7c 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -37,6 +37,12 @@ class UrlValidator extends Validator * contain the scheme part. **/ public $defaultScheme; + /** + * @var boolean whether validation process should take into account IDN (internationalized + * domain names). Defaults to false meaning that validation of URLs containing IDN will always + * fail. + */ + public $idn = false; /** @@ -87,6 +93,12 @@ class UrlValidator extends Validator $pattern = $this->pattern; } + if ($this->idn) { + $value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) { + return '://' . idn_to_ascii($matches[1]); + }, $value); + } + if (preg_match($pattern, $value)) { return true; } @@ -115,6 +127,7 @@ class UrlValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), + 'idn' => (boolean)$this->idn, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; @@ -126,4 +139,3 @@ class UrlValidator extends Validator return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; } } - diff --git a/yii/widgets/ActiveField.php b/yii/widgets/ActiveField.php index 55357a3ee1..d3716b51d9 100644 --- a/yii/widgets/ActiveField.php +++ b/yii/widgets/ActiveField.php @@ -11,6 +11,8 @@ use yii\db\ActiveRecord; use yii\helpers\Html; use yii\base\Model; use yii\web\JsExpression; +use yii\validators\EmailValidator; +use yii\validators\UrlValidator; /** * @author Qiang Xue @@ -121,6 +123,13 @@ class ActiveField extends Component } $options['class'] = implode(' ', $class); + foreach ($this->model->getActiveValidators($attribute) as $validator) { + if (($validator instanceof EmailValidator || $validator instanceof UrlValidator) && $validator->idn) { + $this->form->view->registerAssetBundle('punycode'); + break; + } + } + return Html::beginTag($this->tag, $options); } From 6458b8df8f04dc9b6eb5a7975e1eaf6e9a9fbb42 Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 15 May 2013 22:24:57 +0600 Subject: [PATCH 06/15] UrlValidator and EmailValidator IDN support fixes. --- yii/assets/yii.validation.js | 10 ++++++---- yii/validators/EmailValidator.php | 6 +++--- yii/validators/UrlValidator.php | 6 +++--- yii/widgets/ActiveField.php | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/yii/assets/yii.validation.js b/yii/assets/yii.validation.js index d173e9b5df..c9d62bd3b4 100644 --- a/yii/assets/yii.validation.js +++ b/yii/assets/yii.validation.js @@ -112,8 +112,9 @@ yii.validation = (function ($) { var valid = true; - if (options.idn) { - var regexp = /^(.*)@(.*)$/, matches = regexp.exec(value); + if (options.enableIDN) { + var regexp = /^(.*)@(.*)$/, + matches = regexp.exec(value); if (matches === null) { valid = false; } else { @@ -137,8 +138,9 @@ yii.validation = (function ($) { var valid = true; - if (options.idn) { - var regexp = /^([^:]+):\/\/([^\/]+)(.*)?/, matches = regexp.exec(value); + if (options.enableIDN) { + var regexp = /^([^:]+):\/\/([^\/]+)(.*)?/, + matches = regexp.exec(value); if (matches === null) { valid = false; } else { diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index 00e9d407ba..f1059d778a 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -51,7 +51,7 @@ class EmailValidator extends Validator * @var boolean whether validation process should take into account IDN (internationalized domain * names). Defaults to false meaning that validation of emails containing IDN will always fail. */ - public $idn = false; + public $enableIDN = false; /** @@ -94,7 +94,7 @@ class EmailValidator extends Validator return false; } $domain = rtrim(substr($value, $atPosition + 1), '>'); - if ($this->idn) { + if ($this->enableIDN) { $value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain); } $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); @@ -125,7 +125,7 @@ class EmailValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), - 'idn' => (boolean)$this->idn, + 'enableIDN' => (boolean)$this->enableIDN, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index 9c3c878d7c..ddc3e8661d 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -42,7 +42,7 @@ class UrlValidator extends Validator * domain names). Defaults to false meaning that validation of URLs containing IDN will always * fail. */ - public $idn = false; + public $enableIDN = false; /** @@ -93,7 +93,7 @@ class UrlValidator extends Validator $pattern = $this->pattern; } - if ($this->idn) { + if ($this->enableIDN) { $value = preg_replace_callback('/:\/\/([^\/]+)/', function($matches) { return '://' . idn_to_ascii($matches[1]); }, $value); @@ -127,7 +127,7 @@ class UrlValidator extends Validator '{attribute}' => $object->getAttributeLabel($attribute), '{value}' => $object->$attribute, ))), - 'idn' => (boolean)$this->idn, + 'enableIDN' => (boolean)$this->enableIDN, ); if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/yii/widgets/ActiveField.php b/yii/widgets/ActiveField.php index d3716b51d9..d2254fe6b1 100644 --- a/yii/widgets/ActiveField.php +++ b/yii/widgets/ActiveField.php @@ -124,7 +124,7 @@ class ActiveField extends Component $options['class'] = implode(' ', $class); foreach ($this->model->getActiveValidators($attribute) as $validator) { - if (($validator instanceof EmailValidator || $validator instanceof UrlValidator) && $validator->idn) { + if (($validator instanceof EmailValidator || $validator instanceof UrlValidator) && $validator->enableIDN) { $this->form->view->registerAssetBundle('punycode'); break; } From f7b8595f768488f04885fc6744c34a36d360dcdc Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 15 May 2013 23:17:50 +0600 Subject: [PATCH 07/15] Validator::clientValidateAttribute() now accepts third parameter: a view. punycode.js asset bundle registering moved from ActiveField to validators. --- yii/validators/BooleanValidator.php | 4 +++- yii/validators/CaptchaValidator.php | 4 +++- yii/validators/CompareValidator.php | 4 +++- yii/validators/EmailValidator.php | 8 +++++++- yii/validators/InlineValidator.php | 4 +++- yii/validators/NumberValidator.php | 4 +++- yii/validators/RangeValidator.php | 4 +++- yii/validators/RegularExpressionValidator.php | 4 +++- yii/validators/RequiredValidator.php | 4 +++- yii/validators/StringValidator.php | 4 +++- yii/validators/UrlValidator.php | 8 +++++++- yii/validators/Validator.php | 4 +++- yii/widgets/ActiveField.php | 11 +---------- 13 files changed, 45 insertions(+), 22 deletions(-) diff --git a/yii/validators/BooleanValidator.php b/yii/validators/BooleanValidator.php index 2929dfdb85..3fc8c72ba5 100644 --- a/yii/validators/BooleanValidator.php +++ b/yii/validators/BooleanValidator.php @@ -79,9 +79,11 @@ class BooleanValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array( 'trueValue' => $this->trueValue, diff --git a/yii/validators/CaptchaValidator.php b/yii/validators/CaptchaValidator.php index c49ffdb9ea..f446d5fd08 100644 --- a/yii/validators/CaptchaValidator.php +++ b/yii/validators/CaptchaValidator.php @@ -91,9 +91,11 @@ class CaptchaValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $captcha = $this->getCaptchaAction(); $code = $captcha->getVerifyCode(false); diff --git a/yii/validators/CompareValidator.php b/yii/validators/CompareValidator.php index 7f7318fd44..2fce4fc36c 100644 --- a/yii/validators/CompareValidator.php +++ b/yii/validators/CompareValidator.php @@ -178,9 +178,11 @@ class CompareValidator extends Validator * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated * @return string the client-side validation script + * @param \yii\base\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. * @throws InvalidConfigException if CompareValidator::operator is invalid */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array('operator' => $this->operator); diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index f1059d778a..141b747174 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -113,10 +113,16 @@ class EmailValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } + $options = array( 'pattern' => new JsExpression($this->pattern), 'fullPattern' => new JsExpression($this->fullPattern), diff --git a/yii/validators/InlineValidator.php b/yii/validators/InlineValidator.php index 8af5bbc665..dd951aa7b9 100644 --- a/yii/validators/InlineValidator.php +++ b/yii/validators/InlineValidator.php @@ -79,12 +79,14 @@ class InlineValidator extends Validator * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. Null if the validator does not support * client-side validation. * @see enableClientValidation * @see \yii\web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { if ($this->clientValidate !== null) { $method = $this->clientValidate; diff --git a/yii/validators/NumberValidator.php b/yii/validators/NumberValidator.php index 33822bf0f2..24735ec480 100644 --- a/yii/validators/NumberValidator.php +++ b/yii/validators/NumberValidator.php @@ -114,9 +114,11 @@ class NumberValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); $value = $object->$attribute; diff --git a/yii/validators/RangeValidator.php b/yii/validators/RangeValidator.php index 4bed303c6b..9cf4cb358f 100644 --- a/yii/validators/RangeValidator.php +++ b/yii/validators/RangeValidator.php @@ -81,9 +81,11 @@ class RangeValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $range = array(); foreach ($this->range as $value) { diff --git a/yii/validators/RegularExpressionValidator.php b/yii/validators/RegularExpressionValidator.php index 505812f22c..d28dc223a1 100644 --- a/yii/validators/RegularExpressionValidator.php +++ b/yii/validators/RegularExpressionValidator.php @@ -79,10 +79,12 @@ class RegularExpressionValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. * @throws InvalidConfigException if the "pattern" is not a valid regular expression */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $pattern = $this->pattern; $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern); diff --git a/yii/validators/RequiredValidator.php b/yii/validators/RequiredValidator.php index 424f94c35a..797da2e40a 100644 --- a/yii/validators/RequiredValidator.php +++ b/yii/validators/RequiredValidator.php @@ -102,9 +102,11 @@ class RequiredValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $options = array(); if ($this->requiredValue !== null) { diff --git a/yii/validators/StringValidator.php b/yii/validators/StringValidator.php index 110619e485..9982fc1806 100644 --- a/yii/validators/StringValidator.php +++ b/yii/validators/StringValidator.php @@ -126,9 +126,11 @@ class StringValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { $label = $object->getAttributeLabel($attribute); $value = $object->$attribute; diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index ddc3e8661d..d80238ea2f 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -110,11 +110,17 @@ class UrlValidator extends Validator * Returns the JavaScript needed for performing client-side validation. * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. * @see \yii\Web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } + if (strpos($this->pattern, '{schemes}') !== false) { $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); } else { diff --git a/yii/validators/Validator.php b/yii/validators/Validator.php index 677191b86e..6b103bfdf4 100644 --- a/yii/validators/Validator.php +++ b/yii/validators/Validator.php @@ -211,11 +211,13 @@ abstract class Validator extends Component * * @param \yii\base\Model $object the data object being validated * @param string $attribute the name of the attribute to be validated. + * @param \yii\base\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 the client-side validation script. Null if the validator does not support * client-side validation. * @see \yii\web\ActiveForm::enableClientValidation */ - public function clientValidateAttribute($object, $attribute) + public function clientValidateAttribute($object, $attribute, $view) { return null; } diff --git a/yii/widgets/ActiveField.php b/yii/widgets/ActiveField.php index d2254fe6b1..45faf9d34e 100644 --- a/yii/widgets/ActiveField.php +++ b/yii/widgets/ActiveField.php @@ -11,8 +11,6 @@ use yii\db\ActiveRecord; use yii\helpers\Html; use yii\base\Model; use yii\web\JsExpression; -use yii\validators\EmailValidator; -use yii\validators\UrlValidator; /** * @author Qiang Xue @@ -123,13 +121,6 @@ class ActiveField extends Component } $options['class'] = implode(' ', $class); - foreach ($this->model->getActiveValidators($attribute) as $validator) { - if (($validator instanceof EmailValidator || $validator instanceof UrlValidator) && $validator->enableIDN) { - $this->form->view->registerAssetBundle('punycode'); - break; - } - } - return Html::beginTag($this->tag, $options); } @@ -147,7 +138,7 @@ class ActiveField extends Component $validators = array(); foreach ($this->model->getActiveValidators($attribute) as $validator) { /** @var \yii\validators\Validator $validator */ - $js = $validator->clientValidateAttribute($this->model, $attribute); + $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView()); if ($validator->enableClientValidation && $js != '') { $validators[] = $js; } From bf1b2d9956f73d6365f5b357e12a4976b0ea85cc Mon Sep 17 00:00:00 2001 From: resurtm Date: Wed, 15 May 2013 23:33:55 +0600 Subject: [PATCH 08/15] Form asset bundle registering code moved from ActiveForm::run() to the *Validator::clientValidateAttribute(). --- yii/validators/BooleanValidator.php | 1 + yii/validators/CaptchaValidator.php | 1 + yii/validators/CompareValidator.php | 1 + yii/validators/EmailValidator.php | 8 ++++---- yii/validators/NumberValidator.php | 1 + yii/validators/RangeValidator.php | 1 + yii/validators/RegularExpressionValidator.php | 1 + yii/validators/RequiredValidator.php | 1 + yii/validators/StringValidator.php | 1 + yii/validators/UrlValidator.php | 8 ++++---- yii/widgets/ActiveForm.php | 1 - 11 files changed, 16 insertions(+), 9 deletions(-) diff --git a/yii/validators/BooleanValidator.php b/yii/validators/BooleanValidator.php index 3fc8c72ba5..8cc3b0cee7 100644 --- a/yii/validators/BooleanValidator.php +++ b/yii/validators/BooleanValidator.php @@ -102,6 +102,7 @@ class BooleanValidator extends Validator $options['strict'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CaptchaValidator.php b/yii/validators/CaptchaValidator.php index f446d5fd08..bb0d333a20 100644 --- a/yii/validators/CaptchaValidator.php +++ b/yii/validators/CaptchaValidator.php @@ -113,6 +113,7 @@ class CaptchaValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CompareValidator.php b/yii/validators/CompareValidator.php index 2fce4fc36c..21a644278e 100644 --- a/yii/validators/CompareValidator.php +++ b/yii/validators/CompareValidator.php @@ -205,6 +205,7 @@ class CompareValidator extends Validator '{compareValue}' => $compareValue, ))); + $view->registerAssetBundle('yii/form'); return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index 141b747174..54f2f5ecd4 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -119,10 +119,6 @@ class EmailValidator extends Validator */ public function clientValidateAttribute($object, $attribute, $view) { - if ($this->enableIDN) { - $view->registerAssetBundle('punycode'); - } - $options = array( 'pattern' => new JsExpression($this->pattern), 'fullPattern' => new JsExpression($this->fullPattern), @@ -137,6 +133,10 @@ class EmailValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/NumberValidator.php b/yii/validators/NumberValidator.php index 24735ec480..e23c8162fc 100644 --- a/yii/validators/NumberValidator.php +++ b/yii/validators/NumberValidator.php @@ -151,6 +151,7 @@ class NumberValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RangeValidator.php b/yii/validators/RangeValidator.php index 9cf4cb358f..cb4c25db16 100644 --- a/yii/validators/RangeValidator.php +++ b/yii/validators/RangeValidator.php @@ -103,6 +103,7 @@ class RangeValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.range(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/RegularExpressionValidator.php b/yii/validators/RegularExpressionValidator.php index d28dc223a1..feae920ab4 100644 --- a/yii/validators/RegularExpressionValidator.php +++ b/yii/validators/RegularExpressionValidator.php @@ -112,6 +112,7 @@ class RegularExpressionValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RequiredValidator.php b/yii/validators/RequiredValidator.php index 797da2e40a..6dde13833c 100644 --- a/yii/validators/RequiredValidator.php +++ b/yii/validators/RequiredValidator.php @@ -126,6 +126,7 @@ class RequiredValidator extends Validator '{value}' => $object->$attribute, ))); + $view->registerAssetBundle('yii/form'); return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/StringValidator.php b/yii/validators/StringValidator.php index 9982fc1806..752c9abab4 100644 --- a/yii/validators/StringValidator.php +++ b/yii/validators/StringValidator.php @@ -170,6 +170,7 @@ class StringValidator extends Validator $options['skipOnEmpty'] = 1; } + $view->registerAssetBundle('yii/form'); return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index d80238ea2f..58afad9776 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -117,10 +117,6 @@ class UrlValidator extends Validator */ public function clientValidateAttribute($object, $attribute, $view) { - if ($this->enableIDN) { - $view->registerAssetBundle('punycode'); - } - if (strpos($this->pattern, '{schemes}') !== false) { $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); } else { @@ -142,6 +138,10 @@ class UrlValidator extends Validator $options['defaultScheme'] = $this->defaultScheme; } + $view->registerAssetBundle('yii/form'); + if ($this->enableIDN) { + $view->registerAssetBundle('punycode'); + } return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/widgets/ActiveForm.php b/yii/widgets/ActiveForm.php index 25a205468e..0225f769e7 100644 --- a/yii/widgets/ActiveForm.php +++ b/yii/widgets/ActiveForm.php @@ -134,7 +134,6 @@ class ActiveForm extends Widget $id = $this->options['id']; $options = Json::encode($this->getClientOptions()); $attributes = Json::encode($this->attributes); - $this->view->registerAssetBundle('yii/form'); $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); } echo Html::endForm(); From 60e574a68deb93daeda3d6412dcc7dd89d9b4bb7 Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 16 May 2013 00:06:07 +0600 Subject: [PATCH 09/15] =?UTF-8?q?'yii/form'=20=E2=86=92=20'yii/validation'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yii/validators/BooleanValidator.php | 2 +- yii/validators/CaptchaValidator.php | 2 +- yii/validators/CompareValidator.php | 2 +- yii/validators/EmailValidator.php | 2 +- yii/validators/NumberValidator.php | 2 +- yii/validators/RangeValidator.php | 2 +- yii/validators/RegularExpressionValidator.php | 2 +- yii/validators/RequiredValidator.php | 2 +- yii/validators/StringValidator.php | 2 +- yii/validators/UrlValidator.php | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/yii/validators/BooleanValidator.php b/yii/validators/BooleanValidator.php index 8cc3b0cee7..67a64f60f2 100644 --- a/yii/validators/BooleanValidator.php +++ b/yii/validators/BooleanValidator.php @@ -102,7 +102,7 @@ class BooleanValidator extends Validator $options['strict'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CaptchaValidator.php b/yii/validators/CaptchaValidator.php index bb0d333a20..dbc263ec85 100644 --- a/yii/validators/CaptchaValidator.php +++ b/yii/validators/CaptchaValidator.php @@ -113,7 +113,7 @@ class CaptchaValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/CompareValidator.php b/yii/validators/CompareValidator.php index 21a644278e..b8e8a50ca1 100644 --- a/yii/validators/CompareValidator.php +++ b/yii/validators/CompareValidator.php @@ -205,7 +205,7 @@ class CompareValidator extends Validator '{compareValue}' => $compareValue, ))); - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/EmailValidator.php b/yii/validators/EmailValidator.php index 54f2f5ecd4..4297f127a8 100644 --- a/yii/validators/EmailValidator.php +++ b/yii/validators/EmailValidator.php @@ -133,7 +133,7 @@ class EmailValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); if ($this->enableIDN) { $view->registerAssetBundle('punycode'); } diff --git a/yii/validators/NumberValidator.php b/yii/validators/NumberValidator.php index e23c8162fc..3bcc6ce03f 100644 --- a/yii/validators/NumberValidator.php +++ b/yii/validators/NumberValidator.php @@ -151,7 +151,7 @@ class NumberValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RangeValidator.php b/yii/validators/RangeValidator.php index cb4c25db16..a915275526 100644 --- a/yii/validators/RangeValidator.php +++ b/yii/validators/RangeValidator.php @@ -103,7 +103,7 @@ class RangeValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.range(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/RegularExpressionValidator.php b/yii/validators/RegularExpressionValidator.php index feae920ab4..417f2bc656 100644 --- a/yii/validators/RegularExpressionValidator.php +++ b/yii/validators/RegularExpressionValidator.php @@ -112,7 +112,7 @@ class RegularExpressionValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; } } diff --git a/yii/validators/RequiredValidator.php b/yii/validators/RequiredValidator.php index 6dde13833c..aedbe051df 100644 --- a/yii/validators/RequiredValidator.php +++ b/yii/validators/RequiredValidator.php @@ -126,7 +126,7 @@ class RequiredValidator extends Validator '{value}' => $object->$attribute, ))); - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/StringValidator.php b/yii/validators/StringValidator.php index 752c9abab4..abe4634341 100644 --- a/yii/validators/StringValidator.php +++ b/yii/validators/StringValidator.php @@ -170,7 +170,7 @@ class StringValidator extends Validator $options['skipOnEmpty'] = 1; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; } } diff --git a/yii/validators/UrlValidator.php b/yii/validators/UrlValidator.php index 58afad9776..bbd8883172 100644 --- a/yii/validators/UrlValidator.php +++ b/yii/validators/UrlValidator.php @@ -138,7 +138,7 @@ class UrlValidator extends Validator $options['defaultScheme'] = $this->defaultScheme; } - $view->registerAssetBundle('yii/form'); + $view->registerAssetBundle('yii/validation'); if ($this->enableIDN) { $view->registerAssetBundle('punycode'); } From 19a74c015c2103cabbd887c2e50df1fe6a777ecc Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 16 May 2013 00:07:50 +0600 Subject: [PATCH 10/15] 'yii/form' asset bundle returned to the ActiveForm. --- yii/widgets/ActiveForm.php | 1 + 1 file changed, 1 insertion(+) diff --git a/yii/widgets/ActiveForm.php b/yii/widgets/ActiveForm.php index 0225f769e7..51b883f1b5 100644 --- a/yii/widgets/ActiveForm.php +++ b/yii/widgets/ActiveForm.php @@ -135,6 +135,7 @@ class ActiveForm extends Widget $options = Json::encode($this->getClientOptions()); $attributes = Json::encode($this->attributes); $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + $this->view->registerAssetBundle('yii/form'); } echo Html::endForm(); } From 0d4faf75a48bd7917083c75f5f8d0f5347ca4c21 Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 16 May 2013 00:09:59 +0600 Subject: [PATCH 11/15] Proper 'yii/form' location in the ActiveForm. --- yii/widgets/ActiveForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yii/widgets/ActiveForm.php b/yii/widgets/ActiveForm.php index 51b883f1b5..25a205468e 100644 --- a/yii/widgets/ActiveForm.php +++ b/yii/widgets/ActiveForm.php @@ -134,8 +134,8 @@ class ActiveForm extends Widget $id = $this->options['id']; $options = Json::encode($this->getClientOptions()); $attributes = Json::encode($this->attributes); - $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); $this->view->registerAssetBundle('yii/form'); + $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); } echo Html::endForm(); } From 9803b438181b7977c9a8e9217a52c8c0fada035a Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 16 May 2013 00:12:08 +0600 Subject: [PATCH 12/15] 'yii/validation' is not dependency of the 'yii/form' asset bundle now. --- yii/assets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yii/assets.php b/yii/assets.php index f2b0f942e5..746bf30772 100644 --- a/yii/assets.php +++ b/yii/assets.php @@ -26,7 +26,7 @@ return array( 'js' => array( 'yii.activeForm.js', ), - 'depends' => array('yii', 'yii/validation'), + 'depends' => array('yii'), ), 'yii/captcha' => array( 'sourcePath' => __DIR__ . '/assets', From 762ed2e04d35e14d63d62cf12923f623eec27231 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 15 May 2013 20:36:02 +0200 Subject: [PATCH 13/15] Display Name of HttpException instead of classname class name is alwarys HttpException, better display the name of the http error. --- yii/base/ErrorHandler.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yii/base/ErrorHandler.php b/yii/base/ErrorHandler.php index 44c2ca0e78..83407236c2 100644 --- a/yii/base/ErrorHandler.php +++ b/yii/base/ErrorHandler.php @@ -75,8 +75,11 @@ class ErrorHandler extends Component \Yii::$app->runAction($this->errorAction); } elseif (\Yii::$app instanceof \yii\web\Application) { if (!headers_sent()) { - $errorCode = $exception instanceof HttpException ? $exception->statusCode : 500; - header("HTTP/1.0 $errorCode " . get_class($exception)); + if ($exception instanceof HttpException) { + header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName()); + } else { + header('HTTP/1.0 500 ' . get_class($exception)); + } } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { \Yii::$app->renderException($exception); From a2c6d221248333d68b056902f20aeade0740b5b9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 15 May 2013 20:39:23 +0200 Subject: [PATCH 14/15] refactored web/Response::sendFile() - better throw http exception on not satisfiable range request - constitent header names - fixed range end when range request is to the end - added unit test related to #275, fixes #148 --- tests/unit/framework/web/ResponseTest.php | 86 +++++++++++++++++++++++ yii/web/Response.php | 42 +++++------ 2 files changed, 103 insertions(+), 25 deletions(-) create mode 100644 tests/unit/framework/web/ResponseTest.php diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php new file mode 100644 index 0000000000..b3d908051b --- /dev/null +++ b/tests/unit/framework/web/ResponseTest.php @@ -0,0 +1,86 @@ +reset(); + } + + protected function reset() + { + static::$headers = array(); + static::$httpResponseCode = 200; + } + + public function ranges() + { + // TODO test more cases for range requests and check for rfc compatibility + // http://www.w3.org/Protocols/rfc2616/rfc2616.txt + return array( + array('0-5', '0-5', 6, '12ёж'), + array('2-', '2-66', 65, 'ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'), + array('-12', '55-66', 12, '(ёжик)=?'), + ); + } + + /** + * @dataProvider ranges + */ + public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedFile) + { + $content = $this->generateTestFileContent(); + + $_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader; + $sent = $this->runSendFile('testFile.txt', $content, null); + $this->assertEquals($expectedFile, $sent); + $this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers)); + $this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers)); + $this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers)); + $this->assertTrue(in_array('Content-Type: text/plain', static::$headers)); + $this->assertTrue(in_array('Content-Length: ' . $length, static::$headers)); + } + + protected function generateTestFileContent() + { + return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?'; + } + + protected function runSendFile($fileName, $content, $mimeType) + { + ob_start(); + ob_implicit_flush(false); + $response = new Response(); + $response->sendFile($fileName, $content, $mimeType, false); + $file = ob_get_clean(); + return $file; + } +} \ No newline at end of file diff --git a/yii/web/Response.php b/yii/web/Response.php index 0f166f473f..954c9996cd 100644 --- a/yii/web/Response.php +++ b/yii/web/Response.php @@ -8,6 +8,7 @@ namespace yii\web; use Yii; +use yii\base\HttpException; use yii\helpers\FileHelper; use yii\helpers\Html; use yii\helpers\StringHelper; @@ -32,42 +33,37 @@ class Response extends \yii\base\Response * @param string $content content to be set. * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name. * @param boolean $terminate whether to terminate the current application after calling this method - * @todo + * @throws \yii\base\HttpException when range request is not satisfiable. */ public function sendFile($fileName, $content, $mimeType = null, $terminate = true) { - if ($mimeType === null) { - if (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null) { - $mimeType='application/octet-stream'; - } + if ($mimeType === null && (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null)) { + $mimeType = 'application/octet-stream'; } $fileSize = StringHelper::strlen($content); $contentStart = 0; $contentEnd = $fileSize - 1; - if (isset($_SERVER['HTTP_RANGE'])) { - header('Accept-Ranges: bytes'); + // tell the client that we accept range requests + header('Accept-Ranges: bytes'); - //client sent us a multibyte range, can not hold this one for now + if (isset($_SERVER['HTTP_RANGE'])) { + // client sent us a multibyte range, can not hold this one for now if (strpos(',', $_SERVER['HTTP_RANGE']) !== false) { - header('HTTP/1.1 416 Requested Range Not Satisfiable'); header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); - ob_start(); - Yii::app()->end(0,false); - ob_end_clean(); - exit(0); + throw new HttpException(416, 'Requested Range Not Satisfiable'); } $range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']); - //range requests starts from "-", so it means that data must be dumped the end point. + // range requests starts from "-", so it means that data must be dumped the end point. if ($range[0] === '-') { $contentStart = $fileSize - substr($range, 1); } else { $range = explode('-', $range); $contentStart = $range[0]; - $contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize; + $contentEnd = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $fileSize - 1; } /* Check the range and make sure it's treated according to the specs. @@ -80,12 +76,8 @@ class Response extends \yii\base\Response $wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0); if ($wrongContentStart) { - header('HTTP/1.1 416 Requested Range Not Satisfiable'); header("Content-Range: bytes $contentStart-$contentEnd/$fileSize"); - ob_start(); - Yii::app()->end(0,false); - ob_end_clean(); - exit(0); + throw new HttpException(416, 'Requested Range Not Satisfiable'); } header('HTTP/1.1 206 Partial Content'); @@ -99,17 +91,17 @@ class Response extends \yii\base\Response header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header("Content-type: $mimeType"); - header('Content-Length: '.$length); - header("Content-Disposition: attachment; filename=\"$fileName\""); + header('Content-Type: ' . $mimeType); + header('Content-Length: ' . $length); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); header('Content-Transfer-Encoding: binary'); - $content = StringHelper::strlen($content); + $content = StringHelper::substr($content, $contentStart, $length); if ($terminate) { // clean up the application first because the file downloading could take long time // which may cause timeout of some resources (such as DB connection) ob_start(); - Yii::app()->end(0,false); + Yii::$app->end(0, false); ob_end_clean(); echo $content; exit(0); From e70c98f97703bf7d17a89030e3dfd648b476199a Mon Sep 17 00:00:00 2001 From: resurtm Date: Thu, 16 May 2013 00:39:52 +0600 Subject: [PATCH 15/15] Email validator regexp typo fix. --- yii/assets/yii.validation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yii/assets/yii.validation.js b/yii/assets/yii.validation.js index c9d62bd3b4..2748a74b5e 100644 --- a/yii/assets/yii.validation.js +++ b/yii/assets/yii.validation.js @@ -139,7 +139,7 @@ yii.validation = (function ($) { var valid = true; if (options.enableIDN) { - var regexp = /^([^:]+):\/\/([^\/]+)(.*)?/, + var regexp = /^([^:]+):\/\/([^\/]+)(.*)$/, matches = regexp.exec(value); if (matches === null) { valid = false;