diff --git a/.travis.yml b/.travis.yml index 4f98df1f1..ccbd1b461 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4 before_install: # turn off XDebug diff --git a/package.json b/package.json index d6ade1f71..833585256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nette-forms", - "version": "3.0.2", + "version": "3.0.3", "description": "Client side script for Nette Forms Component", "keywords": [ "nette", diff --git a/readme.md b/readme.md index beba0388b..ba57a09ed 100644 --- a/readme.md +++ b/readme.md @@ -36,7 +36,7 @@ The recommended way to install is via Composer: composer require nette/forms ``` -It requires PHP version 7.1 and supports PHP up to 7.3. +It requires PHP version 7.1 and supports PHP up to 7.4. Usage diff --git a/src/Forms/Container.php b/src/Forms/Container.php index d2150b945..69c33b37e 100644 --- a/src/Forms/Container.php +++ b/src/Forms/Container.php @@ -26,7 +26,7 @@ class Container extends Nette\ComponentModel\Container implements \ArrayAccess private const ARRAY = 'array'; - /** @var callable[]&(callable(Container): void)[]; Occurs when the form is validated */ + /** @var callable[]&(callable(Container, mixed): void)[]; Occurs when the form is validated */ public $onValidate; /** @var ControlGroup|null */ @@ -170,7 +170,7 @@ public function validate(array $controls = null): void } foreach ($this->onValidate as $handler) { $params = Nette\Utils\Callback::toReflection($handler)->getParameters(); - $values = isset($params[1]) ? $this->getValues((string) $params[1]->getType()) : null; + $values = isset($params[1]) ? $this->getValues($params[1]->getType() ? $params[1]->getType()->getName() : null) : null; $handler($this, $values); } } diff --git a/src/Forms/Controls/UploadControl.php b/src/Forms/Controls/UploadControl.php index dbcebc172..3e5f082b2 100644 --- a/src/Forms/Controls/UploadControl.php +++ b/src/Forms/Controls/UploadControl.php @@ -104,9 +104,9 @@ public function isOk(): bool public function addRule($validator, $errorMessage = null, $arg = null) { if ($validator === Forms\Form::IMAGE) { - $this->control->accept = implode(FileUpload::IMAGE_MIME_TYPES, ', '); + $this->control->accept = implode(', ', FileUpload::IMAGE_MIME_TYPES); } elseif ($validator === Forms\Form::MIME_TYPE) { - $this->control->accept = implode((array) $arg, ', '); + $this->control->accept = implode(', ', (array) $arg); } return parent::addRule($validator, $errorMessage, $arg); } diff --git a/src/Forms/Form.php b/src/Forms/Form.php index a399628f9..8f53c8b4e 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -209,7 +209,7 @@ public function isMethod(string $method): bool public function addProtection(string $errorMessage = null): Controls\CsrfProtection { $control = new Controls\CsrfProtection($errorMessage); - $this->addComponent($control, self::PROTECTOR_ID, key($this->getComponents())); + $this->addComponent($control, self::PROTECTOR_ID, key((array) $this->getComponents())); return $control; } @@ -416,7 +416,7 @@ private function invokeHandlers(iterable $handlers, $button = null): void { foreach ($handlers as $handler) { $params = Nette\Utils\Callback::toReflection($handler)->getParameters(); - $values = isset($params[1]) ? $this->getValues((string) $params[1]->getType()) : null; + $values = isset($params[1]) ? $this->getValues($params[1]->getType() ? $params[1]->getType()->getName() : null) : null; $handler($button ?: $this, $values); if (!$this->isValid()) { return; @@ -619,7 +619,7 @@ public function __toString(): string return $this->getRenderer()->render($this); } catch (\Throwable $e) { - if (func_num_args()) { + if (func_num_args() || PHP_VERSION_ID >= 70400) { throw $e; } trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); diff --git a/src/Forms/Helpers.php b/src/Forms/Helpers.php index 55d701076..fc8973ec4 100644 --- a/src/Forms/Helpers.php +++ b/src/Forms/Helpers.php @@ -152,7 +152,7 @@ public static function createInputList(array $items, array $inputAttrs = null, a $res .= ($res === '' && $wrapperEnd === '' ? '' : $wrapper) . $labelTag . $label->attributes() . '>' . $inputTag . $input->attributes() . (Html::$xhtml ? ' />' : '>') - . ($caption instanceof Nette\Utils\IHtmlString ? $caption : htmlspecialchars($caption, ENT_NOQUOTES, 'UTF-8')) + . ($caption instanceof Nette\Utils\IHtmlString ? $caption : htmlspecialchars((string) $caption, ENT_NOQUOTES, 'UTF-8')) . '' . $wrapperEnd; } diff --git a/src/Forms/Validator.php b/src/Forms/Validator.php index 0b0436a68..1b8157510 100644 --- a/src/Forms/Validator.php +++ b/src/Forms/Validator.php @@ -69,9 +69,12 @@ public static function formatMessage(Rule $rule, bool $withValue = true) static $i = -1; switch ($m[1]) { case 'name': return $rule->control->getName(); - case 'label': return $rule->control instanceof Controls\BaseControl - ? rtrim($rule->control->translate($rule->control->getCaption()), ':') - : null; + case 'label': + if ($rule->control instanceof Controls\BaseControl) { + $caption = $rule->control->translate($rule->control->getCaption()); + return rtrim($caption instanceof Nette\Utils\Html ? $caption->getText() : $caption, ':'); + } + return ''; case 'value': return $withValue ? $rule->control->getValue() : $m[0]; default: $args = is_array($rule->arg) ? $rule->arg : [$rule->arg]; @@ -253,7 +256,7 @@ public static function validateUrl(IControl $control): bool */ public static function validatePattern(IControl $control, string $pattern, bool $caseInsensitive = false): bool { - $regexp = "\x01^(?:$pattern)\\z\x01u" . ($caseInsensitive ? 'i' : ''); + $regexp = "\x01^(?:$pattern)$\x01Du" . ($caseInsensitive ? 'i' : ''); foreach (static::toArray($control->getValue()) as $item) { $value = $item instanceof Nette\Http\FileUpload ? $item->getName() : $item; if (!Strings::match((string) $value, $regexp)) { @@ -275,7 +278,7 @@ public static function validatePatternCaseInsensitive(IControl $control, string */ public static function validateNumeric(IControl $control): bool { - return (bool) Strings::match($control->getValue(), '#^\d+\z#'); + return (bool) Strings::match($control->getValue(), '#^\d+$#D'); } diff --git a/src/assets/netteForms.js b/src/assets/netteForms.js index de4ec42d5..539c3e8ec 100644 --- a/src/assets/netteForms.js +++ b/src/assets/netteForms.js @@ -29,6 +29,8 @@ 'use strict'; var Nette = {}; + var preventFiltering = {}; + var formToggles = {}; Nette.formErrors = []; Nette.version = '3.0'; @@ -119,10 +121,12 @@ val = ''; } } - if (filter) { + if (filter && preventFiltering[elem.name] === undefined) { + preventFiltering[elem.name] = true; var ref = {value: val}; Nette.validateControl(elem, null, true, ref); val = ref.value; + delete preventFiltering[elem.name]; } return val; }; @@ -296,8 +300,6 @@ }; - var preventFiltering = false; - /** * Validates single rule. */ @@ -311,16 +313,13 @@ op = op.replace(/\\/g, ''); var arr = Array.isArray(arg) ? arg.slice(0) : [arg]; - if (!preventFiltering) { - preventFiltering = true; - for (var i = 0, len = arr.length; i < len; i++) { - if (arr[i] && arr[i].control) { - var control = elem.form.elements.namedItem(arr[i].control); - arr[i] = control === elem ? value.value : Nette.getEffectiveValue(control, true); - } + for (var i = 0, len = arr.length; i < len; i++) { + if (arr[i] && arr[i].control) { + var control = elem.form.elements.namedItem(arr[i].control); + arr[i] = control === elem ? value.value : Nette.getEffectiveValue(control, true); } - preventFiltering = false; } + return Nette.validators[op] ? Nette.validators[op](elem, Array.isArray(arg) ? arr : arr[0], value.value, value) : null; @@ -560,15 +559,15 @@ */ Nette.toggleForm = function(form, elem) { var i; - Nette.toggles = {}; + formToggles = {}; for (i = 0; i < form.elements.length; i++) { if (form.elements[i].tagName.toLowerCase() in {input: 1, select: 1, textarea: 1, button: 1}) { Nette.toggleControl(form.elements[i], null, null, !elem); } } - for (i in Nette.toggles) { - Nette.toggle(i, Nette.toggles[i], elem); + for (i in formToggles) { + Nette.toggle(i, formToggles[i], elem); } }; @@ -627,7 +626,7 @@ } for (var id2 in rule.toggle || []) { if (Object.prototype.hasOwnProperty.call(rule.toggle, id2)) { - Nette.toggles[id2] = Nette.toggles[id2] || (rule.toggle[id2] ? curSuccess : !curSuccess); + formToggles[id2] = formToggles[id2] || (rule.toggle[id2] ? curSuccess : !curSuccess); } } } diff --git a/src/assets/netteForms.min.js b/src/assets/netteForms.min.js index c33662626..b29f62c3c 100644 --- a/src/assets/netteForms.min.js +++ b/src/assets/netteForms.min.js @@ -1,20 +1,21 @@ /*! netteForms.js | (c) 2004 David Grudl (https://davidgrudl.com) */ -(function(e,d){if(e.JSON)if("function"===typeof define&&define.amd)define(function(){return d(e)});else if("object"===typeof module&&"object"===typeof module.exports)module.exports=d(e);else{var q=!e.Nette||!e.Nette.noInit;e.Nette=d(e);q&&e.Nette.initOnLoad()}})("undefined"!==typeof window?window:this,function(e){var d={formErrors:[],version:"3.0",onDocumentReady:function(a){"loading"!==document.readyState?a.call(this):document.addEventListener("DOMContentLoaded",a)},getValue:function(a){var c;if(a){if(a.tagName){if("radio"=== -a.type){var b=a.form.elements;for(c=0;cc?null:b[c].value;for(c=0;cb.indexOf(p)&&(b.push(p),!d&&h.focus&&(d=h))}b.length&&(alert(b.join("\n")),d&&d.focus())}},q=!1;d.validateRule=function(a,c,b,f){f=void 0===f?{value:d.getEffectiveValue(a,!0)}:f;":"===c.charAt(0)&&(c=c.substr(1)); -c=c.replace("::","_");c=c.replace(/\\/g,"");var e=Array.isArray(b)?b.slice(0):[b];if(!q){q=!0;for(var h=0,p=e.length;h=c},maxLength:function(a,c,b){if("number"===a.type){if(a.validity.tooLong)return!1;if(a.validity.badInput)return null}return b.length<=c},length:function(a,c,b){if("number"===a.type){if(a.validity.tooShort||a.validity.tooLong)return!1;if(a.validity.badInput)return null}c=Array.isArray(c)?c:[c,c];return(null===c[0]||b.length>=c[0])&&(null===c[1]||b.length<=c[1])},email:function(a,c,b){return/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i.test(b)}, +(function(e,d){if(e.JSON)if("function"===typeof define&&define.amd)define(function(){return d(e)});else if("object"===typeof module&&"object"===typeof module.exports)module.exports=d(e);else{var q=!e.Nette||!e.Nette.noInit;e.Nette=d(e);q&&e.Nette.initOnLoad()}})("undefined"!==typeof window?window:this,function(e){var d={},q={},t={};d.formErrors=[];d.version="3.0";d.onDocumentReady=function(a){"loading"!==document.readyState?a.call(this):document.addEventListener("DOMContentLoaded",a)};d.getValue= +function(a){var c;if(a){if(a.tagName){if("radio"===a.type){var b=a.form.elements;for(c=0;cc?null:b[c].value;for(c=0;cb.indexOf(p)&& +(b.push(p),!d&&h.focus&&(d=h))}b.length&&(alert(b.join("\n")),d&&d.focus())};d.validateRule=function(a,c,b,f){f=void 0===f?{value:d.getEffectiveValue(a,!0)}:f;":"===c.charAt(0)&&(c=c.substr(1));c=c.replace("::","_");c=c.replace(/\\/g,"");for(var e=Array.isArray(b)?b.slice(0):[b],h=0,p=e.length;h=c},maxLength:function(a,c,b){if("number"===a.type){if(a.validity.tooLong)return!1;if(a.validity.badInput)return null}return b.length<=c},length:function(a,c,b){if("number"===a.type){if(a.validity.tooShort|| +a.validity.tooLong)return!1;if(a.validity.badInput)return null}c=Array.isArray(c)?c:[c,c];return(null===c[0]||b.length>=c[0])&&(null===c[1]||b.length<=c[1])},email:function(a,c,b){return/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i.test(b)}, url:function(a,c,b,d){/^[a-z\d+.-]+:/.test(b)||(b="http://"+b);return/^https?:\/\/((([-_0-9a-z\u00C0-\u02FF\u0370-\u1EFF]+\.)*[0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)?[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-f:]{3,39}\])(:\d{1,5})?(\/\S*)?$/i.test(b)?(d.value=b,!0):!1},regexp:function(a,c,b){a="string"===typeof c?c.match(/^\/(.*)\/([imu]*)$/): !1;try{return a&&(new RegExp(a[1],a[2].replace("u",""))).test(b)}catch(f){}},pattern:function(a,c,b,d,l){if("string"!==typeof c)return null;try{try{var f=new RegExp("^(?:"+c+")$",l?"ui":"u")}catch(p){f=new RegExp("^(?:"+c+")$",l?"i":"")}if(e.FileList&&b instanceof FileList){for(a=0;a=c},max:function(a,c,b){if("number"===a.type){if(a.validity.rangeOverflow)return!1; if(a.validity.badInput)return null}return null===c||parseFloat(b)<=c},range:function(a,c,b){if("number"===a.type){if(a.validity.rangeUnderflow||a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return Array.isArray(c)?(null===c[0]||parseFloat(b)>=c[0])&&(null===c[1]||parseFloat(b)<=c[1]):null},submitted:function(a){return a.form["nette-submittedBy"]===a},fileSize:function(a,c,b){if(e.FileList)for(a=0;ac)return!1;return!0},image:function(a,c,b){if(e.FileList&& -b instanceof e.FileList)for(a=0;ap.indexOf(n[r])&&(n[r].addEventListener("change",g),p.push(n[r]))}for(var u in m.toggle||[])Object.prototype.hasOwnProperty.call(m.toggle,u)&&(d.toggles[u]=d.toggles[u]||(m.toggle[u]?k:!k))}}}return f};d.toggle=function(a,c,b){/^\w[\w.:-]*$/.test(a)&&(a="#"+a);a=document.querySelectorAll(a);for(b=0;bp.indexOf(n[r])&& +(n[r].addEventListener("change",g),p.push(n[r]))}for(var v in m.toggle||[])Object.prototype.hasOwnProperty.call(m.toggle,v)&&(t[v]=t[v]||(m.toggle[v]?k:!k))}}}return f};d.toggle=function(a,c,b){/^\w[\w.:-]*$/.test(a)&&(a="#"+a);a=document.querySelectorAll(a);for(b=0;baddText('text', Html::el('b', 'Label:')) + ->addRule(Form::REQUIRED, 'Please fill in %label'); + + Assert::same('', (string) $input->getLabel()); + Assert::same('', (string) $input->getLabel(Html::el('b', 'Another label'))); + Assert::type(Html::class, $input->getControl()); + Assert::same('', (string) $input->getControl()); +}); + + test(function () { // password $form = new Form; $input = $form->addPassword('password') diff --git a/tests/Forms/Helpers.createInputList.phpt b/tests/Forms/Helpers.createInputList.phpt index 84ad0116a..e9f08a674 100644 --- a/tests/Forms/Helpers.createInputList.phpt +++ b/tests/Forms/Helpers.createInputList.phpt @@ -25,6 +25,11 @@ test(function () { Helpers::createInputList(['a']) ); + Assert::same( + '', + Helpers::createInputList([1]) + ); + Assert::same( '', Helpers::createInputList(