├── LICENSE.md ├── README.md ├── config └── jsvalidation.php ├── public └── js │ ├── jsvalidation.js │ ├── jsvalidation.js.map │ ├── jsvalidation.min.js │ └── jsvalidation.min.js.map ├── resources ├── assets │ └── js │ │ ├── helpers.js │ │ ├── jsvalidation.js │ │ ├── timezones.js │ │ └── validations.js └── views │ ├── bootstrap.php │ ├── bootstrap4.php │ ├── bootstrap5.php │ └── uikit.php └── src ├── Exceptions └── PropertyNotFoundException.php ├── Facades └── JsValidatorFacade.php ├── Javascript ├── JavascriptRulesTrait.php ├── JavascriptValidator.php ├── MessageParser.php ├── RuleParser.php └── ValidatorHandler.php ├── JsValidationServiceProvider.php ├── JsValidatorFactory.php ├── Remote ├── FormRequest.php ├── Resolver.php └── Validator.php ├── RemoteValidationMiddleware.php └── Support ├── AccessProtectedTrait.php ├── DelegatedValidator.php ├── RuleListTrait.php ├── UseDelegatedValidatorTrait.php └── ValidationRuleParserProxy.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Albert Moreno 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Laravel Javascript Validation 2 | 3 | ![Latest Version](https://img.shields.io/github/release/proengsoft/laravel-jsvalidation.svg?style=flat-square) 4 | ![tests](https://github.com/proengsoft/laravel-jsvalidation/actions/workflows/php-tests.yml/badge.svg) 5 | ![Code Coverage](https://app.codecov.io/gh/proengsoft/laravel-jsvalidation) 6 | ![Total Downloads](https://img.shields.io/packagist/dt/proengsoft/laravel-jsvalidation.svg?style=flat-square) 7 | ![License](https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square) 8 | 9 | **Laravel Javascript Validation** package allows to reuse your Laravel [Validation Rules][], [Messages][], [FormRequest][] and [Validators][] to validate forms automatically in client side without need to write any Javascript code or use HTML Builder Class. 10 | 11 | You can validate forms automatically referencing it to your defined validations. The messages are loaded from your validations and translated according your Localization preferences. 12 | 13 | #### Supported versions 14 | 15 | Laravel 9.x - 11.x 16 | 17 | #### Feature overview 18 | 19 | - Automatic creation of Javascript validation based on your [Validation Rules][] or [FormRequest][], no Javascript coding required. 20 | - Supports other validation packages. 21 | - AJAX validation for [ActiveURL][], [Unique][] and [Exists][] Rules, [Custom Validation Rules][] and other validation packages 22 | - Unobtrusive integration, you can use without Laravel Form Builder 23 | - The package uses [Jquery Validation Plugin][] bundled in provided script. 24 | - Uses Laravel Localization to translate messages. 25 | 26 | #### Supported Rules 27 | 28 | **Almost all [Validation Rules][] provided by Laravel and other packages are supported**. 29 | 30 | Almost are validated in client-side using Javascript, but in some cases, the validation should to be done in server-side via AJAX: 31 | - [ActiveURL][] 32 | - [Unique][] 33 | - [Exists][] 34 | - [Custom Validation Rules][] 35 | - Validations provided by other packages 36 | 37 | ##### Unsupported Rules 38 | 39 | Some Laravel validations are not implemented yet. 40 | 41 | - [Present][] 42 | - [DateFormat][] rule don't support timezone format 43 | 44 | #### Getting started 45 | 46 | The easiest way to create Javascript validations is using [Laravel Form Request Validation][]. 47 | 48 | ##### Installation 49 | 50 | Follow the [Installation Guide][] to install the package. **The default config should work out-of-box** 51 | 52 | ##### Validating Form Request 53 | 54 | Call [JsValidator Facade][] in your view to validate any [FormRequest](https://laravel.com/docs/master/validation) 55 | 56 | ```html 57 |
58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {!! JsValidator::formRequest('App\Http\Requests\MyFormRequest') !!} 69 | ``` 70 | 71 | Take a look to [Basic Usage](https://github.com/proengsoft/laravel-jsvalidation/wiki/Basic-Usage) or [Examples](https://github.com/proengsoft/laravel-jsvalidation/wiki/Validating-Examples) to get more information. 72 | 73 | #### Documentation 74 | 75 | **To get more info refer to [Project Wiki](https://github.com/proengsoft/laravel-jsvalidation/wiki/Home)** 76 | 77 | #### Changelog 78 | 79 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 80 | 81 | #### Contributing 82 | 83 | Please see [CONTRIBUTING][] for details. 84 | 85 | #### Credits 86 | 87 | [Laravel Javascript Validation contributors list](../../contributors) 88 | 89 | #### License 90 | 91 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 92 | 93 | [ActiveURL]: https://laravel.com/docs/5.4/validation#rule-active-url 94 | [CONTRIBUTING]: https://github.com/proengsoft/laravel-jsvalidation/wiki/Contributing 95 | [Custom Validations]: https://laravel.com/docs/5.4/validation#custom-validation-rules 96 | [Custom Validation Rules]: https://laravel.com/docs/5.4/validation#custom-validation-rules 97 | [DateFormat]: https://laravel.com/docs/5.4/validation#rule-date-format 98 | [Exists]: https://laravel.com/docs/5.4/validation#rule-exists 99 | [FormRequest]: https://laravel.com/docs/5.4/validation#form-request-validation 100 | [Installation Guide]: https://github.com/proengsoft/laravel-jsvalidation/wiki/Installation 101 | [JsValidator Facade]: https://github.com/proengsoft/laravel-jsvalidation/wiki/Facade 102 | [JQueryValidation]: https://jqueryvalidation.org/ 103 | [JQuery Validation Plugin]: https://jqueryvalidation.org/ 104 | [Laravel Form Request Validation]: http://laravel.com/docs/5.4/validation#form-request-validation 105 | [Laravel Localization]: https://laravel.com/docs/5.4/localization 106 | [Messages]: https://laravel.com/docs/5.4/validation#error-messages-and-views 107 | [Present]: https://laravel.com/docs/5.4/validation#rule-present 108 | [Unique]: https://laravel.com/docs/5.4/validation#rule-unique 109 | [Validation]: https://laravel.com/docs/5.4/validation 110 | [Validation Rules]: https://laravel.com/docs/5.4/validation#available-validation-rules 111 | [Validators]: https://laravel.com/docs/5.4/validation#form-request-validation 112 | -------------------------------------------------------------------------------- /config/jsvalidation.php: -------------------------------------------------------------------------------- 1 | 'jsvalidation::bootstrap', 11 | 12 | /* 13 | * Default JQuery selector find the form to be validated. 14 | * By default, the validations are applied to all forms. 15 | */ 16 | 'form_selector' => 'form', 17 | 18 | /* 19 | * If you change the focus on detect some error then active 20 | * this parameter to move the focus to the first error found. 21 | */ 22 | 'focus_on_error' => false, 23 | 24 | /* 25 | * Duration time for the animation when We are moving the focus 26 | * to the first error, http://api.jquery.com/animate/ for more information. 27 | */ 28 | 'duration_animate' => 1000, 29 | 30 | /* 31 | * Enable or disable Ajax validations of Database and custom rules. 32 | * By default Unique, ActiveURL, Exists and custom validations are validated via AJAX 33 | */ 34 | 'disable_remote_validation' => false, 35 | 36 | /* 37 | * Field name used in the remote validation Ajax request 38 | * You can change this value to avoid conflicts wth your field names 39 | */ 40 | 'remote_validation_field' => '_jsvalidation', 41 | 42 | /* 43 | * Whether to escape all validation messages with htmlentities. 44 | */ 45 | 'escape' => true, 46 | 47 | /* 48 | * Set a default value for the validate ignore property. 49 | * 50 | * See https://jqueryvalidation.org/validate/#ignore 51 | */ 52 | 'ignore' => ":hidden, [contenteditable='true']", 53 | ]; 54 | -------------------------------------------------------------------------------- /resources/assets/js/helpers.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Laravel Javascript Validation 3 | * 4 | * https://github.com/proengsoft/laravel-jsvalidation 5 | * 6 | * Helper functions used by validators 7 | * 8 | * Copyright (c) 2017 Proengsoft 9 | * Released under the MIT license 10 | */ 11 | 12 | import strlen from 'locutus/php/strings/strlen'; 13 | import array_diff from 'locutus/php/array/array_diff'; 14 | import strtotime from 'locutus/php/datetime/strtotime'; 15 | import is_numeric from 'locutus/php/var/is_numeric'; 16 | 17 | $.extend(true, laravelValidation, { 18 | 19 | helpers: { 20 | 21 | /** 22 | * Numeric rules 23 | */ 24 | numericRules: ['Integer', 'Numeric'], 25 | 26 | /** 27 | * Gets the file information from file input. 28 | * 29 | * @param fieldObj 30 | * @param index 31 | * @returns {{file: *, extension: string, size: number}} 32 | */ 33 | fileinfo: function (fieldObj, index) { 34 | var FileName = fieldObj.value; 35 | index = typeof index !== 'undefined' ? index : 0; 36 | if ( fieldObj.files !== null ) { 37 | if (typeof fieldObj.files[index] !== 'undefined') { 38 | return { 39 | file: FileName, 40 | extension: FileName.substr(FileName.lastIndexOf('.') + 1), 41 | size: fieldObj.files[index].size / 1024, 42 | type: fieldObj.files[index].type 43 | }; 44 | } 45 | } 46 | return false; 47 | }, 48 | 49 | 50 | /** 51 | * Gets the selectors for th specified field names. 52 | * 53 | * @param names 54 | * @returns {string} 55 | */ 56 | selector: function (names) { 57 | var selector = []; 58 | if (! this.isArray(names)) { 59 | names = [names]; 60 | } 61 | for (var i = 0; i < names.length; i++) { 62 | selector.push("[name='" + names[i] + "']"); 63 | } 64 | return selector.join(); 65 | }, 66 | 67 | 68 | /** 69 | * Check if element has numeric rules. 70 | * 71 | * @param element 72 | * @returns {boolean} 73 | */ 74 | hasNumericRules: function (element) { 75 | return this.hasRules(element, this.numericRules); 76 | }, 77 | 78 | /** 79 | * Check if element has passed rules. 80 | * 81 | * @param element 82 | * @param rules 83 | * @returns {boolean} 84 | */ 85 | hasRules: function (element, rules) { 86 | 87 | var found = false; 88 | if (typeof rules === 'string') { 89 | rules = [rules]; 90 | } 91 | 92 | var validator = $.data(element.form, "validator"); 93 | var listRules = []; 94 | var cache = validator.arrayRulesCache; 95 | if (element.name in cache) { 96 | $.each(cache[element.name], function (index, arrayRule) { 97 | listRules.push(arrayRule); 98 | }); 99 | } 100 | if (element.name in validator.settings.rules) { 101 | listRules.push(validator.settings.rules[element.name]); 102 | } 103 | $.each(listRules, function(index,objRules){ 104 | if ('laravelValidation' in objRules) { 105 | var _rules=objRules.laravelValidation; 106 | for (var i = 0; i < _rules.length; i++) { 107 | if ($.inArray(_rules[i][0],rules) !== -1) { 108 | found = true; 109 | return false; 110 | } 111 | } 112 | } 113 | }); 114 | 115 | return found; 116 | }, 117 | 118 | /** 119 | * Return the string length using PHP function. 120 | * http://php.net/manual/en/function.strlen.php 121 | * http://phpjs.org/functions/strlen/ 122 | * 123 | * @param string 124 | */ 125 | strlen: function (string) { 126 | return strlen(string); 127 | }, 128 | 129 | /** 130 | * Get the size of the object depending of his type. 131 | * 132 | * @param obj 133 | * @param element 134 | * @param value 135 | * @returns int 136 | */ 137 | getSize: function getSize(obj, element, value) { 138 | 139 | if (this.hasNumericRules(element) && this.is_numeric(value)) { 140 | return parseFloat(value); 141 | } else if (this.isArray(value)) { 142 | return parseFloat(value.length); 143 | } else if (element.type === 'file') { 144 | return parseFloat(Math.floor(this.fileinfo(element).size)); 145 | } 146 | 147 | return parseFloat(this.strlen(value)); 148 | }, 149 | 150 | 151 | /** 152 | * Return specified rule from element. 153 | * 154 | * @param rule 155 | * @param element 156 | * @returns object 157 | */ 158 | getLaravelValidation: function(rule, element) { 159 | 160 | var found = undefined; 161 | $.each($.validator.staticRules(element), function(key, rules) { 162 | if (key==="laravelValidation") { 163 | $.each(rules, function (i, value) { 164 | if (value[0]===rule) { 165 | found=value; 166 | } 167 | }); 168 | } 169 | }); 170 | 171 | return found; 172 | }, 173 | 174 | /** 175 | * Return he timestamp of value passed using format or default format in element. 176 | * 177 | * @param value 178 | * @param format 179 | * @returns {boolean|int} 180 | */ 181 | parseTime: function (value, format) { 182 | 183 | var timeValue = false; 184 | var fmt = new DateFormatter(); 185 | 186 | if (typeof value === 'number' && typeof format === 'undefined') { 187 | return value; 188 | } 189 | 190 | if (typeof format === 'object') { 191 | var dateRule = this.getLaravelValidation('DateFormat', format); 192 | if (dateRule !== undefined) { 193 | format = dateRule[1][0]; 194 | } else { 195 | format = null; 196 | } 197 | } 198 | 199 | if (format == null) { 200 | timeValue = this.strtotime(value); 201 | } else { 202 | timeValue = fmt.parseDate(value, format); 203 | if (timeValue instanceof Date && fmt.formatDate(timeValue, format) === value) { 204 | timeValue = Math.round((timeValue.getTime() / 1000)); 205 | } else { 206 | timeValue = false; 207 | } 208 | } 209 | 210 | return timeValue; 211 | }, 212 | 213 | /** 214 | * Compare a given date against another using an operator. 215 | * 216 | * @param validator 217 | * @param value 218 | * @param element 219 | * @param params 220 | * @param operator 221 | * @return {boolean} 222 | */ 223 | compareDates: function (validator, value, element, params, operator) { 224 | 225 | var timeCompare = this.parseTime(params); 226 | 227 | if (!timeCompare) { 228 | var target = this.dependentElement(validator, element, params); 229 | if (target === undefined) { 230 | return false; 231 | } 232 | timeCompare = this.parseTime(validator.elementValue(target), target); 233 | } 234 | 235 | var timeValue = this.parseTime(value, element); 236 | if (timeValue === false) { 237 | return false; 238 | } 239 | 240 | switch (operator) { 241 | case '<': 242 | return timeValue < timeCompare; 243 | 244 | case '<=': 245 | return timeValue <= timeCompare; 246 | 247 | case '==': 248 | case '===': 249 | return timeValue === timeCompare; 250 | 251 | case '>': 252 | return timeValue > timeCompare; 253 | 254 | case '>=': 255 | return timeValue >= timeCompare; 256 | 257 | default: 258 | throw new Error('Unsupported operator.'); 259 | } 260 | }, 261 | 262 | /** 263 | * This method allows you to intelligently guess the date by closely matching the specific format. 264 | * 265 | * @param value 266 | * @param format 267 | * @returns {Date} 268 | */ 269 | guessDate: function (value, format) { 270 | var fmt = new DateFormatter(); 271 | return fmt.guessDate(value, format) 272 | }, 273 | 274 | /** 275 | * Returns Unix timestamp based on PHP function strototime. 276 | * http://php.net/manual/es/function.strtotime.php 277 | * http://phpjs.org/functions/strtotime/ 278 | * 279 | * @param text 280 | * @param now 281 | * @returns {*} 282 | */ 283 | strtotime: function (text, now) { 284 | return strtotime(text, now) 285 | }, 286 | 287 | /** 288 | * Returns if value is numeric. 289 | * http://php.net/manual/es/var.is_numeric.php 290 | * http://phpjs.org/functions/is_numeric/ 291 | * 292 | * @param mixed_var 293 | * @returns {*} 294 | */ 295 | is_numeric: function (mixed_var) { 296 | return is_numeric(mixed_var) 297 | }, 298 | 299 | /** 300 | * Check whether the argument is of type Array. 301 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Polyfill 302 | * 303 | * @param arg 304 | * @returns {boolean} 305 | */ 306 | isArray: function(arg) { 307 | return Object.prototype.toString.call(arg) === '[object Array]'; 308 | }, 309 | 310 | /** 311 | * Returns Array diff based on PHP function array_diff. 312 | * http://php.net/manual/es/function.array_diff.php 313 | * http://phpjs.org/functions/array_diff/ 314 | * 315 | * @param arr1 316 | * @param arr2 317 | * @returns {*} 318 | */ 319 | arrayDiff: function (arr1, arr2) { 320 | return array_diff(arr1, arr2); 321 | }, 322 | 323 | /** 324 | * Check whether two arrays are equal to one another. 325 | * 326 | * @param arr1 327 | * @param arr2 328 | * @returns {*} 329 | */ 330 | arrayEquals: function (arr1, arr2) { 331 | if (! this.isArray(arr1) || ! this.isArray(arr2)) { 332 | return false; 333 | } 334 | 335 | if (arr1.length !== arr2.length) { 336 | return false; 337 | } 338 | 339 | return $.isEmptyObject(this.arrayDiff(arr1, arr2)); 340 | }, 341 | 342 | /** 343 | * Makes element dependant from other. 344 | * 345 | * @param validator 346 | * @param element 347 | * @param name 348 | * @returns {*} 349 | */ 350 | dependentElement: function(validator, element, name) { 351 | var el = validator.findByName(name); 352 | 353 | var targetElement = el[el.length - 1]; 354 | 355 | if (targetElement !== undefined && validator.settings.onfocusout) { 356 | var event = 'blur'; 357 | if ( 358 | targetElement.tagName === 'SELECT' || 359 | targetElement.tagName === 'OPTION' || 360 | targetElement.type === 'checkbox' || 361 | targetElement.type === 'radio' 362 | ) { 363 | event = 'click'; 364 | } 365 | 366 | var ruleName = '.validate-laravelValidation'; 367 | $(targetElement) 368 | .off(ruleName) 369 | .off(event + ruleName + '-' + element.name) 370 | .on(event + ruleName + '-' + element.name, function () { 371 | $(element).valid(); 372 | }); 373 | } 374 | 375 | return targetElement; 376 | }, 377 | 378 | /** 379 | * Parses error Ajax response and gets the message. 380 | * 381 | * @param response 382 | * @returns {string[]} 383 | */ 384 | parseErrorResponse: function (response) { 385 | var newResponse = ['Whoops, looks like something went wrong.']; 386 | if ('responseText' in response) { 387 | var errorMsg = response.responseText.match(/(.*)<\/h1\s*>/i); 388 | if (this.isArray(errorMsg)) { 389 | newResponse = [errorMsg[1]]; 390 | } 391 | } 392 | return newResponse; 393 | }, 394 | 395 | /** 396 | * Escape string to use as Regular Expression. 397 | * 398 | * @param str 399 | * @returns string 400 | */ 401 | escapeRegExp: function (str) { 402 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 403 | }, 404 | 405 | /** 406 | * Generate RegExp from wildcard attributes. 407 | * 408 | * @param name 409 | * @returns {RegExp} 410 | */ 411 | regexFromWildcard: function (name) { 412 | var nameParts = name.split('[*]'); 413 | if (nameParts.length === 1) nameParts.push(''); 414 | 415 | return new RegExp('^' + nameParts.map(function(x) { 416 | return laravelValidation.helpers.escapeRegExp(x) 417 | }).join('\\[[^\\]]*\\]') + '$'); 418 | }, 419 | 420 | /** 421 | * Merge additional laravel validation rules into the current rule set. 422 | * 423 | * @param {object} rules 424 | * @param {object} newRules 425 | * @returns {object} 426 | */ 427 | mergeRules: function (rules, newRules) { 428 | var rulesList = { 429 | 'laravelValidation': newRules.laravelValidation || [], 430 | 'laravelValidationRemote': newRules.laravelValidationRemote || [] 431 | }; 432 | 433 | for (var key in rulesList) { 434 | if (rulesList[key].length === 0) { 435 | continue; 436 | } 437 | 438 | if (typeof rules[key] === "undefined") { 439 | rules[key] = []; 440 | } 441 | 442 | rules[key] = rules[key].concat(rulesList[key]); 443 | } 444 | 445 | return rules; 446 | }, 447 | 448 | /** 449 | * HTML entity encode a string. 450 | * 451 | * @param string 452 | * @returns {string} 453 | */ 454 | encode: function (string) { 455 | return $('
').text(string).html(); 456 | }, 457 | 458 | /** 459 | * Lookup name in an array. 460 | * 461 | * @param validator 462 | * @param {string} name Name in dot notation format. 463 | * @returns {*} 464 | */ 465 | findByArrayName: function (validator, name) { 466 | var sqName = name.replace(/\.([^\.]+)/g, '[$1]'), 467 | lookups = [ 468 | // Convert dot to square brackets. e.g. foo.bar.0 becomes foo[bar][0] 469 | sqName, 470 | // Append [] to the name e.g. foo becomes foo[] or foo.bar.0 becomes foo[bar][0][] 471 | sqName + '[]', 472 | // Remove key from last array e.g. foo[bar][0] becomes foo[bar][] 473 | sqName.replace(/(.*)\[(.*)\]$/g, '$1[]') 474 | ]; 475 | 476 | for (var i = 0; i < lookups.length; i++) { 477 | var elem = validator.findByName(lookups[i]); 478 | if (elem.length > 0) { 479 | return elem; 480 | } 481 | } 482 | 483 | return $(null); 484 | }, 485 | 486 | /** 487 | * Attempt to find an element in the DOM matching the given name. 488 | * Example names include: 489 | * - domain.0 which matches domain[] 490 | * - customfield.3 which matches customfield[3] 491 | * 492 | * @param validator 493 | * @param {string} name 494 | * @returns {*} 495 | */ 496 | findByName: function (validator, name) { 497 | // Exact match. 498 | var elem = validator.findByName(name); 499 | if (elem.length > 0) { 500 | return elem; 501 | } 502 | 503 | // Find name in data, using dot notation. 504 | var delim = '.', 505 | parts = name.split(delim); 506 | for (var i = parts.length; i > 0; i--) { 507 | var reconstructed = []; 508 | for (var c = 0; c < i; c++) { 509 | reconstructed.push(parts[c]); 510 | } 511 | 512 | elem = this.findByArrayName(validator, reconstructed.join(delim)); 513 | if (elem.length > 0) { 514 | return elem; 515 | } 516 | } 517 | 518 | return $(null); 519 | }, 520 | 521 | /** 522 | * If it's an array element, get all values. 523 | * 524 | * @param validator 525 | * @param element 526 | * @returns {*|string} 527 | */ 528 | allElementValues: function (validator, element) { 529 | if (element.name.indexOf('[]') !== -1) { 530 | return validator.findByName(element.name).map(function (i, e) { 531 | return validator.elementValue(e); 532 | }).get(); 533 | } 534 | 535 | return validator.elementValue(element); 536 | } 537 | } 538 | }); 539 | -------------------------------------------------------------------------------- /resources/assets/js/jsvalidation.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Laravel Javascript Validation 3 | * 4 | * https://github.com/proengsoft/laravel-jsvalidation 5 | * 6 | * Copyright (c) 2017 Proengsoft 7 | * Released under the MIT license 8 | */ 9 | 10 | var laravelValidation; 11 | laravelValidation = { 12 | 13 | implicitRules: ['Required','Confirmed'], 14 | 15 | /** 16 | * Initialize laravel validations. 17 | */ 18 | init: function () { 19 | 20 | // jquery-validation requires the field under validation to be present. We're adding a dummy hidden 21 | // field so that any errors are not visible. 22 | var constructor = $.fn.validate; 23 | $.fn.validate = function( options ) { 24 | var name = 'proengsoft_jsvalidation'; // must match the name defined in JsValidatorFactory.newFormRequestValidator 25 | var $elm = $(this).find('input[name="' + name + '"]'); 26 | if ($elm.length === 0) { 27 | $('').attr({type: 'hidden', name: name}).appendTo(this); 28 | } 29 | 30 | return constructor.apply(this, [options]); 31 | }; 32 | 33 | // Disable class rules and attribute rules 34 | $.validator.classRuleSettings = {}; 35 | $.validator.attributeRules = function () {}; 36 | 37 | $.validator.dataRules = this.arrayRules; 38 | $.validator.prototype.arrayRulesCache = {}; 39 | 40 | // Register validations methods 41 | this.setupValidations(); 42 | }, 43 | 44 | arrayRules: function(element) { 45 | 46 | var rules = {}, 47 | validator = $.data( element.form, "validator"), 48 | cache = validator.arrayRulesCache; 49 | 50 | // Is not an Array 51 | if (element.name.indexOf('[') === -1) { 52 | return rules; 53 | } 54 | 55 | if (! (element.name in cache)) { 56 | cache[element.name] = {}; 57 | } 58 | 59 | $.each(validator.settings.rules, function(name, tmpRules) { 60 | if (name in cache[element.name]) { 61 | rules = laravelValidation.helpers.mergeRules(rules, cache[element.name][name]); 62 | } else { 63 | cache[element.name][name] = {}; 64 | 65 | var nameRegExp = laravelValidation.helpers.regexFromWildcard(name); 66 | if (element.name.match(nameRegExp)) { 67 | var newRules = $.validator.normalizeRule(tmpRules) || {}; 68 | cache[element.name][name] = newRules; 69 | 70 | rules = laravelValidation.helpers.mergeRules(rules, newRules); 71 | } 72 | } 73 | }); 74 | 75 | return rules; 76 | }, 77 | 78 | setupValidations: function () { 79 | 80 | /** 81 | * Get CSRF token. 82 | * 83 | * @param params 84 | * @returns {string} 85 | */ 86 | var getCsrfToken = function (params) { 87 | return params[0][1][1]; 88 | }; 89 | 90 | /** 91 | * Whether to validate all attributes. 92 | * 93 | * @param params 94 | * @returns {boolean} 95 | */ 96 | var isValidateAll = function (params) { 97 | return params[0][1][2]; 98 | }; 99 | 100 | /** 101 | * Determine whether the rule is implicit. 102 | * 103 | * @param params 104 | * @returns {boolean} 105 | */ 106 | var isImplicit = function (params) { 107 | var implicit = false; 108 | $.each(params, function (i, parameters) { 109 | implicit = implicit || parameters[3]; 110 | }); 111 | 112 | return implicit; 113 | }; 114 | 115 | /** 116 | * Get form method from a validator instance. 117 | * 118 | * @param validator 119 | * @returns {string} 120 | */ 121 | var formMethod = function (validator) { 122 | var formMethod = $(validator.currentForm).attr('method'); 123 | if ($(validator.currentForm).find('input[name="_method"]').length) { 124 | formMethod = $(validator.currentForm).find('input[name="_method"]').val(); 125 | } 126 | 127 | return formMethod; 128 | }; 129 | 130 | /** 131 | * Get AJAX parameters for remote requests. 132 | * 133 | * @param validator 134 | * @param element 135 | * @param params 136 | * @param data 137 | * @returns {object} 138 | */ 139 | var ajaxOpts = function (validator, element, params, data) { 140 | return { 141 | mode: 'abort', 142 | port: 'validate' + element.name, 143 | dataType: 'json', 144 | data: data, 145 | context: validator.currentForm, 146 | url: $(validator.currentForm).attr('action'), 147 | type: formMethod(validator), 148 | beforeSend: function (xhr) { 149 | var token = getCsrfToken(params); 150 | if (formMethod(validator) !== 'get' && token) { 151 | return xhr.setRequestHeader('X-XSRF-TOKEN', token); 152 | } 153 | }, 154 | }; 155 | }; 156 | 157 | /** 158 | * Validate a set of local JS based rules against an element. 159 | * 160 | * @param validator 161 | * @param values 162 | * @param element 163 | * @param rules 164 | * @returns {boolean} 165 | */ 166 | var validateLocalRules = function (validator, values, element, rules) { 167 | var validated = true, 168 | previous = validator.previousValue(element); 169 | 170 | $.each(rules, function (i, param) { 171 | var implicit = param[3] || laravelValidation.implicitRules.indexOf(param[0]) !== -1; 172 | var rule = param[0]; 173 | var message = param[2]; 174 | 175 | if (! implicit && validator.optional(element)) { 176 | validated = "dependency-mismatch"; 177 | return false; 178 | } 179 | 180 | if (laravelValidation.methods[rule] !== undefined) { 181 | $.each(values, function(index, value) { 182 | validated = laravelValidation.methods[rule].call(validator, value, element, param[1], function(valid) { 183 | validator.settings.messages[element.name].laravelValidationRemote = previous.originalMessage; 184 | if (valid) { 185 | var submitted = validator.formSubmitted; 186 | validator.prepareElement(element); 187 | validator.formSubmitted = submitted; 188 | validator.successList.push(element); 189 | delete validator.invalid[element.name]; 190 | validator.showErrors(); 191 | } else { 192 | var errors = {}; 193 | errors[ element.name ] 194 | = previous.message 195 | = typeof message === "function" ? message( value ) : message; 196 | validator.invalid[element.name] = true; 197 | validator.showErrors(errors); 198 | } 199 | validator.showErrors(validator.errorMap); 200 | previous.valid = valid; 201 | }); 202 | 203 | // Break loop. 204 | if (validated === false) { 205 | return false; 206 | } 207 | }); 208 | } else { 209 | validated = false; 210 | } 211 | 212 | if (validated !== true) { 213 | if (!validator.settings.messages[element.name] ) { 214 | validator.settings.messages[element.name] = {}; 215 | } 216 | 217 | validator.settings.messages[element.name].laravelValidation= message; 218 | 219 | return false; 220 | } 221 | 222 | }); 223 | 224 | return validated; 225 | }; 226 | 227 | /** 228 | * Create JQueryValidation check to validate Laravel rules. 229 | */ 230 | 231 | $.validator.addMethod("laravelValidation", function (value, element, params) { 232 | var rules = [], 233 | arrayRules = []; 234 | $.each(params, function (i, param) { 235 | // put Implicit rules in front 236 | var isArrayRule = param[4].indexOf('[') !== -1; 237 | if (param[3] || laravelValidation.implicitRules.indexOf(param[0]) !== -1) { 238 | isArrayRule ? arrayRules.unshift(param) : rules.unshift(param); 239 | } else { 240 | isArrayRule ? arrayRules.push(param) : rules.push(param); 241 | } 242 | }); 243 | 244 | // Validate normal rules. 245 | var localRulesResult = validateLocalRules(this, [value], element, rules); 246 | 247 | // Validate items of the array using array rules. 248 | var arrayValue = ! Array.isArray(value) ? [value] : value; 249 | var arrayRulesResult = validateLocalRules(this, arrayValue, element, arrayRules); 250 | 251 | return localRulesResult && arrayRulesResult; 252 | }, ''); 253 | 254 | 255 | /** 256 | * Create JQueryValidation check to validate Remote Laravel rules. 257 | */ 258 | $.validator.addMethod("laravelValidationRemote", function (value, element, params) { 259 | 260 | if (! isImplicit(params) && this.optional( element )) { 261 | return "dependency-mismatch"; 262 | } 263 | 264 | var previous = this.previousValue( element ), 265 | validator, data; 266 | 267 | if (! this.settings.messages[ element.name ]) { 268 | this.settings.messages[ element.name ] = {}; 269 | } 270 | previous.originalMessage = this.settings.messages[ element.name ].laravelValidationRemote; 271 | this.settings.messages[ element.name ].laravelValidationRemote = previous.message; 272 | 273 | if (laravelValidation.helpers.arrayEquals(previous.old, value) || previous.old === value) { 274 | return previous.valid; 275 | } 276 | 277 | previous.old = value; 278 | validator = this; 279 | this.startRequest( element ); 280 | 281 | data = $(validator.currentForm).serializeArray(); 282 | data.push({'name': '_jsvalidation', 'value': element.name}); 283 | data.push({'name': '_jsvalidation_validate_all', 'value': isValidateAll(params)}); 284 | 285 | $.ajax( ajaxOpts(validator, element, params, data) ) 286 | .always(function( response, textStatus ) { 287 | var errors, message, submitted, valid; 288 | 289 | if (textStatus === 'error') { 290 | valid = false; 291 | response = laravelValidation.helpers.parseErrorResponse(response); 292 | } else if (textStatus === 'success') { 293 | valid = response === true || response === "true"; 294 | } else { 295 | return; 296 | } 297 | 298 | validator.settings.messages[ element.name ].laravelValidationRemote = previous.originalMessage; 299 | 300 | if ( valid ) { 301 | submitted = validator.formSubmitted; 302 | validator.prepareElement( element ); 303 | validator.formSubmitted = submitted; 304 | validator.successList.push( element ); 305 | delete validator.invalid[ element.name ]; 306 | validator.showErrors(); 307 | } else { 308 | errors = {}; 309 | message = response || validator.defaultMessage( element, "remote" ); 310 | errors[ element.name ] 311 | = previous.message 312 | = typeof message === "function" ? message( value ) : message[0]; 313 | validator.invalid[ element.name ] = true; 314 | validator.showErrors( errors ); 315 | } 316 | validator.showErrors(validator.errorMap); 317 | previous.valid = valid; 318 | validator.stopRequest( element, valid ); 319 | } 320 | ); 321 | return "pending"; 322 | }, ''); 323 | 324 | /** 325 | * Create JQueryValidation check to form requests. 326 | */ 327 | $.validator.addMethod("laravelValidationFormRequest", function (value, element, params) { 328 | 329 | var validator = this, 330 | previous = validator.previousValue(element); 331 | 332 | var data = $(validator.currentForm).serializeArray(); 333 | data.push({name: '__proengsoft_form_request', value: 1}); // must match FormRequest.JS_VALIDATION_FIELD 334 | 335 | // Skip AJAX if the value remains the same as a prior request. 336 | if (JSON.stringify(previous.old) === JSON.stringify(data)) { 337 | if (! previous.valid) { 338 | validator.showErrors(previous.errors || {}); 339 | } 340 | 341 | return previous.valid; 342 | } 343 | 344 | previous.old = data; 345 | this.startRequest( element ); 346 | 347 | $.ajax(ajaxOpts(validator, element, params, data)) 348 | .always(function( response, textStatus ) { 349 | var errors = {}, 350 | valid = textStatus === 'success' && (response === true || response === 'true'); 351 | 352 | if (valid) { 353 | validator.resetInternals(); 354 | validator.toHide = validator.errorsFor( element ); 355 | } else { 356 | $.each( response, function( fieldName, errorMessages ) { 357 | var errorElement = laravelValidation.helpers.findByName(validator, fieldName)[0]; 358 | if (errorElement) { 359 | errors[errorElement.name] = laravelValidation.helpers.encode(errorMessages[0] || ''); 360 | } 361 | }); 362 | 363 | // Failed to find the error fields so mark the form as valid otherwise user 364 | // will be left in limbo with no visible error messages. 365 | if ($.isEmptyObject(errors)) { 366 | valid = true; 367 | } 368 | } 369 | 370 | previous.valid = valid; 371 | previous.errors = errors; 372 | validator.showErrors(errors); 373 | validator.stopRequest(element, valid); 374 | }); 375 | 376 | return 'pending'; 377 | }, ''); 378 | } 379 | }; 380 | 381 | $(function() { 382 | laravelValidation.init(); 383 | }); 384 | -------------------------------------------------------------------------------- /resources/assets/js/timezones.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Laravel Javascript Validation 3 | * 4 | * https://github.com/proengsoft/laravel-jsvalidation 5 | * 6 | * Timezone Helper functions used by validators 7 | * 8 | * Copyright (c) 2017 Proengsoft 9 | * Released under the MIT license 10 | */ 11 | 12 | $.extend(true, laravelValidation, { 13 | 14 | helpers: { 15 | 16 | /** 17 | * Check if the specified timezone is valid. 18 | * 19 | * @param value 20 | * @returns {boolean} 21 | */ 22 | isTimezone: function (value) { 23 | 24 | var timezones={ 25 | "africa": [ 26 | "abidjan", 27 | "accra", 28 | "addis_ababa", 29 | "algiers", 30 | "asmara", 31 | "bamako", 32 | "bangui", 33 | "banjul", 34 | "bissau", 35 | "blantyre", 36 | "brazzaville", 37 | "bujumbura", 38 | "cairo", 39 | "casablanca", 40 | "ceuta", 41 | "conakry", 42 | "dakar", 43 | "dar_es_salaam", 44 | "djibouti", 45 | "douala", 46 | "el_aaiun", 47 | "freetown", 48 | "gaborone", 49 | "harare", 50 | "johannesburg", 51 | "juba", 52 | "kampala", 53 | "khartoum", 54 | "kigali", 55 | "kinshasa", 56 | "lagos", 57 | "libreville", 58 | "lome", 59 | "luanda", 60 | "lubumbashi", 61 | "lusaka", 62 | "malabo", 63 | "maputo", 64 | "maseru", 65 | "mbabane", 66 | "mogadishu", 67 | "monrovia", 68 | "nairobi", 69 | "ndjamena", 70 | "niamey", 71 | "nouakchott", 72 | "ouagadougou", 73 | "porto-novo", 74 | "sao_tome", 75 | "tripoli", 76 | "tunis", 77 | "windhoek" 78 | ], 79 | "america": [ 80 | "adak", 81 | "anchorage", 82 | "anguilla", 83 | "antigua", 84 | "araguaina", 85 | "argentina\/buenos_aires", 86 | "argentina\/catamarca", 87 | "argentina\/cordoba", 88 | "argentina\/jujuy", 89 | "argentina\/la_rioja", 90 | "argentina\/mendoza", 91 | "argentina\/rio_gallegos", 92 | "argentina\/salta", 93 | "argentina\/san_juan", 94 | "argentina\/san_luis", 95 | "argentina\/tucuman", 96 | "argentina\/ushuaia", 97 | "aruba", 98 | "asuncion", 99 | "atikokan", 100 | "bahia", 101 | "bahia_banderas", 102 | "barbados", 103 | "belem", 104 | "belize", 105 | "blanc-sablon", 106 | "boa_vista", 107 | "bogota", 108 | "boise", 109 | "cambridge_bay", 110 | "campo_grande", 111 | "cancun", 112 | "caracas", 113 | "cayenne", 114 | "cayman", 115 | "chicago", 116 | "chihuahua", 117 | "costa_rica", 118 | "creston", 119 | "cuiaba", 120 | "curacao", 121 | "danmarkshavn", 122 | "dawson", 123 | "dawson_creek", 124 | "denver", 125 | "detroit", 126 | "dominica", 127 | "edmonton", 128 | "eirunepe", 129 | "el_salvador", 130 | "fortaleza", 131 | "glace_bay", 132 | "godthab", 133 | "goose_bay", 134 | "grand_turk", 135 | "grenada", 136 | "guadeloupe", 137 | "guatemala", 138 | "guayaquil", 139 | "guyana", 140 | "halifax", 141 | "havana", 142 | "hermosillo", 143 | "indiana\/indianapolis", 144 | "indiana\/knox", 145 | "indiana\/marengo", 146 | "indiana\/petersburg", 147 | "indiana\/tell_city", 148 | "indiana\/vevay", 149 | "indiana\/vincennes", 150 | "indiana\/winamac", 151 | "inuvik", 152 | "iqaluit", 153 | "jamaica", 154 | "juneau", 155 | "kentucky\/louisville", 156 | "kentucky\/monticello", 157 | "kralendijk", 158 | "la_paz", 159 | "lima", 160 | "los_angeles", 161 | "lower_princes", 162 | "maceio", 163 | "managua", 164 | "manaus", 165 | "marigot", 166 | "martinique", 167 | "matamoros", 168 | "mazatlan", 169 | "menominee", 170 | "merida", 171 | "metlakatla", 172 | "mexico_city", 173 | "miquelon", 174 | "moncton", 175 | "monterrey", 176 | "montevideo", 177 | "montreal", 178 | "montserrat", 179 | "nassau", 180 | "new_york", 181 | "nipigon", 182 | "nome", 183 | "noronha", 184 | "north_dakota\/beulah", 185 | "north_dakota\/center", 186 | "north_dakota\/new_salem", 187 | "ojinaga", 188 | "panama", 189 | "pangnirtung", 190 | "paramaribo", 191 | "phoenix", 192 | "port-au-prince", 193 | "port_of_spain", 194 | "porto_velho", 195 | "puerto_rico", 196 | "rainy_river", 197 | "rankin_inlet", 198 | "recife", 199 | "regina", 200 | "resolute", 201 | "rio_branco", 202 | "santa_isabel", 203 | "santarem", 204 | "santiago", 205 | "santo_domingo", 206 | "sao_paulo", 207 | "scoresbysund", 208 | "shiprock", 209 | "sitka", 210 | "st_barthelemy", 211 | "st_johns", 212 | "st_kitts", 213 | "st_lucia", 214 | "st_thomas", 215 | "st_vincent", 216 | "swift_current", 217 | "tegucigalpa", 218 | "thule", 219 | "thunder_bay", 220 | "tijuana", 221 | "toronto", 222 | "tortola", 223 | "vancouver", 224 | "whitehorse", 225 | "winnipeg", 226 | "yakutat", 227 | "yellowknife" 228 | ], 229 | "antarctica": [ 230 | "casey", 231 | "davis", 232 | "dumontdurville", 233 | "macquarie", 234 | "mawson", 235 | "mcmurdo", 236 | "palmer", 237 | "rothera", 238 | "south_pole", 239 | "syowa", 240 | "vostok" 241 | ], 242 | "arctic": [ 243 | "longyearbyen" 244 | ], 245 | "asia": [ 246 | "aden", 247 | "almaty", 248 | "amman", 249 | "anadyr", 250 | "aqtau", 251 | "aqtobe", 252 | "ashgabat", 253 | "baghdad", 254 | "bahrain", 255 | "baku", 256 | "bangkok", 257 | "beirut", 258 | "bishkek", 259 | "brunei", 260 | "choibalsan", 261 | "chongqing", 262 | "colombo", 263 | "damascus", 264 | "dhaka", 265 | "dili", 266 | "dubai", 267 | "dushanbe", 268 | "gaza", 269 | "harbin", 270 | "hebron", 271 | "ho_chi_minh", 272 | "hong_kong", 273 | "hovd", 274 | "irkutsk", 275 | "jakarta", 276 | "jayapura", 277 | "jerusalem", 278 | "kabul", 279 | "kamchatka", 280 | "karachi", 281 | "kashgar", 282 | "kathmandu", 283 | "khandyga", 284 | "kolkata", 285 | "krasnoyarsk", 286 | "kuala_lumpur", 287 | "kuching", 288 | "kuwait", 289 | "macau", 290 | "magadan", 291 | "makassar", 292 | "manila", 293 | "muscat", 294 | "nicosia", 295 | "novokuznetsk", 296 | "novosibirsk", 297 | "omsk", 298 | "oral", 299 | "phnom_penh", 300 | "pontianak", 301 | "pyongyang", 302 | "qatar", 303 | "qyzylorda", 304 | "rangoon", 305 | "riyadh", 306 | "sakhalin", 307 | "samarkand", 308 | "seoul", 309 | "shanghai", 310 | "singapore", 311 | "taipei", 312 | "tashkent", 313 | "tbilisi", 314 | "tehran", 315 | "thimphu", 316 | "tokyo", 317 | "ulaanbaatar", 318 | "urumqi", 319 | "ust-nera", 320 | "vientiane", 321 | "vladivostok", 322 | "yakutsk", 323 | "yekaterinburg", 324 | "yerevan" 325 | ], 326 | "atlantic": [ 327 | "azores", 328 | "bermuda", 329 | "canary", 330 | "cape_verde", 331 | "faroe", 332 | "madeira", 333 | "reykjavik", 334 | "south_georgia", 335 | "st_helena", 336 | "stanley" 337 | ], 338 | "australia": [ 339 | "adelaide", 340 | "brisbane", 341 | "broken_hill", 342 | "currie", 343 | "darwin", 344 | "eucla", 345 | "hobart", 346 | "lindeman", 347 | "lord_howe", 348 | "melbourne", 349 | "perth", 350 | "sydney" 351 | ], 352 | "europe": [ 353 | "amsterdam", 354 | "andorra", 355 | "athens", 356 | "belgrade", 357 | "berlin", 358 | "bratislava", 359 | "brussels", 360 | "bucharest", 361 | "budapest", 362 | "busingen", 363 | "chisinau", 364 | "copenhagen", 365 | "dublin", 366 | "gibraltar", 367 | "guernsey", 368 | "helsinki", 369 | "isle_of_man", 370 | "istanbul", 371 | "jersey", 372 | "kaliningrad", 373 | "kiev", 374 | "lisbon", 375 | "ljubljana", 376 | "london", 377 | "luxembourg", 378 | "madrid", 379 | "malta", 380 | "mariehamn", 381 | "minsk", 382 | "monaco", 383 | "moscow", 384 | "oslo", 385 | "paris", 386 | "podgorica", 387 | "prague", 388 | "riga", 389 | "rome", 390 | "samara", 391 | "san_marino", 392 | "sarajevo", 393 | "simferopol", 394 | "skopje", 395 | "sofia", 396 | "stockholm", 397 | "tallinn", 398 | "tirane", 399 | "uzhgorod", 400 | "vaduz", 401 | "vatican", 402 | "vienna", 403 | "vilnius", 404 | "volgograd", 405 | "warsaw", 406 | "zagreb", 407 | "zaporozhye", 408 | "zurich" 409 | ], 410 | "indian": [ 411 | "antananarivo", 412 | "chagos", 413 | "christmas", 414 | "cocos", 415 | "comoro", 416 | "kerguelen", 417 | "mahe", 418 | "maldives", 419 | "mauritius", 420 | "mayotte", 421 | "reunion" 422 | ], 423 | "pacific": [ 424 | "apia", 425 | "auckland", 426 | "chatham", 427 | "chuuk", 428 | "easter", 429 | "efate", 430 | "enderbury", 431 | "fakaofo", 432 | "fiji", 433 | "funafuti", 434 | "galapagos", 435 | "gambier", 436 | "guadalcanal", 437 | "guam", 438 | "honolulu", 439 | "johnston", 440 | "kiritimati", 441 | "kosrae", 442 | "kwajalein", 443 | "majuro", 444 | "marquesas", 445 | "midway", 446 | "nauru", 447 | "niue", 448 | "norfolk", 449 | "noumea", 450 | "pago_pago", 451 | "palau", 452 | "pitcairn", 453 | "pohnpei", 454 | "port_moresby", 455 | "rarotonga", 456 | "saipan", 457 | "tahiti", 458 | "tarawa", 459 | "tongatapu", 460 | "wake", 461 | "wallis" 462 | ], 463 | "utc": [ 464 | "" 465 | ] 466 | }; 467 | 468 | var tzparts= value.split('/',2); 469 | var continent=tzparts[0].toLowerCase(); 470 | var city=''; 471 | if (tzparts[1]) { 472 | city=tzparts[1].toLowerCase(); 473 | } 474 | 475 | return (continent in timezones && ( timezones[continent].length===0 || timezones[continent].indexOf(city)!==-1)) 476 | } 477 | } 478 | }); 479 | -------------------------------------------------------------------------------- /resources/assets/js/validations.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Laravel Javascript Validation 3 | * 4 | * https://github.com/proengsoft/laravel-jsvalidation 5 | * 6 | * Methods that implement Laravel Validations 7 | * 8 | * Copyright (c) 2017 Proengsoft 9 | * Released under the MIT license 10 | */ 11 | 12 | $.extend(true, laravelValidation, { 13 | 14 | methods:{ 15 | 16 | helpers: laravelValidation.helpers, 17 | 18 | jsRemoteTimer:0, 19 | 20 | /** 21 | * "Validate" optional attributes. 22 | * Always returns true, just lets us put sometimes in rules. 23 | * 24 | * @return {boolean} 25 | */ 26 | Sometimes: function() { 27 | return true; 28 | }, 29 | 30 | /** 31 | * Bail This is the default behaivour os JSValidation. 32 | * Always returns true, just lets us put sometimes in rules. 33 | * 34 | * @return {boolean} 35 | */ 36 | Bail: function() { 37 | return true; 38 | }, 39 | 40 | /** 41 | * "Indicate" validation should pass if value is null. 42 | * Always returns true, just lets us put "nullable" in rules. 43 | * 44 | * @return {boolean} 45 | */ 46 | Nullable: function() { 47 | return true; 48 | }, 49 | 50 | /** 51 | * Validate the given attribute is filled if it is present. 52 | */ 53 | Filled: function(value, element) { 54 | return $.validator.methods.required.call(this, value, element, true); 55 | }, 56 | 57 | 58 | /** 59 | * Validate that a required attribute exists. 60 | */ 61 | Required: function(value, element) { 62 | return $.validator.methods.required.call(this, value, element); 63 | }, 64 | 65 | /** 66 | * Validate that an attribute exists when any other attribute exists. 67 | * 68 | * @return {boolean} 69 | */ 70 | RequiredWith: function(value, element, params) { 71 | var validator=this, 72 | required=false; 73 | var currentObject=this; 74 | 75 | $.each(params,function(i,param) { 76 | var target=laravelValidation.helpers.dependentElement( 77 | currentObject, element, param 78 | ); 79 | required=required || ( 80 | target!==undefined && 81 | $.validator.methods.required.call( 82 | validator, 83 | currentObject.elementValue(target), 84 | target,true 85 | )); 86 | }); 87 | 88 | if (required) { 89 | return $.validator.methods.required.call(this, value, element, true); 90 | } 91 | return true; 92 | }, 93 | 94 | /** 95 | * Validate that an attribute exists when all other attribute exists. 96 | * 97 | * @return {boolean} 98 | */ 99 | RequiredWithAll: function(value, element, params) { 100 | var validator=this, 101 | required=true; 102 | var currentObject=this; 103 | 104 | $.each(params,function(i,param) { 105 | var target=laravelValidation.helpers.dependentElement( 106 | currentObject, element, param 107 | ); 108 | required = required && ( 109 | target!==undefined && 110 | $.validator.methods.required.call( 111 | validator, 112 | currentObject.elementValue(target), 113 | target,true 114 | )); 115 | }); 116 | 117 | if (required) { 118 | return $.validator.methods.required.call(this, value, element, true); 119 | } 120 | return true; 121 | }, 122 | 123 | /** 124 | * Validate that an attribute exists when any other attribute does not exists. 125 | * 126 | * @return {boolean} 127 | */ 128 | RequiredWithout: function(value, element, params) { 129 | var validator=this, 130 | required=false; 131 | var currentObject=this; 132 | 133 | $.each(params,function(i,param) { 134 | var target=laravelValidation.helpers.dependentElement( 135 | currentObject, element, param 136 | ); 137 | required = required || 138 | target===undefined|| 139 | !$.validator.methods.required.call( 140 | validator, 141 | currentObject.elementValue(target), 142 | target,true 143 | ); 144 | }); 145 | 146 | if (required) { 147 | return $.validator.methods.required.call(this, value, element, true); 148 | } 149 | return true; 150 | }, 151 | 152 | /** 153 | * Validate that an attribute exists when all other attribute does not exists. 154 | * 155 | * @return {boolean} 156 | */ 157 | RequiredWithoutAll: function(value, element, params) { 158 | var validator=this, 159 | required=true, 160 | currentObject=this; 161 | 162 | $.each(params,function(i, param) { 163 | var target=laravelValidation.helpers.dependentElement( 164 | currentObject, element, param 165 | ); 166 | required = required && ( 167 | target===undefined || 168 | !$.validator.methods.required.call( 169 | validator, 170 | currentObject.elementValue(target), 171 | target,true 172 | )); 173 | }); 174 | 175 | if (required) { 176 | return $.validator.methods.required.call(this, value, element, true); 177 | } 178 | return true; 179 | }, 180 | 181 | /** 182 | * Validate that an attribute exists when another attribute has a given value. 183 | * 184 | * @return {boolean} 185 | */ 186 | RequiredIf: function(value, element, params) { 187 | 188 | var target=laravelValidation.helpers.dependentElement( 189 | this, element, params[0] 190 | ); 191 | 192 | if (target!==undefined) { 193 | var val=String(this.elementValue(target)); 194 | if (typeof val !== 'undefined') { 195 | var data = params.slice(1); 196 | if ($.inArray(val, data) !== -1) { 197 | return $.validator.methods.required.call( 198 | this, value, element, true 199 | ); 200 | } 201 | } 202 | } 203 | 204 | return true; 205 | }, 206 | 207 | /** 208 | * Validate that an attribute exists when another 209 | * attribute does not have a given value. 210 | * 211 | * @return {boolean} 212 | */ 213 | RequiredUnless: function(value, element, params) { 214 | 215 | var target=laravelValidation.helpers.dependentElement( 216 | this, element, params[0] 217 | ); 218 | 219 | if (target!==undefined) { 220 | var val=String(this.elementValue(target)); 221 | if (typeof val !== 'undefined') { 222 | var data = params.slice(1); 223 | if ($.inArray(val, data) !== -1) { 224 | return true; 225 | } 226 | } 227 | } 228 | 229 | return $.validator.methods.required.call( 230 | this, value, element, true 231 | ); 232 | 233 | }, 234 | 235 | /** 236 | * Validate that an attribute has a matching confirmation. 237 | * 238 | * @return {boolean} 239 | */ 240 | Confirmed: function(value, element, params) { 241 | return laravelValidation.methods.Same.call(this,value, element, params); 242 | }, 243 | 244 | /** 245 | * Validate that two attributes match. 246 | * 247 | * @return {boolean} 248 | */ 249 | Same: function(value, element, params) { 250 | 251 | var target=laravelValidation.helpers.dependentElement( 252 | this, element, params[0] 253 | ); 254 | 255 | if (target!==undefined) { 256 | return String(value) === String(this.elementValue(target)); 257 | } 258 | return false; 259 | }, 260 | 261 | /** 262 | * Validate that the values of an attribute is in another attribute. 263 | * 264 | * @param value 265 | * @param element 266 | * @param params 267 | * @returns {boolean} 268 | * @constructor 269 | */ 270 | InArray: function (value, element, params) { 271 | if (typeof params[0] === 'undefined') { 272 | return false; 273 | } 274 | var elements = this.elements(); 275 | var found = false; 276 | var nameRegExp = laravelValidation.helpers.regexFromWildcard(params[0]); 277 | 278 | for ( var i = 0; i < elements.length ; i++ ) { 279 | var targetName = elements[i].name; 280 | if (targetName.match(nameRegExp)) { 281 | var equals = laravelValidation.methods.Same.call(this,value, element, [targetName]); 282 | found = found || equals; 283 | } 284 | } 285 | 286 | return found; 287 | }, 288 | 289 | /** 290 | * Validate an attribute is unique among other values. 291 | * 292 | * @param value 293 | * @param element 294 | * @param params 295 | * @returns {boolean} 296 | */ 297 | Distinct: function (value, element, params) { 298 | if (typeof params[0] === 'undefined') { 299 | return false; 300 | } 301 | 302 | var elements = this.elements(); 303 | var found = false; 304 | var nameRegExp = laravelValidation.helpers.regexFromWildcard(params[0]); 305 | 306 | for ( var i = 0; i < elements.length ; i++ ) { 307 | var targetName = elements[i].name; 308 | if (targetName !== element.name && targetName.match(nameRegExp)) { 309 | var equals = laravelValidation.methods.Same.call(this,value, element, [targetName]); 310 | found = found || equals; 311 | } 312 | } 313 | 314 | return !found; 315 | }, 316 | 317 | 318 | /** 319 | * Validate that an attribute is different from another attribute. 320 | * 321 | * @return {boolean} 322 | */ 323 | Different: function(value, element, params) { 324 | return ! laravelValidation.methods.Same.call(this,value, element, params); 325 | }, 326 | 327 | /** 328 | * Validate that an attribute was "accepted". 329 | * This validation rule implies the attribute is "required". 330 | * 331 | * @return {boolean} 332 | */ 333 | Accepted: function(value) { 334 | var regex = new RegExp("^(?:(yes|on|1|true))$",'i'); 335 | return regex.test(value); 336 | }, 337 | 338 | /** 339 | * Validate that an attribute is an array. 340 | * 341 | * @param value 342 | * @param element 343 | */ 344 | Array: function(value, element) { 345 | if (element.name.indexOf('[') !== -1 && element.name.indexOf(']') !== -1) { 346 | return true; 347 | } 348 | 349 | return laravelValidation.helpers.isArray(value); 350 | }, 351 | 352 | /** 353 | * Validate that an attribute is a boolean. 354 | * 355 | * @return {boolean} 356 | */ 357 | Boolean: function(value) { 358 | var regex= new RegExp("^(?:(true|false|1|0))$",'i'); 359 | return regex.test(value); 360 | }, 361 | 362 | /** 363 | * Validate that an attribute is an integer. 364 | * 365 | * @return {boolean} 366 | */ 367 | Integer: function(value) { 368 | var regex= new RegExp("^(?:-?\\d+)$",'i'); 369 | return regex.test(value); 370 | }, 371 | 372 | /** 373 | * Validate that an attribute is numeric. 374 | */ 375 | Numeric: function(value, element) { 376 | return $.validator.methods.number.call(this, value, element, true); 377 | }, 378 | 379 | /** 380 | * Validate that an attribute is a string. 381 | * 382 | * @return {boolean} 383 | */ 384 | String: function(value) { 385 | return typeof value === 'string'; 386 | }, 387 | 388 | /** 389 | * The field under validation must be numeric and must have an exact length of value. 390 | */ 391 | Digits: function(value, element, params) { 392 | return ( 393 | $.validator.methods.number.call(this, value, element, true) && 394 | value.length === parseInt(params, 10) 395 | ); 396 | }, 397 | 398 | /** 399 | * The field under validation must have a length between the given min and max. 400 | */ 401 | DigitsBetween: function(value, element, params) { 402 | return ($.validator.methods.number.call(this, value, element, true) 403 | && value.length>=parseFloat(params[0]) && value.length<=parseFloat(params[1])); 404 | }, 405 | 406 | /** 407 | * Validate the size of an attribute. 408 | * 409 | * @return {boolean} 410 | */ 411 | Size: function(value, element, params) { 412 | return laravelValidation.helpers.getSize(this, element,value) === parseFloat(params[0]); 413 | }, 414 | 415 | /** 416 | * Validate the size of an attribute is between a set of values. 417 | * 418 | * @return {boolean} 419 | */ 420 | Between: function(value, element, params) { 421 | return ( laravelValidation.helpers.getSize(this, element,value) >= parseFloat(params[0]) && 422 | laravelValidation.helpers.getSize(this,element,value) <= parseFloat(params[1])); 423 | }, 424 | 425 | /** 426 | * Validate the size of an attribute is greater than a minimum value. 427 | * 428 | * @return {boolean} 429 | */ 430 | Min: function(value, element, params) { 431 | value = laravelValidation.helpers.allElementValues(this, element); 432 | 433 | return laravelValidation.helpers.getSize(this, element, value) >= parseFloat(params[0]); 434 | }, 435 | 436 | /** 437 | * Validate the size of an attribute is less than a maximum value. 438 | * 439 | * @return {boolean} 440 | */ 441 | Max: function(value, element, params) { 442 | value = laravelValidation.helpers.allElementValues(this, element); 443 | 444 | return laravelValidation.helpers.getSize(this, element, value) <= parseFloat(params[0]); 445 | }, 446 | 447 | /** 448 | * Validate an attribute is contained within a list of values. 449 | * 450 | * @return {boolean} 451 | */ 452 | In: function(value, element, params) { 453 | if (laravelValidation.helpers.isArray(value) 454 | && laravelValidation.helpers.hasRules(element, "Array") 455 | ) { 456 | var diff = laravelValidation.helpers.arrayDiff(value, params); 457 | 458 | return Object.keys(diff).length === 0; 459 | } 460 | 461 | return params.indexOf(value.toString()) !== -1; 462 | }, 463 | 464 | /** 465 | * Validate an attribute is not contained within a list of values. 466 | * 467 | * @return {boolean} 468 | */ 469 | NotIn: function(value, element, params) { 470 | return params.indexOf(value.toString()) === -1; 471 | }, 472 | 473 | /** 474 | * Validate that an attribute is a valid IP. 475 | * 476 | * @return {boolean} 477 | */ 478 | Ip: function(value) { 479 | return /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(value) || 480 | /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value); 481 | }, 482 | 483 | /** 484 | * Validate that an attribute is a valid e-mail address. 485 | */ 486 | Email: function(value, element) { 487 | return $.validator.methods.email.call(this, value, element, true); 488 | }, 489 | 490 | /** 491 | * Validate that an attribute is a valid URL. 492 | */ 493 | Url: function(value, element) { 494 | return $.validator.methods.url.call(this, value, element, true); 495 | }, 496 | 497 | /** 498 | * The field under validation must be a successfully uploaded file. 499 | * 500 | * @return {boolean} 501 | */ 502 | File: function(value, element) { 503 | if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { 504 | return true; 505 | } 506 | if ('files' in element ) { 507 | return (element.files.length > 0); 508 | } 509 | return false; 510 | }, 511 | 512 | /** 513 | * Validate the MIME type of a file upload attribute is in a set of MIME types. 514 | * 515 | * @return {boolean} 516 | */ 517 | Mimes: function(value, element, params) { 518 | if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { 519 | return true; 520 | } 521 | var lowerParams = $.map(params, function(item) { 522 | return item.toLowerCase(); 523 | }); 524 | 525 | var fileinfo = laravelValidation.helpers.fileinfo(element); 526 | return (fileinfo !== false && lowerParams.indexOf(fileinfo.extension.toLowerCase())!==-1); 527 | }, 528 | 529 | /** 530 | * The file under validation must match one of the given MIME types. 531 | * 532 | * @return {boolean} 533 | */ 534 | Mimetypes: function(value, element, params) { 535 | if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { 536 | return true; 537 | } 538 | var lowerParams = $.map(params, function(item) { 539 | return item.toLowerCase(); 540 | }); 541 | 542 | var fileinfo = laravelValidation.helpers.fileinfo(element); 543 | 544 | if (fileinfo === false) { 545 | return false; 546 | } 547 | return (lowerParams.indexOf(fileinfo.type.toLowerCase())!==-1); 548 | }, 549 | 550 | /** 551 | * Validate the MIME type of a file upload attribute is in a set of MIME types. 552 | */ 553 | Image: function(value, element) { 554 | return laravelValidation.methods.Mimes.call(this, value, element, [ 555 | 'jpg', 'png', 'gif', 'bmp', 'svg', 'jpeg' 556 | ]); 557 | }, 558 | 559 | /** 560 | * Validate dimensions of Image. 561 | * 562 | * @return {boolean|string} 563 | */ 564 | Dimensions: function(value, element, params, callback) { 565 | if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { 566 | return true; 567 | } 568 | if (element.files === null || typeof element.files[0] === 'undefined') { 569 | return false; 570 | } 571 | 572 | var fr = new FileReader; 573 | fr.onload = function () { 574 | var img = new Image(); 575 | img.onload = function () { 576 | var height = parseFloat(img.naturalHeight); 577 | var width = parseFloat(img.naturalWidth); 578 | var ratio = width / height; 579 | var notValid = ((params['width']) && parseFloat(params['width'] !== width)) || 580 | ((params['min_width']) && parseFloat(params['min_width']) > width) || 581 | ((params['max_width']) && parseFloat(params['max_width']) < width) || 582 | ((params['height']) && parseFloat(params['height']) !== height) || 583 | ((params['min_height']) && parseFloat(params['min_height']) > height) || 584 | ((params['max_height']) && parseFloat(params['max_height']) < height) || 585 | ((params['ratio']) && ratio !== parseFloat(eval(params['ratio'])) 586 | ); 587 | callback(! notValid); 588 | }; 589 | img.onerror = function() { 590 | callback(false); 591 | }; 592 | img.src = fr.result; 593 | }; 594 | fr.readAsDataURL(element.files[0]); 595 | 596 | return 'pending'; 597 | }, 598 | 599 | /** 600 | * Validate that an attribute contains only alphabetic characters. 601 | * 602 | * @return {boolean} 603 | */ 604 | Alpha: function(value) { 605 | if (typeof value !== 'string') { 606 | return false; 607 | } 608 | 609 | var regex = new RegExp("^(?:^[a-z\u00E0-\u00FC]+$)$",'i'); 610 | return regex.test(value); 611 | 612 | }, 613 | 614 | /** 615 | * Validate that an attribute contains only alpha-numeric characters. 616 | * 617 | * @return {boolean} 618 | */ 619 | AlphaNum: function(value) { 620 | if (typeof value !== 'string') { 621 | return false; 622 | } 623 | var regex = new RegExp("^(?:^[a-z0-9\u00E0-\u00FC]+$)$",'i'); 624 | return regex.test(value); 625 | }, 626 | 627 | /** 628 | * Validate that an attribute contains only alphabetic characters. 629 | * 630 | * @return {boolean} 631 | */ 632 | AlphaDash: function(value) { 633 | if (typeof value !== 'string') { 634 | return false; 635 | } 636 | var regex = new RegExp("^(?:^[a-z0-9\u00E0-\u00FC_-]+$)$",'i'); 637 | return regex.test(value); 638 | }, 639 | 640 | /** 641 | * Validate that an attribute passes a regular expression check. 642 | * 643 | * @return {boolean} 644 | */ 645 | Regex: function(value, element, params) { 646 | var invalidModifiers=['x','s','u','X','U','A']; 647 | // Converting php regular expression 648 | var phpReg= new RegExp('^(?:\/)(.*\\\/?[^\/]*|[^\/]*)(?:\/)([gmixXsuUAJ]*)?$'); 649 | var matches=params[0].match(phpReg); 650 | if (matches === null) { 651 | return false; 652 | } 653 | // checking modifiers 654 | var php_modifiers=[]; 655 | if (matches[2]!==undefined) { 656 | php_modifiers=matches[2].split(''); 657 | for (var i=0; i'); 710 | }, 711 | 712 | /** 713 | * Validate the date is equal or after a given date. 714 | * 715 | * @return {boolean} 716 | */ 717 | AfterOrEqual: function(value, element, params) { 718 | return laravelValidation.helpers.compareDates(this, value, element, params[0], '>='); 719 | }, 720 | 721 | 722 | /** 723 | * Validate that an attribute is a valid date. 724 | */ 725 | Timezone: function(value) { 726 | return laravelValidation.helpers.isTimezone(value); 727 | }, 728 | 729 | 730 | /** 731 | * Validate the attribute is a valid JSON string. 732 | * 733 | * @param value 734 | * @return bool 735 | */ 736 | Json: function(value) { 737 | var result = true; 738 | try { 739 | JSON.parse(value); 740 | } catch (e) { 741 | result = false; 742 | } 743 | return result; 744 | }, 745 | 746 | /** 747 | * Noop (always returns true). 748 | * 749 | * @param value 750 | * @returns {boolean} 751 | */ 752 | ProengsoftNoop: function (value) { 753 | return true; 754 | }, 755 | } 756 | }); 757 | -------------------------------------------------------------------------------- /resources/views/bootstrap.php: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /resources/views/bootstrap4.php: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /resources/views/bootstrap5.php: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /resources/views/uikit.php: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /src/Exceptions/PropertyNotFoundException.php: -------------------------------------------------------------------------------- 1 | $value items. 17 | * 18 | * @param array $parameters 19 | * @return array 20 | */ 21 | abstract public function parseNamedParameters($parameters); 22 | 23 | /** 24 | * Confirmed rule is applied to confirmed attribute. 25 | * 26 | * @param $attribute 27 | * @param array $parameters 28 | * @return array 29 | */ 30 | protected function ruleConfirmed($attribute, array $parameters) 31 | { 32 | $parameters[0] = $this->getAttributeName($attribute); 33 | $attribute = "{$attribute}_confirmation"; 34 | 35 | return [$attribute, $parameters]; 36 | } 37 | 38 | /** 39 | * Returns Javascript parameters for After rule. 40 | * 41 | * @param $attribute 42 | * @param array $parameters 43 | * @return array 44 | */ 45 | protected function ruleAfter($attribute, array $parameters) 46 | { 47 | if (! ($date = strtotime($parameters[0]))) { 48 | $date = $this->getAttributeName($parameters[0]); 49 | } 50 | 51 | return [$attribute, [$date]]; 52 | } 53 | 54 | /** 55 | * Returns Javascript parameters for Before rule. 56 | * 57 | * @param $attribute 58 | * @param array $parameters 59 | * @return array 60 | */ 61 | protected function ruleBefore($attribute, array $parameters) 62 | { 63 | return $this->ruleAfter($attribute, $parameters); 64 | } 65 | 66 | /** 67 | * Validate that two attributes match. 68 | * 69 | * @param string $attribute 70 | * @param array $parameters 71 | * @return array 72 | */ 73 | protected function ruleSame($attribute, array $parameters) 74 | { 75 | $other = $this->getAttributeName($parameters[0]); 76 | 77 | return [$attribute, [$other]]; 78 | } 79 | 80 | /** 81 | * Validate that an attribute is different from another attribute. 82 | * 83 | * @param string $attribute 84 | * @param array $parameters 85 | * @return array 86 | */ 87 | protected function ruleDifferent($attribute, array $parameters) 88 | { 89 | return $this->ruleSame($attribute, $parameters); 90 | } 91 | 92 | /** 93 | * Validate that an attribute exists when any other attribute exists. 94 | * 95 | * @param string $attribute 96 | * @param mixed $parameters 97 | * @return array 98 | */ 99 | protected function ruleRequiredWith($attribute, array $parameters) 100 | { 101 | $parameters = array_map([$this, 'getAttributeName'], $parameters); 102 | 103 | return [$attribute, $parameters]; 104 | } 105 | 106 | /** 107 | * Validate that an attribute exists when all other attributes exists. 108 | * 109 | * @param string $attribute 110 | * @param mixed $parameters 111 | * @return array 112 | */ 113 | protected function ruleRequiredWithAll($attribute, array $parameters) 114 | { 115 | return $this->ruleRequiredWith($attribute, $parameters); 116 | } 117 | 118 | /** 119 | * Validate that an attribute exists when another attribute does not. 120 | * 121 | * @param string $attribute 122 | * @param mixed $parameters 123 | * @return array 124 | */ 125 | protected function ruleRequiredWithout($attribute, array $parameters) 126 | { 127 | return $this->ruleRequiredWith($attribute, $parameters); 128 | } 129 | 130 | /** 131 | * Validate that an attribute exists when all other attributes do not. 132 | * 133 | * @param string $attribute 134 | * @param mixed $parameters 135 | * @return array 136 | */ 137 | protected function ruleRequiredWithoutAll($attribute, array $parameters) 138 | { 139 | return $this->ruleRequiredWith($attribute, $parameters); 140 | } 141 | 142 | /** 143 | * Validate that an attribute exists when another attribute has a given value. 144 | * 145 | * @param string $attribute 146 | * @param mixed $parameters 147 | * @return array 148 | */ 149 | protected function ruleRequiredIf($attribute, array $parameters) 150 | { 151 | $parameters[0] = $this->getAttributeName($parameters[0]); 152 | 153 | return [$attribute, $parameters]; 154 | } 155 | 156 | /** 157 | * Validate that an attribute exists when another attribute does not have a given value. 158 | * 159 | * @param string $attribute 160 | * @param mixed $parameters 161 | * @return array 162 | */ 163 | protected function ruleRequiredUnless($attribute, array $parameters) 164 | { 165 | return $this->ruleRequiredIf($attribute, $parameters); 166 | } 167 | 168 | /** 169 | * Validate that the values of an attribute is in another attribute. 170 | * 171 | * @param string $attribute 172 | * @param mixed $parameters 173 | * @return array 174 | */ 175 | protected function ruleInArray($attribute, array $parameters) 176 | { 177 | return $this->ruleRequiredIf($attribute, $parameters); 178 | } 179 | 180 | /** 181 | * Validate the dimensions of an image matches the given values. 182 | * 183 | * @param string $attribute 184 | * @param array $parameters 185 | * @return array 186 | */ 187 | protected function ruleDimensions($attribute, $parameters) 188 | { 189 | $parameters = $this->parseNamedParameters($parameters); 190 | 191 | return [$attribute, $parameters]; 192 | } 193 | 194 | /** 195 | * Validate an attribute is unique among other values. 196 | * 197 | * @param string $attribute 198 | * @param array $parameters 199 | * @return array 200 | */ 201 | protected function ruleDistinct($attribute, array $parameters) 202 | { 203 | $parameters[0] = $attribute; 204 | 205 | return $this->ruleRequiredIf($attribute, $parameters); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Javascript/JavascriptValidator.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 56 | $this->setDefaults($options); 57 | } 58 | 59 | /** 60 | * Set default parameters. 61 | * 62 | * @param $options 63 | * @return void 64 | */ 65 | protected function setDefaults($options) 66 | { 67 | $this->selector = empty($options['selector']) ? 'form' : $options['selector']; 68 | $this->view = empty($options['view']) ? 'jsvalidation::bootstrap' : $options['view']; 69 | $this->remote = isset($options['remote']) ? $options['remote'] : true; 70 | 71 | if (isset($options['ignore'])) { 72 | $this->ignore = $options['ignore']; 73 | } 74 | } 75 | 76 | /** 77 | * Render the specified view with validator data. 78 | * 79 | * @param null|\Illuminate\Contracts\View\View|string $view 80 | * @param null|string $selector 81 | * @return string 82 | */ 83 | public function render($view = null, $selector = null) 84 | { 85 | $this->view($view); 86 | $this->selector($selector); 87 | 88 | return View::make($this->view, ['validator' => $this->getViewData()]) 89 | ->render(); 90 | } 91 | 92 | /** 93 | * Get the view data as an array. 94 | * 95 | * @return array 96 | */ 97 | public function toArray() 98 | { 99 | return $this->getViewData(); 100 | } 101 | 102 | /** 103 | * Get the string resulting of render default view. 104 | * 105 | * @return string 106 | */ 107 | public function __toString() 108 | { 109 | try { 110 | return $this->render(); 111 | } catch (Exception $exception) { 112 | return trigger_error($exception->__toString(), E_USER_ERROR); 113 | } 114 | } 115 | 116 | /** 117 | * Gets value from view data. 118 | * 119 | * @param $name 120 | * @return string 121 | * 122 | * @throws \Proengsoft\JsValidation\Exceptions\PropertyNotFoundException 123 | */ 124 | public function __get($name) 125 | { 126 | $data = $this->getViewData(); 127 | if (! array_key_exists($name, $data)) { 128 | throw new PropertyNotFoundException($name, get_class()); 129 | } 130 | 131 | return $data[$name]; 132 | } 133 | 134 | /** 135 | * Gets view data. 136 | * 137 | * @return array 138 | */ 139 | protected function getViewData() 140 | { 141 | $this->validator->setRemote($this->remote); 142 | $data = $this->validator->validationData(); 143 | $data['selector'] = $this->selector; 144 | 145 | if (! is_null($this->ignore)) { 146 | $data['ignore'] = $this->ignore; 147 | } 148 | 149 | return $data; 150 | } 151 | 152 | /** 153 | * Set the form selector to validate. 154 | * 155 | * @param string $selector 156 | * 157 | * @deprecated 158 | */ 159 | public function setSelector($selector) 160 | { 161 | $this->selector = $selector; 162 | } 163 | 164 | /** 165 | * Set the form selector to validate. 166 | * 167 | * @param string $selector 168 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 169 | */ 170 | public function selector($selector) 171 | { 172 | $this->selector = is_null($selector) ? $this->selector : $selector; 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Set the input selector to ignore for validation. 179 | * 180 | * @param string $ignore 181 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 182 | */ 183 | public function ignore($ignore) 184 | { 185 | $this->ignore = $ignore; 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Set the view to render Javascript Validations. 192 | * 193 | * @param null|\Illuminate\Contracts\View\View|string $view 194 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 195 | */ 196 | public function view($view) 197 | { 198 | $this->view = is_null($view) ? $this->view : $view; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * Enables or disables remote validations. 205 | * 206 | * @param null|bool $enabled 207 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 208 | */ 209 | public function remote($enabled = true) 210 | { 211 | $this->remote = $enabled; 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Validate Conditional Validations using Ajax in specified fields. 218 | * 219 | * @param string $attribute 220 | * @param string|array $rules 221 | * @param null $callback Dummy attribute to make API seamless with Laravel sometimes() 222 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 223 | */ 224 | public function sometimes($attribute, $rules, $callback = null) 225 | { 226 | $this->validator->sometimes($attribute, $rules); 227 | 228 | return $this; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Javascript/MessageParser.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 30 | $this->escape = $escape; 31 | } 32 | 33 | /** 34 | * Replace javascript error message place-holders with actual values. 35 | * 36 | * @param string $attribute 37 | * @param string $rule 38 | * @param array $parameters 39 | * @return mixed 40 | */ 41 | public function getMessage($attribute, $rule, $parameters) 42 | { 43 | $attribute = str_replace(JsValidatorFactory::ASTERISK, '*', $attribute); 44 | 45 | $data = $this->fakeValidationData($attribute, $rule, $parameters); 46 | 47 | $message = $this->validator->getMessage($attribute, $rule); 48 | $message = $this->validator->makeReplacements($message, $attribute, $rule, $parameters); 49 | 50 | $this->validator->setData($data); 51 | 52 | return $this->escape ? e($message) : $message; 53 | } 54 | 55 | /** 56 | * Creates fake data needed to parse messages 57 | * Returns original data. 58 | * 59 | * @param string $attribute 60 | * @param string $rule 61 | * @param $parameters 62 | * @return array 63 | */ 64 | protected function fakeValidationData($attribute, $rule, $parameters) 65 | { 66 | $data = $this->validator->getData(); 67 | 68 | $this->fakeFileData($data, $attribute); 69 | $this->fakeRequiredIfData($data, $rule, $parameters); 70 | 71 | return $data; 72 | } 73 | 74 | /** 75 | * Generate fake data to get RequiredIf message. 76 | * 77 | * @param $data 78 | * @param $rule 79 | * @param $parameters 80 | * @return void 81 | */ 82 | private function fakeRequiredIfData($data, $rule, $parameters) 83 | { 84 | if ($rule !== 'RequiredIf') { 85 | return; 86 | } 87 | 88 | $newData = $data; 89 | $newData[$parameters[0]] = $parameters[1]; 90 | $this->validator->setData($newData); 91 | } 92 | 93 | /** 94 | * Generate fake data to get file type messages. 95 | * 96 | * @param $data 97 | * @param $attribute 98 | * @return void 99 | */ 100 | private function fakeFileData($data, $attribute) 101 | { 102 | if (! $this->validator->hasRule($attribute, ['Mimes', 'Image'])) { 103 | return; 104 | } 105 | 106 | $newFiles = $data; 107 | $newFiles[$attribute] = $this->createUploadedFile(); 108 | $this->validator->setData($newFiles); 109 | } 110 | 111 | /** 112 | * Create fake UploadedFile to generate file messages. 113 | * 114 | * @return UploadedFile 115 | */ 116 | protected function createUploadedFile() 117 | { 118 | return new UploadedFile('fakefile', 'fakefile', null, UPLOAD_ERR_NO_FILE, true); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Javascript/RuleParser.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 59 | $this->remoteToken = $remoteToken; 60 | } 61 | 62 | /** 63 | * Return parsed Javascript Rule. 64 | * 65 | * @param string $attribute 66 | * @param string $rule 67 | * @param $parameters 68 | * @param $rawRule 69 | * @return array 70 | */ 71 | public function getRule($attribute, $rule, $parameters, $rawRule) 72 | { 73 | $isConditional = $this->isConditionalRule($attribute, $rawRule); 74 | $isRemote = $this->isRemoteRule($rule); 75 | $isFormRequest = $this->isFormRequestRule($rule); 76 | 77 | if ($isFormRequest || $isConditional || $isRemote) { 78 | [$attribute, $parameters] = $this->remoteRule($attribute, $isConditional); 79 | $jsRule = $isFormRequest ? static::FORM_REQUEST_RULE : static::REMOTE_RULE; 80 | } else { 81 | [$jsRule, $attribute, $parameters] = $this->clientRule($attribute, $rule, $parameters); 82 | } 83 | 84 | $attribute = $this->getAttributeName($attribute); 85 | 86 | return [$attribute, $jsRule, $parameters]; 87 | } 88 | 89 | /** 90 | * Gets rules from Validator instance. 91 | * 92 | * @return array 93 | */ 94 | public function getValidatorRules() 95 | { 96 | return $this->validator->getRules(); 97 | } 98 | 99 | /** 100 | * Add conditional rules. 101 | * 102 | * @param mixed $attribute 103 | * @param array $rules 104 | * @return void 105 | */ 106 | public function addConditionalRules($attribute, $rules = []) 107 | { 108 | foreach ((array) $attribute as $key) { 109 | $current = isset($this->conditional[$key]) ? $this->conditional[$key] : []; 110 | $merge = head($this->validator->explodeRules((array) $rules)); 111 | $this->conditional[$key] = array_merge($current, $merge); 112 | } 113 | } 114 | 115 | /** 116 | * Determine if rule is passed with sometimes. 117 | * 118 | * @param mixed $attribute 119 | * @param string $rule 120 | * @return bool 121 | */ 122 | protected function isConditionalRule($attribute, $rule) 123 | { 124 | return isset($this->conditional[$attribute]) 125 | && in_array($rule, $this->conditional[$attribute]); 126 | } 127 | 128 | /** 129 | * Returns Javascript parameters for remote validated rules. 130 | * 131 | * @param string $attribute 132 | * @param string $rule 133 | * @param $parameters 134 | * @return array 135 | */ 136 | protected function clientRule($attribute, $rule, $parameters) 137 | { 138 | $jsRule = self::JAVASCRIPT_RULE; 139 | $method = "rule{$rule}"; 140 | 141 | if (method_exists($this, $method)) { 142 | [$attribute, $parameters] = $this->$method($attribute, $parameters); 143 | } 144 | 145 | return [$jsRule, $attribute, $parameters]; 146 | } 147 | 148 | /** 149 | * Returns Javascript parameters for remote validated rules. 150 | * 151 | * @param string $attribute 152 | * @param bool $forceRemote 153 | * @return array 154 | */ 155 | protected function remoteRule($attribute, $forceRemote) 156 | { 157 | $attrHtmlName = $this->getAttributeName($attribute); 158 | $params = [ 159 | $attrHtmlName, 160 | $this->remoteToken, 161 | $forceRemote, 162 | ]; 163 | 164 | return [$attribute, $params]; 165 | } 166 | 167 | /** 168 | * Handles multidimensional attribute names. 169 | * 170 | * @param mixed $attribute 171 | * @return string 172 | */ 173 | protected function getAttributeName($attribute) 174 | { 175 | $attribute = str_replace(JsValidatorFactory::ASTERISK, '*', $attribute); 176 | 177 | $attributeArray = explode('.', $attribute); 178 | if (count($attributeArray) > 1) { 179 | return $attributeArray[0].'['.implode('][', array_slice($attributeArray, 1)).']'; 180 | } 181 | 182 | return $attribute; 183 | } 184 | 185 | /** 186 | * Parse named parameters to $key => $value items. 187 | * 188 | * @param array $parameters 189 | * @return array 190 | */ 191 | public function parseNamedParameters($parameters) 192 | { 193 | return array_reduce($parameters, function ($result, $item) { 194 | [$key, $value] = array_pad(explode('=', $item, 2), 2, null); 195 | 196 | $result[$key] = $value; 197 | 198 | return $result; 199 | }); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Javascript/ValidatorHandler.php: -------------------------------------------------------------------------------- 1 | rules = $rules; 42 | $this->messages = $messages; 43 | $this->validator = $rules->getDelegatedValidator(); 44 | } 45 | 46 | /** 47 | * Sets delegated Validator instance. 48 | * 49 | * @param \Proengsoft\JsValidation\Support\DelegatedValidator $validator 50 | * @return void 51 | */ 52 | public function setDelegatedValidator(DelegatedValidator $validator) 53 | { 54 | $this->validator = $validator; 55 | $this->rules->setDelegatedValidator($validator); 56 | $this->messages->setDelegatedValidator($validator); 57 | } 58 | 59 | /** 60 | * Enable or disables remote validations. 61 | * 62 | * @param bool $enabled 63 | * @return void 64 | */ 65 | public function setRemote($enabled) 66 | { 67 | $this->remote = $enabled; 68 | } 69 | 70 | /** 71 | * Generate Javascript Validations. 72 | * 73 | * @return array 74 | */ 75 | protected function generateJavascriptValidations() 76 | { 77 | $jsValidations = []; 78 | 79 | foreach ($this->validator->getRules() as $attribute => $rules) { 80 | if (! $this->jsValidationEnabled($attribute)) { 81 | continue; 82 | } 83 | 84 | $newRules = $this->jsConvertRules($attribute, $rules, $this->remote); 85 | $jsValidations = array_merge($jsValidations, $newRules); 86 | } 87 | 88 | return $jsValidations; 89 | } 90 | 91 | /** 92 | * Make Laravel Validations compatible with JQuery Validation Plugin. 93 | * 94 | * @param $attribute 95 | * @param $rules 96 | * @param bool $includeRemote 97 | * @return array 98 | */ 99 | protected function jsConvertRules($attribute, $rules, $includeRemote) 100 | { 101 | $jsRules = []; 102 | foreach ($rules as $rawRule) { 103 | [$rule, $parameters] = $this->validator->parseRule($rawRule); 104 | [$jsAttribute, $jsRule, $jsParams] = $this->rules->getRule($attribute, $rule, $parameters, $rawRule); 105 | if ($this->isValidatable($jsRule, $includeRemote)) { 106 | $jsRules[$jsAttribute][$jsRule][] = [ 107 | $rule, 108 | $jsParams, 109 | $this->messages->getMessage($attribute, $rule, $parameters), 110 | $this->validator->isImplicit($rule), 111 | $jsAttribute, 112 | ]; 113 | } 114 | } 115 | 116 | return $jsRules; 117 | } 118 | 119 | /** 120 | * Check if rule should be validated with javascript. 121 | * 122 | * @param $jsRule 123 | * @param bool $includeRemote 124 | * @return bool 125 | */ 126 | protected function isValidatable($jsRule, $includeRemote) 127 | { 128 | return $jsRule && ($includeRemote || $jsRule !== RuleParser::REMOTE_RULE); 129 | } 130 | 131 | /** 132 | * Check if JS Validation is disabled for attribute. 133 | * 134 | * @param $attribute 135 | * @return bool 136 | */ 137 | public function jsValidationEnabled($attribute) 138 | { 139 | return ! $this->validator->hasRule($attribute, self::JSVALIDATION_DISABLE); 140 | } 141 | 142 | /** 143 | * Returns view data to render javascript. 144 | * 145 | * @return array 146 | */ 147 | public function validationData() 148 | { 149 | $jsMessages = []; 150 | $jsValidations = $this->generateJavascriptValidations(); 151 | 152 | return [ 153 | 'rules' => $jsValidations, 154 | 'messages' => $jsMessages, 155 | ]; 156 | } 157 | 158 | /** 159 | * Validate Conditional Validations using Ajax in specified fields. 160 | * 161 | * @param string $attribute 162 | * @param string|array $rules 163 | * @return void 164 | */ 165 | public function sometimes($attribute, $rules = []) 166 | { 167 | $callback = function () { 168 | return true; 169 | }; 170 | $this->validator->sometimes($attribute, $rules, $callback); 171 | $this->rules->addConditionalRules($attribute, (array) $rules); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/JsValidationServiceProvider.php: -------------------------------------------------------------------------------- 1 | bootstrapConfigs(); 19 | $this->bootstrapViews(); 20 | $this->bootstrapValidator(); 21 | $this->publishAssets(); 22 | 23 | if ($this->app['config']->get('jsvalidation.disable_remote_validation') === false) { 24 | $this->app[Kernel::class]->pushMiddleware(RemoteValidationMiddleware::class); 25 | } 26 | } 27 | 28 | /** 29 | * Register the application services. 30 | * 31 | * @return void 32 | */ 33 | public function register() 34 | { 35 | $this->app->singleton('jsvalidator', function ($app) { 36 | $config = $app['config']->get('jsvalidation'); 37 | 38 | return new JsValidatorFactory($app, $config); 39 | }); 40 | } 41 | 42 | /** 43 | * Configure and publish views. 44 | * 45 | * @return void 46 | */ 47 | protected function bootstrapViews() 48 | { 49 | $viewPath = realpath(__DIR__.'/../resources/views'); 50 | 51 | $this->loadViewsFrom($viewPath, 'jsvalidation'); 52 | $this->publishes([ 53 | $viewPath => $this->app['path.base'].'/resources/views/vendor/jsvalidation', 54 | ], 'views'); 55 | } 56 | 57 | /** 58 | * Configure Laravel Validator. 59 | * 60 | * @return void 61 | */ 62 | protected function bootstrapValidator() 63 | { 64 | $callback = function () { 65 | return true; 66 | }; 67 | $this->app['validator']->extend(ValidatorHandler::JSVALIDATION_DISABLE, $callback); 68 | } 69 | 70 | /** 71 | * Load and publishes configs. 72 | * 73 | * @return void 74 | */ 75 | protected function bootstrapConfigs() 76 | { 77 | $configFile = realpath(__DIR__.'/../config/jsvalidation.php'); 78 | 79 | $this->mergeConfigFrom($configFile, 'jsvalidation'); 80 | $this->publishes([$configFile => $this->app['path.config'].'/jsvalidation.php'], 'config'); 81 | } 82 | 83 | /** 84 | * Publish public assets. 85 | * 86 | * @return void 87 | */ 88 | protected function publishAssets() 89 | { 90 | $this->publishes([ 91 | realpath(__DIR__.'/../public') => $this->app['path.public'].'/vendor/jsvalidation', 92 | ], 'public'); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/JsValidatorFactory.php: -------------------------------------------------------------------------------- 1 | app = $app; 44 | $this->setOptions($options); 45 | } 46 | 47 | /** 48 | * @param $options 49 | * @return void 50 | */ 51 | protected function setOptions($options) 52 | { 53 | $options['disable_remote_validation'] = empty($options['disable_remote_validation']) ? false : $options['disable_remote_validation']; 54 | $options['view'] = empty($options['view']) ? 'jsvalidation:bootstrap' : $options['view']; 55 | $options['form_selector'] = empty($options['form_selector']) ? 'form' : $options['form_selector']; 56 | 57 | $this->options = $options; 58 | } 59 | 60 | /** 61 | * Creates JsValidator instance based on rules and message arrays. 62 | * 63 | * @param array $rules 64 | * @param array $messages 65 | * @param array $customAttributes 66 | * @param null|string $selector 67 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 68 | */ 69 | public function make(array $rules, array $messages = [], array $customAttributes = [], $selector = null) 70 | { 71 | $validator = $this->getValidatorInstance($rules, $messages, $customAttributes); 72 | 73 | return $this->validator($validator, $selector); 74 | } 75 | 76 | /** 77 | * Get the validator instance for the request. 78 | * 79 | * @param array $rules 80 | * @param array $messages 81 | * @param array $customAttributes 82 | * @return \Illuminate\Validation\Validator 83 | */ 84 | protected function getValidatorInstance(array $rules, array $messages = [], array $customAttributes = []) 85 | { 86 | $factory = $this->app->make(ValidationFactory::class); 87 | 88 | $data = $this->getValidationData($rules, $customAttributes); 89 | $validator = $factory->make($data, $rules, $messages, $customAttributes); 90 | $validator->addCustomAttributes($customAttributes); 91 | 92 | return $validator; 93 | } 94 | 95 | /** 96 | * Gets fake data when validator has wildcard rules. 97 | * 98 | * @param array $rules 99 | * @param array $customAttributes 100 | * @return array 101 | */ 102 | protected function getValidationData(array $rules, array $customAttributes = []) 103 | { 104 | $attributes = array_filter(array_keys($rules), function ($attribute) { 105 | return $attribute !== '' && mb_strpos($attribute, '*') !== false; 106 | }); 107 | 108 | $attributes = array_merge(array_keys($customAttributes), $attributes); 109 | $data = array_reduce($attributes, function ($data, $attribute) { 110 | // Prevent wildcard rule being removed as an implicit attribute (not present in the data). 111 | $attribute = str_replace('*', self::ASTERISK, $attribute); 112 | 113 | Arr::set($data, $attribute, true); 114 | 115 | return $data; 116 | }, []); 117 | 118 | return $data; 119 | } 120 | 121 | /** 122 | * Creates JsValidator instance based on FormRequest. 123 | * 124 | * @param $formRequest 125 | * @param null|string $selector 126 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 127 | * 128 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 129 | */ 130 | public function formRequest($formRequest, $selector = null) 131 | { 132 | if (! is_object($formRequest)) { 133 | $formRequest = $this->createFormRequest($formRequest); 134 | } 135 | 136 | if ($formRequest instanceof FormRequest) { 137 | return $this->newFormRequestValidator($formRequest, $selector); 138 | } 139 | 140 | return $this->oldFormRequestValidator($formRequest, $selector); 141 | } 142 | 143 | /** 144 | * Create form request validator. 145 | * 146 | * @param FormRequest $formRequest 147 | * @param string $selector 148 | * @return JavascriptValidator 149 | */ 150 | private function newFormRequestValidator($formRequest, $selector) 151 | { 152 | // Replace all rules with Noop rules which are checked client-side and always valid to true. 153 | // This is important because jquery-validation expects fields under validation to have rules present. For 154 | // example, if you mark a field as invalid without a defined rule, then unhighlight won't be called. 155 | $rules = method_exists($formRequest, 'rules') ? $this->app->call([$formRequest, 'rules']) : []; 156 | foreach ($rules as $key => $value) { 157 | $rules[$key] = 'proengsoft_noop'; 158 | } 159 | 160 | // This rule controls AJAX validation of all fields. 161 | $rules['proengsoft_jsvalidation'] = RuleParser::FORM_REQUEST_RULE_NAME; 162 | 163 | $baseValidator = $this->getValidatorInstance($rules); 164 | 165 | return $this->validator($baseValidator, $selector); 166 | } 167 | 168 | /** 169 | * Create a form request validator instance. 170 | * 171 | * @param IlluminateFormRequest $formRequest 172 | * @param string $selector 173 | * @return JavascriptValidator 174 | */ 175 | private function oldFormRequestValidator($formRequest, $selector) 176 | { 177 | $rules = method_exists($formRequest, 'rules') ? $this->app->call([$formRequest, 'rules']) : []; 178 | 179 | $validator = $this->getValidatorInstance($rules, $formRequest->messages(), $formRequest->attributes()); 180 | 181 | $jsValidator = $this->validator($validator, $selector); 182 | 183 | if (method_exists($formRequest, 'withJsValidator')) { 184 | $formRequest->withJsValidator($jsValidator); 185 | } 186 | 187 | return $jsValidator; 188 | } 189 | 190 | /** 191 | * @param string|array $class 192 | * @return array 193 | */ 194 | protected function parseFormRequestName($class) 195 | { 196 | $params = []; 197 | if (is_array($class)) { 198 | $params = empty($class[1]) ? $params : $class[1]; 199 | $class = $class[0]; 200 | } 201 | 202 | return [$class, $params]; 203 | } 204 | 205 | /** 206 | * Creates and initializes an Form Request instance. 207 | * 208 | * @param string $class 209 | * @return IlluminateFormRequest 210 | * 211 | * @throws \Illuminate\Contracts\Container\BindingResolutionException 212 | */ 213 | protected function createFormRequest($class) 214 | { 215 | /* 216 | * @var $formRequest \Illuminate\Foundation\Http\FormRequest 217 | * @var $request Request 218 | */ 219 | [$class, $params] = $this->parseFormRequestName($class); 220 | 221 | $request = $this->app->__get('request'); 222 | $formRequest = $this->app->build($class, $params); 223 | 224 | if ($request->hasSession() && $session = $request->session()) { 225 | $formRequest->setLaravelSession($session); 226 | } 227 | $formRequest->setUserResolver($request->getUserResolver()); 228 | $formRequest->setRouteResolver($request->getRouteResolver()); 229 | $formRequest->setContainer($this->app); 230 | $formRequest->query = $request->query; 231 | 232 | return $formRequest; 233 | } 234 | 235 | /** 236 | * Creates JsValidator instance based on Validator. 237 | * 238 | * @param \Illuminate\Validation\Validator $validator 239 | * @param null|string $selector 240 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 241 | */ 242 | public function validator(Validator $validator, $selector = null) 243 | { 244 | return $this->jsValidator($validator, $selector); 245 | } 246 | 247 | /** 248 | * Creates JsValidator instance based on Validator. 249 | * 250 | * @param \Illuminate\Validation\Validator $validator 251 | * @param null|string $selector 252 | * @return \Proengsoft\JsValidation\Javascript\JavascriptValidator 253 | */ 254 | protected function jsValidator(Validator $validator, $selector = null) 255 | { 256 | $remote = ! $this->options['disable_remote_validation']; 257 | $view = $this->options['view']; 258 | $selector = is_null($selector) ? $this->options['form_selector'] : $selector; 259 | $ignore = $this->options['ignore']; 260 | 261 | $delegated = new DelegatedValidator($validator, new ValidationRuleParserProxy($validator->getData())); 262 | $rules = new RuleParser($delegated, $this->getSessionToken()); 263 | $messages = new MessageParser($delegated, isset($this->options['escape']) ? $this->options['escape'] : false); 264 | 265 | $jsValidator = new ValidatorHandler($rules, $messages); 266 | 267 | return new JavascriptValidator($jsValidator, compact('view', 'selector', 'remote', 'ignore')); 268 | } 269 | 270 | /** 271 | * Get and encrypt token from session store. 272 | * 273 | * @return null|string 274 | */ 275 | protected function getSessionToken() 276 | { 277 | $token = null; 278 | if ($session = $this->app->__get('session')) { 279 | $token = $session->token(); 280 | } 281 | 282 | if ($encrypter = $this->app->__get('encrypter')) { 283 | $token = $encrypter->encrypt($token); 284 | } 285 | 286 | return $token; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Remote/FormRequest.php: -------------------------------------------------------------------------------- 1 | passedValidation(); 29 | } 30 | 31 | /** 32 | * Handle a passed validation attempt. 33 | * 34 | * @return void 35 | */ 36 | protected function passedValidation() 37 | { 38 | if ($this->isJsValidation()) { 39 | throw new HttpResponseException( 40 | new JsonResponse(true, 200) 41 | ); 42 | } 43 | 44 | parent::passedValidation(); 45 | } 46 | 47 | /** 48 | * Handle a failed validation attempt. 49 | * 50 | * @param \Illuminate\Contracts\Validation\Validator $validator 51 | * @return void 52 | * 53 | * @throws \Illuminate\Validation\ValidationException 54 | */ 55 | protected function failedValidation(Validator $validator) 56 | { 57 | if ($this->isJsValidation()) { 58 | throw new ValidationException( 59 | $validator, 60 | new JsonResponse($validator->errors()->messages(), 200) 61 | ); 62 | } 63 | 64 | parent::failedValidation($validator); 65 | } 66 | 67 | /** 68 | * Whether the request originated from laravel-jsvalidation. 69 | * 70 | * @return bool 71 | */ 72 | private function isJsValidation() 73 | { 74 | return $this->has(static::JS_VALIDATION_FIELD); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Remote/Resolver.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 40 | $this->resolver = $this->getProtected($factory, 'resolver'); 41 | $this->escape = $escape; 42 | } 43 | 44 | /** 45 | * Closure used to resolve Validator instance. 46 | * 47 | * @param $field 48 | * @return \Closure 49 | */ 50 | public function resolver($field) 51 | { 52 | return function ($translator, $data, $rules, $messages, $customAttributes) use ($field) { 53 | return $this->resolve($translator, $data, $rules, $messages, $customAttributes, $field); 54 | }; 55 | } 56 | 57 | /** 58 | * Resolves Validator instance. 59 | * 60 | * @param $translator 61 | * @param $data 62 | * @param $rules 63 | * @param $messages 64 | * @param $customAttributes 65 | * @param $field 66 | * @return \Illuminate\Validation\Validator 67 | */ 68 | protected function resolve($translator, $data, $rules, $messages, $customAttributes, $field) 69 | { 70 | $validateAll = Arr::get($data, $field.'_validate_all', false); 71 | $validationRule = 'bail|'.Validator::EXTENSION_NAME.':'.$validateAll; 72 | $rules = [$field => $validationRule] + $rules; 73 | $validator = $this->createValidator($translator, $data, $rules, $messages, $customAttributes); 74 | 75 | return $validator; 76 | } 77 | 78 | /** 79 | * Create new validator instance. 80 | * 81 | * @param $translator 82 | * @param $data 83 | * @param $rules 84 | * @param $messages 85 | * @param $customAttributes 86 | * @return \Illuminate\Validation\Validator 87 | */ 88 | protected function createValidator($translator, $data, $rules, $messages, $customAttributes) 89 | { 90 | if (is_null($this->resolver)) { 91 | return new BaseValidator($translator, $data, $rules, $messages, $customAttributes); 92 | } 93 | 94 | return call_user_func($this->resolver, $translator, $data, $rules, $messages, $customAttributes); 95 | } 96 | 97 | /** 98 | * Closure used to trigger JsValidations. 99 | * 100 | * @return \Closure 101 | */ 102 | public function validatorClosure() 103 | { 104 | return function ($attribute, $value, $parameters, BaseValidator $validator) { 105 | $remoteValidator = new Validator($validator, $this->escape); 106 | $remoteValidator->validate($value, $parameters); 107 | 108 | return $attribute; 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Remote/Validator.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 45 | $this->escape = $escape; 46 | } 47 | 48 | /** 49 | * Validate request. 50 | * 51 | * @param $field 52 | * @param $parameters 53 | * @return void 54 | * 55 | * @throws \Illuminate\Validation\ValidationException 56 | */ 57 | public function validate($field, $parameters = []) 58 | { 59 | $attribute = $this->parseAttributeName($field); 60 | $validationParams = $this->parseParameters($parameters); 61 | $validationResult = $this->validateJsRemoteRequest($attribute, $validationParams); 62 | 63 | $this->throwValidationException($validationResult, $this->validator); 64 | } 65 | 66 | /** 67 | * Throw the failed validation exception. 68 | * 69 | * @param mixed $result 70 | * @param \Illuminate\Validation\Validator $validator 71 | * @return void 72 | * 73 | * @throws \Illuminate\Validation\ValidationException|\Illuminate\Http\Exceptions\HttpResponseException 74 | */ 75 | protected function throwValidationException($result, $validator) 76 | { 77 | $response = new JsonResponse($result, 200); 78 | 79 | if ($result !== true && class_exists(ValidationException::class)) { 80 | throw new ValidationException($validator, $response); 81 | } 82 | 83 | throw new HttpResponseException($response); 84 | } 85 | 86 | /** 87 | * Parse Validation input request data. 88 | * 89 | * @param $data 90 | * @return array 91 | */ 92 | protected function parseAttributeName($data) 93 | { 94 | parse_str($data, $attrParts); 95 | $attrParts = is_null($attrParts) ? [] : $attrParts; 96 | $newAttr = array_keys(Arr::dot($attrParts)); 97 | 98 | return array_pop($newAttr); 99 | } 100 | 101 | /** 102 | * Parse Validation parameters. 103 | * 104 | * @param $parameters 105 | * @return array 106 | */ 107 | protected function parseParameters($parameters) 108 | { 109 | $newParams = ['validate_all' => false]; 110 | if (isset($parameters[0])) { 111 | $newParams['validate_all'] = ($parameters[0] === 'true') ? true : false; 112 | } 113 | 114 | return $newParams; 115 | } 116 | 117 | /** 118 | * Validate remote Javascript Validations. 119 | * 120 | * @param $attribute 121 | * @param array $parameters 122 | * @return array|bool 123 | */ 124 | protected function validateJsRemoteRequest($attribute, $parameters) 125 | { 126 | $this->setRemoteValidation($attribute, $parameters['validate_all']); 127 | 128 | $validator = $this->validator; 129 | if ($validator->passes()) { 130 | return true; 131 | } 132 | 133 | $messages = $validator->messages()->get($attribute); 134 | 135 | if ($this->escape) { 136 | foreach ($messages as $key => $value) { 137 | $messages[$key] = e($value); 138 | } 139 | } 140 | 141 | return $messages; 142 | } 143 | 144 | /** 145 | * Sets data for validate remote rules. 146 | * 147 | * @param $attribute 148 | * @param bool $validateAll 149 | * @return void 150 | */ 151 | protected function setRemoteValidation($attribute, $validateAll = false) 152 | { 153 | $validator = $this->validator; 154 | $rules = $validator->getRules(); 155 | $rules = isset($rules[$attribute]) ? $rules[$attribute] : []; 156 | if (in_array('no_js_validation', $rules)) { 157 | $validator->setRules([$attribute => []]); 158 | 159 | return; 160 | } 161 | if (! $validateAll) { 162 | $rules = $this->purgeNonRemoteRules($rules, $validator); 163 | } 164 | $validator->setRules([$attribute => $rules]); 165 | } 166 | 167 | /** 168 | * Remove rules that should not be validated remotely. 169 | * 170 | * @param $rules 171 | * @param BaseValidator $validator 172 | * @return mixed 173 | */ 174 | protected function purgeNonRemoteRules($rules, $validator) 175 | { 176 | $protectedValidator = $this->createProtectedCaller($validator); 177 | 178 | foreach ($rules as $i => $rule) { 179 | $parsedRule = ValidationRuleParser::parse($rule); 180 | if (! $this->isRemoteRule($parsedRule[0])) { 181 | unset($rules[$i]); 182 | } 183 | } 184 | 185 | return $rules; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/RemoteValidationMiddleware.php: -------------------------------------------------------------------------------- 1 | factory = $validator; 44 | $this->field = $config->get('jsvalidation.remote_validation_field'); 45 | $this->escape = (bool) $config->get('jsvalidation.escape', false); 46 | } 47 | 48 | /** 49 | * Handle an incoming request. 50 | * 51 | * @param \Illuminate\Http\Request $request 52 | * @param \Closure $next 53 | * @return mixed 54 | */ 55 | public function handle(Request $request, Closure $next) 56 | { 57 | if ($request->has($this->field)) { 58 | $this->wrapValidator(); 59 | } 60 | 61 | return $next($request); 62 | } 63 | 64 | /** 65 | * Wraps Validator resolver with RemoteValidator resolver. 66 | * 67 | * @return void 68 | */ 69 | protected function wrapValidator() 70 | { 71 | $resolver = new Resolver($this->factory, $this->escape); 72 | $this->factory->resolver($resolver->resolver($this->field)); 73 | $this->factory->extend(RemoteValidator::EXTENSION_NAME, $resolver->validatorClosure()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Support/AccessProtectedTrait.php: -------------------------------------------------------------------------------- 1 | bindTo($instance, $instance); 24 | } 25 | 26 | /** 27 | * Gets inaccessible property. 28 | * 29 | * @param $instance 30 | * @param $property 31 | * @return \Closure 32 | */ 33 | protected function getProtected($instance, $property) 34 | { 35 | $closure = function ($property) { 36 | return $this->$property; 37 | }; 38 | $callback = $closure->bindTo($instance, $instance); 39 | 40 | return $callback($property); 41 | } 42 | 43 | /** 44 | * Calls inaccessible method. 45 | * 46 | * @param object|\Closure $instance 47 | * @param $method 48 | * @param $args 49 | * @return mixed 50 | */ 51 | protected function callProtected($instance, $method, $args = []) 52 | { 53 | if (! ($instance instanceof Closure)) { 54 | $instance = $this->createProtectedCaller($instance); 55 | } 56 | 57 | return call_user_func($instance, $method, $args); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Support/DelegatedValidator.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 41 | $this->ruleParser = $ruleParser; 42 | $this->validatorMethod = $this->createProtectedCaller($validator); 43 | } 44 | 45 | /** 46 | * Call validator method. 47 | * 48 | * @param string $method 49 | * @param array $args 50 | * @return mixed 51 | */ 52 | private function callValidator($method, $args = []) 53 | { 54 | return $this->callProtected($this->validatorMethod, $method, $args); 55 | } 56 | 57 | /** 58 | * Get current \Illuminate\Validation\Validator instance. 59 | * 60 | * @return \Illuminate\Validation\Validator 61 | */ 62 | public function getValidator() 63 | { 64 | return $this->validator; 65 | } 66 | 67 | /** 68 | * Get the data under validation. 69 | * 70 | * @return array 71 | */ 72 | public function getData() 73 | { 74 | return $this->validator->getData(); 75 | } 76 | 77 | /** 78 | * Set the data under validation. 79 | * 80 | * @param array 81 | */ 82 | public function setData($data) 83 | { 84 | $rules = $this->validator->getRules(); 85 | $this->validator->setData($data); 86 | if (is_array($rules)) { 87 | $this->validator->setRules($rules); 88 | } 89 | } 90 | 91 | /** 92 | * Get the validation rules. 93 | * 94 | * @return array 95 | */ 96 | public function getRules() 97 | { 98 | return $this->validator->getRules(); 99 | } 100 | 101 | /** 102 | * Determine if a given rule implies the attribute is required. 103 | * 104 | * @param string $rule 105 | * @return bool 106 | */ 107 | public function isImplicit($rule) 108 | { 109 | return $this->callValidator('isImplicit', [$rule]); 110 | } 111 | 112 | /** 113 | * Replace all error message place-holders with actual values. 114 | * 115 | * @param string $message 116 | * @param string $attribute 117 | * @param string $rule 118 | * @param array $parameters 119 | * @return string 120 | */ 121 | public function makeReplacements($message, $attribute, $rule, $parameters) 122 | { 123 | if (is_object($rule)) { 124 | $rule = get_class($rule); 125 | } 126 | 127 | return $this->callValidator('makeReplacements', [$message, $attribute, $rule, $parameters]); 128 | } 129 | 130 | /** 131 | * Determine if the given attribute has a rule in the given set. 132 | * 133 | * @param string $attribute 134 | * @param string|array $rules 135 | * @return bool 136 | */ 137 | public function hasRule($attribute, $rules) 138 | { 139 | return $this->callValidator('hasRule', [$attribute, $rules]); 140 | } 141 | 142 | /** 143 | * Get the validation message for an attribute and rule. 144 | * 145 | * @param string $attribute 146 | * @param string $rule 147 | * @return string 148 | */ 149 | public function getMessage($attribute, $rule) 150 | { 151 | if (is_object($rule)) { 152 | $rule = get_class($rule); 153 | } 154 | 155 | return $this->callValidator('getMessage', [$attribute, $rule]); 156 | } 157 | 158 | /** 159 | * Extract the rule name and parameters from a rule. 160 | * 161 | * @param array|string $rules 162 | * @return array 163 | */ 164 | public function parseRule($rules) 165 | { 166 | return $this->ruleParser->parse($rules); 167 | } 168 | 169 | /** 170 | * Explode the rules into an array of rules. 171 | * 172 | * @param string|array $rules 173 | * @return array 174 | */ 175 | public function explodeRules($rules) 176 | { 177 | return $this->ruleParser->explodeRules($rules); 178 | } 179 | 180 | /** 181 | * Add conditions to a given field based on a Closure. 182 | * 183 | * @param string $attribute 184 | * @param string|array $rules 185 | * @param callable $callback 186 | * @return void 187 | */ 188 | public function sometimes($attribute, $rules, callable $callback) 189 | { 190 | $this->validator->sometimes($attribute, $rules, $callback); 191 | } 192 | 193 | /** 194 | * Delegate method calls to validator instance. 195 | * 196 | * @param $method 197 | * @param $params 198 | * @return mixed 199 | */ 200 | public function __call($method, $params) 201 | { 202 | $arrCaller = [$this->validator, $method]; 203 | 204 | return call_user_func_array($arrCaller, $params); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Support/RuleListTrait.php: -------------------------------------------------------------------------------- 1 | clientRules) || in_array($rule, $this->serverRules); 54 | } 55 | 56 | /** 57 | * Check if rule must be validated in server-side. 58 | * 59 | * @param $rule 60 | * @return bool 61 | */ 62 | protected function isRemoteRule($rule) 63 | { 64 | return in_array($rule, $this->serverRules) || 65 | ! in_array($rule, $this->clientRules); 66 | } 67 | 68 | /** 69 | * Form request rule. 70 | * 71 | * @param string $rule 72 | * @return bool 73 | */ 74 | protected function isFormRequestRule($rule) 75 | { 76 | return $rule === RuleParser::FORM_REQUEST_RULE_NAME; 77 | } 78 | 79 | /** 80 | * Check if rule disables rule processing. 81 | * 82 | * @param $rule 83 | * @return bool 84 | */ 85 | protected function isDisableRule($rule) 86 | { 87 | return $rule === $this->disableJsValidationRule; 88 | } 89 | 90 | /** 91 | * Check if rules should be validated. 92 | * 93 | * @param $rules 94 | * @return bool 95 | */ 96 | protected function validationDisabled($rules) 97 | { 98 | $rules = (array) $rules; 99 | 100 | return in_array($this->disableJsValidationRule, $rules); 101 | } 102 | 103 | /** 104 | * Check if rules is for input file type. 105 | * 106 | * @param $rule 107 | * @return bool 108 | */ 109 | protected function isFileRule($rule) 110 | { 111 | return in_array($rule, $this->fileRules); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Support/UseDelegatedValidatorTrait.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 23 | } 24 | 25 | /** 26 | * Gets current DelegatedValidator instance. 27 | * 28 | * @return \Proengsoft\JsValidation\Support\DelegatedValidator 29 | */ 30 | public function getDelegatedValidator() 31 | { 32 | return $this->validator; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Support/ValidationRuleParserProxy.php: -------------------------------------------------------------------------------- 1 | parser = new ValidationRuleParser((array) $data); 33 | $this->parserMethod = $this->createProtectedCaller($this->parser); 34 | } 35 | 36 | /** 37 | * Extract the rule name and parameters from a rule. 38 | * 39 | * @param array|string $rules 40 | * @return array 41 | */ 42 | public function parse($rules) 43 | { 44 | return $this->parser->parse($rules); 45 | } 46 | 47 | /** 48 | * Explode the rules into an array of explicit rules. 49 | * 50 | * @param array $rules 51 | * @return mixed 52 | */ 53 | public function explodeRules($rules) 54 | { 55 | return $this->callProtected($this->parserMethod, 'explodeRules', [$rules]); 56 | } 57 | 58 | /** 59 | * Delegate method calls to parser instance. 60 | * 61 | * @param string $method 62 | * @param mixed $params 63 | * @return mixed 64 | */ 65 | public function __call($method, $params) 66 | { 67 | $arrCaller = [$this->parser, $method]; 68 | 69 | return call_user_func_array($arrCaller, $params); 70 | } 71 | } 72 | --------------------------------------------------------------------------------