├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── Tests └── app │ ├── .env │ ├── .env.test │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── assets │ └── js │ │ └── app.js │ ├── bin │ └── console │ ├── config │ ├── bootstrap.php │ ├── bundles.php │ ├── packages │ │ ├── assets.yaml │ │ ├── cache.yaml │ │ ├── dev │ │ │ ├── routing.yaml │ │ │ └── security_checker.yaml │ │ ├── framework.yaml │ │ ├── html_sanitizer.yaml │ │ ├── routing.yaml │ │ ├── sensio_framework_extra.yaml │ │ ├── test │ │ │ ├── framework.yaml │ │ │ ├── routing.yaml │ │ │ └── security.yaml │ │ ├── translation.yaml │ │ ├── twig.yaml │ │ ├── twig_extensions.yaml │ │ ├── validator.yaml │ │ └── webpack_encore.yaml │ ├── routes.yaml │ ├── routes │ │ ├── annotations.yaml │ │ └── dev │ │ │ └── twig.yaml │ └── services.yaml │ ├── package.json │ ├── public │ ├── index.php │ └── robots.txt │ ├── src │ ├── Controller │ │ └── DefaultController.php │ ├── Form │ │ └── TestForm.php │ └── Kernel.php │ ├── symfony.lock │ ├── templates │ ├── base.html.twig │ ├── bundles │ │ └── TwigBundle │ │ │ └── Exception │ │ │ ├── error.html.twig │ │ │ ├── error403.html.twig │ │ │ ├── error404.html.twig │ │ │ └── error500.html.twig │ ├── default │ │ └── index.html.twig │ └── form │ │ ├── fields.html.twig │ │ └── layout.html.twig │ ├── translations │ └── .gitignore │ └── webpack.config.js ├── UPGRADE-1.1.md ├── babel.config.js ├── composer.json ├── cypress.json ├── cypress └── integration │ └── form_spec.js ├── docker-compose.yml ├── package.json ├── phpdocker ├── README.html ├── README.md ├── nginx │ └── nginx.conf └── php-fpm │ ├── Dockerfile │ └── php-ini-overrides.ini ├── src ├── Controller │ ├── .gitignore │ └── AjaxController.php ├── DependencyInjection │ ├── Configuration.php │ └── FpJsFormValidatorExtension.php ├── Exception │ └── UndefinedFormException.php ├── Factory │ └── JsFormValidatorFactory.php ├── Form │ ├── Constraint │ │ └── UniqueEntity.php │ ├── Extension │ │ └── FormExtension.php │ └── Subscriber │ │ └── SubscriberToQueue.php ├── FpJsFormValidatorBundle.php ├── Model │ ├── JsConfig.php │ ├── JsFormElement.php │ └── JsModelAbstract.php ├── Resources │ ├── config │ │ ├── routing.xml │ │ └── services.xml │ ├── doc │ │ ├── 2_1.md │ │ ├── 2_2.md │ │ ├── 2_3.md │ │ ├── 3_0.md │ │ ├── 3_1.md │ │ ├── 3_10.md │ │ ├── 3_11.md │ │ ├── 3_12.md │ │ ├── 3_13.md │ │ ├── 3_2.md │ │ ├── 3_3.md │ │ ├── 3_4.md │ │ ├── 3_5.md │ │ ├── 3_6.md │ │ ├── 3_7.md │ │ ├── 3_8.md │ │ └── 3_9.md │ └── public │ │ └── js │ │ ├── FpJsFormValidator.js │ │ ├── FpJsFormValidatorWithJqueryInit.js │ │ ├── constraints │ │ ├── Blank.js │ │ ├── Blank.test.js │ │ ├── Callback.js │ │ ├── Callback.test.js │ │ ├── Choice.js │ │ ├── Choice.test.js │ │ ├── Count.js │ │ ├── Count.test.js │ │ ├── Date.js │ │ ├── Date.test.js │ │ ├── DateTime.js │ │ ├── DateTime.test.js │ │ ├── Email.js │ │ ├── Email.test.js │ │ ├── EqualTo.js │ │ ├── EqualTo.test.js │ │ ├── GreaterThan.js │ │ ├── GreaterThan.test.js │ │ ├── GreaterThanOrEqual.js │ │ ├── GreaterThanOrEqual.test.js │ │ ├── IdenticalTo.js │ │ ├── IdenticalTo.test.js │ │ ├── Ip.js │ │ ├── Ip.test.js │ │ ├── IsFalse.js │ │ ├── IsFalse.test.js │ │ ├── IsNull.js │ │ ├── IsNull.test.js │ │ ├── IsTrue.js │ │ ├── IsTrue.test.js │ │ ├── Length.js │ │ ├── Length.test.js │ │ ├── LessThan.js │ │ ├── LessThan.test.js │ │ ├── LessThanOrEqual.js │ │ ├── LessThanOrEqual.test.js │ │ ├── NotBlank.js │ │ ├── NotBlank.test.js │ │ ├── NotEqualTo.js │ │ ├── NotEqualTo.test.js │ │ ├── NotIdenticalTo.js │ │ ├── NotIdenticalTo.test.js │ │ ├── NotNull.js │ │ ├── NotNull.test.js │ │ ├── Range.js │ │ ├── Range.test.js │ │ ├── Regex.js │ │ ├── Regex.test.js │ │ ├── Time.js │ │ ├── Time.test.js │ │ ├── Type.js │ │ ├── Type.test.js │ │ ├── UniqueEntity.js │ │ ├── Url.js │ │ ├── Url.test.js │ │ ├── Valid.js │ │ └── index.js │ │ ├── jquery.fpjsformvalidator.js │ │ └── transformers │ │ ├── ArrayToParts.js │ │ ├── BooleanToString.js │ │ ├── ChoiceToBooleanArray.js │ │ ├── ChoiceToValue.js │ │ ├── ChoicesToBooleanArray.js │ │ ├── ChoicesToValues.js │ │ ├── DataTransformerChain.js │ │ ├── DateTimeToArray.js │ │ ├── ValueToDuplicates.js │ │ └── index.js └── Twig │ └── Extension │ └── JsFormValidatorTwigExtension.php └── symfony.lock /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | 4 | package-lock.json 5 | node_modules 6 | 7 | Tests/app/var 8 | Tests/app/public/bundles 9 | Tests/app/public/build 10 | cypress/plugins 11 | cypress 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | env: 3 | global: 4 | - TRAVIS_USER_ID=$(id -u) 5 | - TRAVIS_GROUP_ID=$(id -g) 6 | 7 | before_install: 8 | - "sed -i \"s#www_data_uid: 1000#www_data_uid: $TRAVIS_USER_ID#\" ./docker-compose.yml" 9 | - "sed -i \"s#www_data_gid: 1000#www_data_gid: $TRAVIS_GROUP_ID#\" ./docker-compose.yml" 10 | 11 | install: 12 | 13 | - docker-compose pull --parallel webserver php-fpm 14 | - docker-compose build --parallel webserver php-fpm 15 | - docker-compose up -d webserver php-fpm 16 | - docker-compose exec php-fpm composer install --no-interaction 17 | - docker-compose exec php-fpm Tests/app/bin/console assets:install -v Tests/app 18 | - docker-compose exec -w /application/Tests/app php-fpm npm i 19 | - docker-compose exec -w /application/Tests/app php-fpm npm run build 20 | - docker-compose exec php-fpm npm i 21 | 22 | script: 23 | - docker-compose exec php-fpm npm run test 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | ## Setup the development environment for this bundle (Linux/Ubuntu) 5 | 6 | 1) Clone the bundle 7 | ```bash 8 | cd /path/to/your/projects 9 | git clone https://github.com/formapro/JsFormValidatorBundle.git 10 | ``` 11 | 2) Install vendors via docker 12 | ```bash 13 | cd JsFormValidatorBundle 14 | docker-compose up -d 15 | docker exec -it PHP-CONTAINER-NAME bash 16 | composer install --dev 17 | ``` 18 | 4) Create var folder and set permissions 19 | ```bash 20 | mkdir Tests/app/var 21 | sudo chmod -R 0777 Tests/app/var 22 | ``` 23 | 3) Install assests 24 | ```bash 25 | npm i 26 | 27 | cd Tests/app/ 28 | ./bin/console assets:install Tests/app/public 29 | npm run build 30 | ``` 31 | 4) Run tests 32 | ```bash 33 | npm run test 34 | ``` 35 | 36 | ## Tests 37 | 38 | Basically the bundle covered by unit jest's test and e2e cypress test. 39 | The main test case is placed in ```Tests/Functional/MainFunctionalTest.php``` 40 | Unit tests are placed in main resource folder with suffix ```.test.js``` 41 | e2e test is placed in cypress folder in project root ```cypress/integration/form_spec.js``` 42 | 43 | The main idea of unit tests is covered constrain logic. 44 | The main idea of e2e test is visit route with example form with all symfony constraint and find used error messages. 45 | 46 | ## Javascripts 47 | 48 | All the javascripts placed in the ```Resources/pulic/js``` folder 49 | All of them are merged and included by assets command to the dev/test environments. 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 by Forma-Pro 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FpJsFormValidatorBundle 2 | [![Build Status](https://travis-ci.com/formapro/JsFormValidatorBundle.svg?branch=master)](https://app.travis-ci.com/formapro/JsFormValidatorBundle) 3 | [![Total Downloads](https://poser.pugx.org/fp/jsformvalidator-bundle/downloads.png)](https://packagist.org/packages/fp/jsformvalidator-bundle) 4 | 5 | This module enables validation of the Symfony 4 or later forms on the JavaScript side. 6 | It converts form type constraints into JavaScript validation rules. 7 | 8 | If you have Symfony 4.* - you need to use [Version 1.6.x-dev](https://github.com/formapro/JsFormValidatorBundle/tree/1.6) 9 | 10 | If you have Symfony 3.1.* - you need to use [Version 1.5.*](https://github.com/formapro/JsFormValidatorBundle/tree/1.5) 11 | 12 | If you have Symfony 3.0.* - you need to use [Version 1.4.*](https://github.com/formapro/JsFormValidatorBundle/tree/1.4) 13 | 14 | If you have Symfony 2.8.* or 2.7.* - you need to use [Version 1.3.*](https://github.com/formapro/JsFormValidatorBundle/tree/1.3) 15 | 16 | If you have Symfony 2.6.* or less - you need to use [Version 1.2.*](https://github.com/formapro/JsFormValidatorBundle/tree/1.2) 17 | 18 | ## 1 Installation 19 | 20 | ### 1.1 Download FpJsFormValidatorBundle using composer 21 | 22 | Run in terminal: 23 | ```bash 24 | $ composer require "fp/jsformvalidator-bundle":"dev-master" 25 | ``` 26 | Or if you do not want to unexpected problems better to use exact version. 27 | ```bash 28 | $ composer require "fp/jsformvalidator-bundle":"v1.6.*" 29 | ``` 30 | 31 | ### 1.2 Enable javascript libraries 32 | 33 | There are two ways to initialize javascript's files for this library. 34 | You can create a new entry in the webpack or import the main file into your javascript. 35 | 36 | #### 1.2.1 Add FpJsFormValidatorBundle to webpack.config.js 37 | ```diff 38 | Encore 39 | ... 40 | .addEntry('app', './assets/js/app.js') 41 | + .addEntry('FpJsFormElement', './vendor/fp/jsformvalidator-bundle/Fp/JsFormValidatorBundle/Resources/public/js/FpJsFormValidatorWithJqueryInit.js') 42 | ... 43 | .configureBabel(null, { 44 | useBuiltIns: 'usage', 45 | corejs: 3, 46 | }) 47 | ; 48 | ``` 49 | 50 | And include new entry in your template 51 | ```diff 52 | + {{ encore_entry_script_tags('FpJsFormElement') }} 53 | {{ encore_entry_script_tags('app') }} 54 | ``` 55 | 56 | #### 1.2.2 Import FpJsFormValidatorBundle in your main javascript 57 | ```diff 58 | import $ from 'jquery'; 59 | + import 'path-to-bundles/fpjsformvalidator/js/FpJsFormValidator'; 60 | + import 'path-to-bundles/fpjsformvalidator/js/jquery.fpjsformvalidator'; 61 | ``` 62 | 63 | #### 1.2.3 Use inits in your template 64 | ```diff 65 | {% block javascripts %} 66 | + {{ js_validator_config() }} 67 | + {{ init_js_validation() }} 68 | {% endblock %} 69 | ``` 70 | 71 | ### 1.4 Add routes 72 | 73 | If you use the UniqueEntity constraint, then you have to include the next part to your routing config: app/config/routing.yml 74 | ```yaml 75 | # ... 76 | fp_js_form_validator: 77 | resource: "@FpJsFormValidatorBundle/Resources/config/routing.xml" 78 | prefix: /fp_js_form_validator 79 | ``` 80 | Make sure that your security settings do not prevent these routes. 81 | 82 | ## 2 Usage 83 | 84 | After the previous steps the javascript validation will be enabled automatically for all your forms. 85 | 86 | 1. [Disabling validation](src/Resources/doc/2_1.md) 87 | 2. [If your forms are placed in sub-requests](src/Resources/doc/2_2.md) 88 | 3. If you need to initialize JS validation for your forms separately, or by some event, in this case you need to follow [these steps](src/Resources/doc/2_3.md) instead of the [chapter 1.3](#p_1_3) 89 | 90 | ## 3 Customization 91 | 92 | ### Preface 93 | 94 | This bundle finds related DOM elements for each element of a symfony form and attach to it a special object-validator. 95 | This object contains list of properties and methods which fully define the validation process for the related form element. 96 | And some of those properties and methods can be changed to customize the validation process. 97 | 98 | If you render forms with a some level of customization - read [this note](src/Resources/doc/3_0.md). 99 | 100 | 1. [Disable validation for a specified field](src/Resources/doc/3_1.md) 101 | 2. [Error display](src/Resources/doc/3_2.md) 102 | 3. [Get validation groups from a closure](src/Resources/doc/3_3.md) 103 | 4. [Getters validation](src/Resources/doc/3_4.md) 104 | 5. [The Callback constraint](src/Resources/doc/3_5.md) 105 | 6. [The Choice constraint. How to get the choices list from a callback](src/Resources/doc/3_6.md) 106 | 7. [Custom constraints](src/Resources/doc/3_7.md) 107 | 8. [Custom data transformers](src/Resources/doc/3_8.md) 108 | 9. [Checking the uniqueness of entities](src/Resources/doc/3_9.md) 109 | 10. [Form submit by Javasrcipt](src/Resources/doc/3_10.md) 110 | 11. [onValidate callback](src/Resources/doc/3_11.md) 111 | 12. [Run validation on custom event](Resources/doc/3_12.md) 112 | 13. [Collections validation](src/Resources/doc/3_13.md) 113 | -------------------------------------------------------------------------------- /Tests/app/.env: -------------------------------------------------------------------------------- 1 | # This file is a "template" of which env vars needs to be defined in your configuration or in an .env file 2 | # Set variables here that may be different on each deployment target of the app, e.g. development, staging, production. 3 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration 4 | 5 | ###> symfony/framework-bundle ### 6 | APP_ENV=dev 7 | APP_DEBUG=1 8 | APP_SECRET=67d829bf61dc5f87a73fd814e2c9f629 9 | ###< symfony/framework-bundle ### 10 | -------------------------------------------------------------------------------- /Tests/app/.env.test: -------------------------------------------------------------------------------- 1 | # This file will be loaded after .env file and override environment 2 | # variables for your test environment. 3 | DATABASE_URL=sqlite:///%kernel.project_dir%/data/database_test.sqlite 4 | -------------------------------------------------------------------------------- /Tests/app/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | The Symfony Demo application is an open source project. Contributions made by 5 | the community are welcome. Send us your ideas, code reviews, pull requests and 6 | feature requests to help us improve this project. All contributions must follow 7 | the [usual Symfony contribution requirements](https://symfony.com/doc/current/contributing/index.html). 8 | -------------------------------------------------------------------------------- /Tests/app/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2019 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Tests/app/README.md: -------------------------------------------------------------------------------- 1 | Symfony Demo Application 2 | ======================== 3 | 4 | The "Symfony Demo Application" is a reference application created to show how 5 | to develop applications following the [Symfony Best Practices][1]. 6 | 7 | Requirements 8 | ------------ 9 | 10 | * PHP 7.1.3 or higher; 11 | * PDO-SQLite PHP extension enabled; 12 | * and the [usual Symfony application requirements][2]. 13 | 14 | Installation 15 | ------------ 16 | 17 | Install the [Symfony client][4] binary and run this command: 18 | 19 | ```bash 20 | $ symfony new --demo my_project 21 | ``` 22 | 23 | Alternatively, you can use Composer: 24 | 25 | ```bash 26 | $ composer create-project symfony/symfony-demo my_project 27 | ``` 28 | 29 | Usage 30 | ----- 31 | 32 | There's no need to configure anything to run the application. If you have 33 | installed the [Symfony client][4] binary, run this command to run the built-in 34 | web server and access the application in your browser at : 35 | 36 | ```bash 37 | $ cd my_project/ 38 | $ symfony serve 39 | ``` 40 | 41 | If you don't have the Symfony client installed, run `php bin/console server:run`. 42 | Alternatively, you can [configure a web server][3] like Nginx or Apache to run 43 | the application. 44 | 45 | Tests 46 | ----- 47 | 48 | Execute this command to run tests: 49 | 50 | ```bash 51 | $ cd my_project/ 52 | $ ./bin/phpunit 53 | ``` 54 | 55 | [1]: https://symfony.com/doc/current/best_practices/index.html 56 | [2]: https://symfony.com/doc/current/reference/requirements.html 57 | [3]: https://symfony.com/doc/current/cookbook/configuration/web_server_configuration.html 58 | [4]: https://symfony.com/download 59 | [5]: https://github.com/symfony/webpack-encore 60 | -------------------------------------------------------------------------------- /Tests/app/assets/js/app.js: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | -------------------------------------------------------------------------------- /Tests/app/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 19 | putenv('APP_ENV='.$_ENV['APP_ENV']); 20 | // force loading .env files when --env is defined 21 | $_SERVER['APP_ENV'] = null; 22 | } 23 | 24 | if ($input->hasParameterOption('--no-debug', true)) { 25 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 26 | } 27 | 28 | require dirname(__DIR__).'/config/bootstrap.php'; 29 | 30 | if ($_SERVER['APP_DEBUG']) { 31 | umask(0000); 32 | 33 | if (class_exists(Debug::class)) { 34 | Debug::enable(); 35 | } 36 | } 37 | 38 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 39 | $application = new Application($kernel); 40 | $application->run($input); 41 | -------------------------------------------------------------------------------- /Tests/app/config/bootstrap.php: -------------------------------------------------------------------------------- 1 | =1.2) 9 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) { 10 | foreach ($env as $k => $v) { 11 | $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); 12 | } 13 | } elseif (!class_exists(Dotenv::class)) { 14 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); 15 | } else { 16 | // load all the .env files 17 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 18 | } 19 | 20 | $_SERVER += $_ENV; 21 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 22 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 23 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 24 | -------------------------------------------------------------------------------- /Tests/app/config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 6 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 7 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 8 | Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], 9 | HtmlSanitizer\Bundle\HtmlSanitizerBundle::class => ['all' => true], 10 | Fp\JsFormValidatorBundle\FpJsFormValidatorBundle::class => ['all' => true], 11 | ]; 12 | -------------------------------------------------------------------------------- /Tests/app/config/packages/assets.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | assets: 3 | json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' 4 | -------------------------------------------------------------------------------- /Tests/app/config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Put the unique name of your app here: the prefix seed 4 | # is used to compute stable namespaces for cache keys. 5 | #prefix_seed: your_vendor_name/app_name 6 | 7 | # The app cache caches to the filesystem by default. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: ~ 20 | -------------------------------------------------------------------------------- /Tests/app/config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /Tests/app/config/packages/dev/security_checker.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | SensioLabs\Security\SecurityChecker: 3 | public: false 4 | 5 | SensioLabs\Security\Command\SecurityCheckerCommand: 6 | arguments: ['@SensioLabs\Security\SecurityChecker'] 7 | tags: 8 | - { name: console.command } 9 | -------------------------------------------------------------------------------- /Tests/app/config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | csrf_protection: true 4 | http_method_override: true 5 | trusted_hosts: ~ 6 | session: 7 | # With this config, PHP's native session handling is used 8 | handler_id: ~ 9 | cookie_secure: auto 10 | cookie_samesite: lax 11 | # When using the HTTP Cache, ESI allows to render page fragments separately 12 | # and with different cache configurations for each fragment 13 | # https://symfony.com/doc/current/book/http_cache.html#edge-side-includes 14 | esi: true 15 | fragments: true 16 | php_errors: 17 | log: true 18 | assets: 19 | json_manifest_path: '%kernel.project_dir%/public/build/manifest.json' 20 | cache: 21 | # this value is used as part of the "namespace" generated for the cache item keys 22 | # to avoid collisions when multiple apps share the same cache backend (e.g. a Redis server) 23 | # See https://symfony.com/doc/current/reference/configuration/framework.html#prefix-seed 24 | prefix_seed: symfony-demo 25 | # The 'ide' option turns all of the file paths in an exception page 26 | # into clickable links that open the given file using your favorite IDE. 27 | # When 'ide' is set to null the file is opened in your web browser. 28 | # See https://symfony.com/doc/current/reference/configuration/framework.html#ide 29 | ide: ~ 30 | validation: 31 | email_validation_mode: 'html5' 32 | enable_annotations: true 33 | -------------------------------------------------------------------------------- /Tests/app/config/packages/html_sanitizer.yaml: -------------------------------------------------------------------------------- 1 | html_sanitizer: 2 | default_sanitizer: 'default' 3 | sanitizers: 4 | default: 5 | # Read https://github.com/tgalopin/html-sanitizer/blob/master/docs/1-getting-started.md#extensions 6 | # to learn more about which extensions you would like to enable. 7 | extensions: 8 | - 'basic' 9 | - 'list' 10 | - 'table' 11 | - 'image' 12 | - 'code' 13 | 14 | # Read https://github.com/tgalopin/html-sanitizer/blob/master/docs/3-configuration-reference.md 15 | # to discover all the available options for each extension. 16 | -------------------------------------------------------------------------------- /Tests/app/config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: ~ 4 | utf8: true 5 | -------------------------------------------------------------------------------- /Tests/app/config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /Tests/app/config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /Tests/app/config/packages/test/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: true 4 | -------------------------------------------------------------------------------- /Tests/app/config/packages/test/security.yaml: -------------------------------------------------------------------------------- 1 | # this configuration simplifies testing URLs protected by the security mechanism 2 | # See https://symfony.com/doc/current/cookbook/testing/http_authentication.html 3 | security: 4 | firewalls: 5 | main: 6 | http_basic: ~ 7 | -------------------------------------------------------------------------------- /Tests/app/config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | -------------------------------------------------------------------------------- /Tests/app/config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | debug: '%kernel.debug%' 3 | default_path: '%kernel.project_dir%/templates' 4 | strict_variables: '%kernel.debug%' 5 | form_themes: 6 | - 'form/layout.html.twig' 7 | - 'form/fields.html.twig' 8 | -------------------------------------------------------------------------------- /Tests/app/config/packages/twig_extensions.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | public: false 4 | autowire: true 5 | autoconfigure: true 6 | 7 | #Twig\Extensions\ArrayExtension: ~ 8 | #Twig\Extensions\DateExtension: ~ 9 | Twig\Extensions\IntlExtension: ~ # needed for the 'localizeddate' filter 10 | #Twig\Extensions\TextExtension: ~ 11 | -------------------------------------------------------------------------------- /Tests/app/config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | -------------------------------------------------------------------------------- /Tests/app/config/packages/webpack_encore.yaml: -------------------------------------------------------------------------------- 1 | webpack_encore: 2 | # The path where Encore is building the assets. 3 | # This should match Encore.setOutputPath() in webpack.config.js. 4 | output_path: '%kernel.project_dir%/public/build' 5 | -------------------------------------------------------------------------------- /Tests/app/config/routes.yaml: -------------------------------------------------------------------------------- 1 | # These lines define a route using YAML configuration. The controller used by 2 | # the route (FrameworkBundle:Template:template) is a convenient shortcut when 3 | # the template can be rendered without executing any logic in your own controller. 4 | # See https://symfony.com/doc/current/cookbook/templating/render_without_controller.html 5 | 6 | fp_js_form_validator: 7 | resource: "@FpJsFormValidatorBundle/Resources/config/routing.xml" 8 | prefix: /fp_js_form_validator 9 | -------------------------------------------------------------------------------- /Tests/app/config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: '../src/Controller/' 3 | type: annotation 4 | prefix: / 5 | requirements: 6 | _locale: '%app_locales%' 7 | defaults: 8 | _locale: '%locale%' 9 | -------------------------------------------------------------------------------- /Tests/app/config/routes/dev/twig.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@TwigBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /Tests/app/config/services.yaml: -------------------------------------------------------------------------------- 1 | # Put parameters here that don't need to change on each machine where the app is deployed 2 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 3 | parameters: 4 | locale: 'en' 5 | # This parameter defines the codes of the locales (languages) enabled in the application 6 | app_locales: en 7 | app.notifications.email_sender: anonymous@example.com 8 | 9 | services: 10 | # default configuration for services in *this* file 11 | _defaults: 12 | autowire: true # Automatically injects dependencies in your services. 13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 14 | bind: # defines the scalar arguments once and apply them to any service defined/created in this file 15 | $locales: '%app_locales%' 16 | $defaultLocale: '%locale%' 17 | $emailSender: '%app.notifications.email_sender%' 18 | 19 | # makes classes in src/ available to be used as services 20 | # this creates a service per class whose id is the fully-qualified class name 21 | App\: 22 | resource: '../src/*' 23 | exclude: '../src/{Entity,Migrations,Tests,Kernel.php}' 24 | 25 | # controllers are imported separately to make sure services can be injected 26 | # as action arguments even if you don't extend any base controller class 27 | App\Controller\: 28 | resource: '../src/Controller' 29 | tags: ['controller.service_arguments'] 30 | -------------------------------------------------------------------------------- /Tests/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "core-js": "^3.0.0", 4 | "@symfony/webpack-encore": "^0.28.0" 5 | }, 6 | "dependencies": { 7 | "jquery": "^3.3.1" 8 | }, 9 | "license": "UNLICENSED", 10 | "private": true, 11 | "scripts": { 12 | "dev-server": "encore dev-server", 13 | "dev": "encore dev", 14 | "watch": "encore dev --watch", 15 | "build": "encore production --progress" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/app/public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /Tests/app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | -------------------------------------------------------------------------------- /Tests/app/src/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | createForm(TestForm::class); 19 | 20 | return $this->render('default/index.html.twig', [ 21 | 'testForm' => $testForm->createView(), 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/app/src/Form/TestForm.php: -------------------------------------------------------------------------------- 1 | 'a', 24 | 'True' => 'b', 25 | 'False' => 'c', 26 | 'Null' => 'd', 27 | ]; 28 | 29 | $builder 30 | ->add('notBlank', TextType::class, [ 31 | 'constraints' => [ 32 | new Constraints\NotBlank(['message' => 'Please fill field']), 33 | ], 34 | ]) 35 | ->add('blank', TextType::class, [ 36 | 'constraints' => [ 37 | new Constraints\Blank(['message' => 'Please do not fill field']), 38 | ], 39 | ]) 40 | ->add('choice', TextType::class, [ 41 | 'constraints' => [ 42 | new Constraints\Choice(['choices' => ['1', '2', '3'], 'message' => 'Please fill field correct value (1,2,3)']), 43 | ], 44 | ]) 45 | ->add('date', TextType::class, [ 46 | 'constraints' => [ 47 | new Constraints\Date(['message' => 'Please fill valid date']), 48 | ], 49 | ]) 50 | ->add('datetime', TextType::class, [ 51 | 'constraints' => [ 52 | new Constraints\DateTime(['message' => 'Please fill valid date time']), 53 | ], 54 | ]) 55 | ->add('email', TextType::class, [ 56 | 'constraints' => [ 57 | new Constraints\Email(['message' => 'Please fill valid email']), 58 | ], 59 | ]) 60 | ->add('equalTo', TextType::class, [ 61 | 'constraints' => [ 62 | new Constraints\EqualTo(['value' => 'abc', 'message' => 'Please fill correct value (20)']), 63 | ], 64 | ]) 65 | ->add('greaterThan', TextType::class, [ 66 | 'constraints' => [ 67 | new Constraints\GreaterThan(['value' => '20', 'message' => 'Please fill greater than 20 value']), 68 | ], 69 | ]) 70 | ->add('greaterThanOrEqual', TextType::class, [ 71 | 'constraints' => [ 72 | new Constraints\GreaterThanOrEqual(['value' => '20', 'message' => 'Please fill greater than or equal 20 value']), 73 | ], 74 | ]) 75 | ->add('ip', TextType::class, [ 76 | 'constraints' => [ 77 | new Constraints\Ip(['message' => 'Please fill valid IP']), 78 | ], 79 | ]) 80 | ->add('isFalse', CheckboxType::class, [ 81 | 'value' => true, 82 | 'constraints' => [ 83 | new Constraints\IsFalse(['message' => 'Please choice false']), 84 | ], 85 | ]) 86 | ->add('isTrue', CheckboxType::class, [ 87 | 'value' => true, 88 | 'constraints' => [ 89 | new Constraints\IsTrue(['message' => 'Please choice true']), 90 | ], 91 | ]) 92 | ->add('lessThan', TextType::class, [ 93 | 'constraints' => [ 94 | new Constraints\LessThan(['value' => '20', 'message' => 'Please fill least than 20 value']), 95 | ], 96 | ]) 97 | ->add('lessThanOrEqual', TextType::class, [ 98 | 'constraints' => [ 99 | new Constraints\LessThanOrEqual(['value' => '20', 'message' => 'Please fill least than or equal 20 value']), 100 | ], 101 | ]) 102 | ->add('notEqualTo', TextType::class, [ 103 | 'constraints' => [ 104 | new Constraints\NotEqualTo(['value' => 'abc', 'message' => 'Please fill correct value (not abc)']), 105 | ], 106 | ]) 107 | ->add('range', TextType::class, [ 108 | 'constraints' => [ 109 | new Constraints\Range([ 110 | 'min' => 120, 111 | 'max' => 180, 112 | 'minMessage' => 'You must be at least {{ limit }}', 113 | 'maxMessage' => 'You cannot be taller than {{ limit }}', 114 | ]), 115 | ], 116 | ]) 117 | ->add('url', TextType::class, [ 118 | 'constraints' => [ 119 | new Constraints\Url(['message' => 'Please fill valid url']), 120 | ], 121 | ]) 122 | 123 | ->add('save', SubmitType::class) 124 | ; 125 | } 126 | 127 | public function configureOptions(OptionsResolver $resolver) 128 | { 129 | $resolver->setDefaults([ 130 | 'attr' => ['novalidate' => 'novalidate'], 131 | ]); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Tests/app/src/Kernel.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace App; 13 | 14 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 15 | use Symfony\Component\Config\Loader\LoaderInterface; 16 | use Symfony\Component\Config\Resource\FileResource; 17 | use Symfony\Component\DependencyInjection\ContainerBuilder; 18 | use Symfony\Component\HttpKernel\Kernel as BaseKernel; 19 | use Symfony\Component\Routing\RouteCollectionBuilder; 20 | 21 | class Kernel extends BaseKernel 22 | { 23 | use MicroKernelTrait; 24 | 25 | const CONFIG_EXTS = '.{php,xml,yaml,yml}'; 26 | 27 | public function getCacheDir() 28 | { 29 | return $this->getProjectDir().'/var/cache/'.$this->environment; 30 | } 31 | 32 | public function getLogDir() 33 | { 34 | return $this->getProjectDir().'//var/log'; 35 | } 36 | 37 | public function getProjectDir() 38 | { 39 | return parent::getProjectDir() . '/Tests/app'; 40 | } 41 | 42 | public function registerBundles() 43 | { 44 | $contents = require $this->getProjectDir().'/config/bundles.php'; 45 | foreach ($contents as $class => $envs) { 46 | if ($envs[$this->environment] ?? $envs['all'] ?? false) { 47 | yield new $class(); 48 | } 49 | } 50 | } 51 | 52 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader) 53 | { 54 | $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); 55 | $container->setParameter('container.dumper.inline_class_loader', true); 56 | $confDir = $this->getProjectDir().'/config'; 57 | 58 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); 59 | $loader->load($confDir.'/{packages}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); 60 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); 61 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); 62 | } 63 | 64 | protected function configureRoutes(RouteCollectionBuilder $routes) 65 | { 66 | $confDir = $this->getProjectDir().'/config'; 67 | 68 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); 69 | $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob'); 70 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/app/symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "composer/ca-bundle": { 3 | "version": "1.2.6" 4 | }, 5 | "doctrine/annotations": { 6 | "version": "1.0", 7 | "recipe": { 8 | "repo": "github.com/symfony/recipes", 9 | "branch": "master", 10 | "version": "1.0", 11 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" 12 | }, 13 | "files": [ 14 | "config/routes/annotations.yaml" 15 | ] 16 | }, 17 | "doctrine/lexer": { 18 | "version": "1.2.0" 19 | }, 20 | "erusev/parsedown": { 21 | "version": "1.7.4" 22 | }, 23 | "league/uri-parser": { 24 | "version": "1.4.1" 25 | }, 26 | "masterminds/html5": { 27 | "version": "2.7.0" 28 | }, 29 | "php": { 30 | "version": "7.2" 31 | }, 32 | "psr/cache": { 33 | "version": "1.0.1" 34 | }, 35 | "psr/container": { 36 | "version": "1.0.0" 37 | }, 38 | "psr/log": { 39 | "version": "1.1.2" 40 | }, 41 | "sensio/framework-extra-bundle": { 42 | "version": "5.2", 43 | "recipe": { 44 | "repo": "github.com/symfony/recipes", 45 | "branch": "master", 46 | "version": "5.2", 47 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" 48 | }, 49 | "files": [ 50 | "config/packages/sensio_framework_extra.yaml" 51 | ] 52 | }, 53 | "sensiolabs/security-checker": { 54 | "version": "4.0", 55 | "recipe": { 56 | "repo": "github.com/symfony/recipes", 57 | "branch": "master", 58 | "version": "4.0", 59 | "ref": "160c9b600564faa1224e8f387d49ef13ceb8b793" 60 | }, 61 | "files": [ 62 | "config/packages/security_checker.yaml" 63 | ] 64 | }, 65 | "symfony/asset": { 66 | "version": "v5.0.3" 67 | }, 68 | "symfony/cache": { 69 | "version": "v5.0.3" 70 | }, 71 | "symfony/cache-contracts": { 72 | "version": "v2.0.1" 73 | }, 74 | "symfony/config": { 75 | "version": "v5.0.3" 76 | }, 77 | "symfony/console": { 78 | "version": "4.4", 79 | "recipe": { 80 | "repo": "github.com/symfony/recipes", 81 | "branch": "master", 82 | "version": "4.4", 83 | "ref": "ea8c0eda34fda57e7d5cd8cbd889e2a387e3472c" 84 | }, 85 | "files": [ 86 | "bin/console", 87 | "config/bootstrap.php" 88 | ] 89 | }, 90 | "symfony/debug": { 91 | "version": "v4.4.3" 92 | }, 93 | "symfony/dependency-injection": { 94 | "version": "v5.0.3" 95 | }, 96 | "symfony/dotenv": { 97 | "version": "v5.0.3" 98 | }, 99 | "symfony/error-handler": { 100 | "version": "v4.4.3" 101 | }, 102 | "symfony/event-dispatcher": { 103 | "version": "v4.4.3" 104 | }, 105 | "symfony/event-dispatcher-contracts": { 106 | "version": "v1.1.7" 107 | }, 108 | "symfony/expression-language": { 109 | "version": "v5.0.3" 110 | }, 111 | "symfony/filesystem": { 112 | "version": "v5.0.3" 113 | }, 114 | "symfony/finder": { 115 | "version": "v5.0.3" 116 | }, 117 | "symfony/flex": { 118 | "version": "1.0", 119 | "recipe": { 120 | "repo": "github.com/symfony/recipes", 121 | "branch": "master", 122 | "version": "1.0", 123 | "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" 124 | }, 125 | "files": [ 126 | ".env" 127 | ] 128 | }, 129 | "symfony/form": { 130 | "version": "v4.4.3" 131 | }, 132 | "symfony/framework-bundle": { 133 | "version": "4.4", 134 | "recipe": { 135 | "repo": "github.com/symfony/recipes", 136 | "branch": "master", 137 | "version": "4.4", 138 | "ref": "23ecaccc551fe2f74baf613811ae529eb07762fa" 139 | }, 140 | "files": [ 141 | "config/bootstrap.php", 142 | "config/packages/cache.yaml", 143 | "config/packages/framework.yaml", 144 | "config/packages/test/framework.yaml", 145 | "config/routes/dev/framework.yaml", 146 | "config/services.yaml", 147 | "public/index.php", 148 | "src/Controller/.gitignore", 149 | "src/Kernel.php" 150 | ] 151 | }, 152 | "symfony/http-foundation": { 153 | "version": "v5.0.3" 154 | }, 155 | "symfony/http-kernel": { 156 | "version": "v4.4.3" 157 | }, 158 | "symfony/inflector": { 159 | "version": "v5.0.3" 160 | }, 161 | "symfony/intl": { 162 | "version": "v5.0.3" 163 | }, 164 | "symfony/mime": { 165 | "version": "v5.0.3" 166 | }, 167 | "symfony/options-resolver": { 168 | "version": "v5.0.3" 169 | }, 170 | "symfony/polyfill-ctype": { 171 | "version": "v1.13.1" 172 | }, 173 | "symfony/polyfill-intl-icu": { 174 | "version": "v1.13.1" 175 | }, 176 | "symfony/polyfill-intl-idn": { 177 | "version": "v1.13.1" 178 | }, 179 | "symfony/polyfill-mbstring": { 180 | "version": "v1.13.1" 181 | }, 182 | "symfony/polyfill-php72": { 183 | "version": "v1.13.1" 184 | }, 185 | "symfony/polyfill-php73": { 186 | "version": "v1.13.1" 187 | }, 188 | "symfony/property-access": { 189 | "version": "v5.0.3" 190 | }, 191 | "symfony/routing": { 192 | "version": "4.2", 193 | "recipe": { 194 | "repo": "github.com/symfony/recipes", 195 | "branch": "master", 196 | "version": "4.2", 197 | "ref": "683dcb08707ba8d41b7e34adb0344bfd68d248a7" 198 | }, 199 | "files": [ 200 | "config/packages/prod/routing.yaml", 201 | "config/packages/routing.yaml", 202 | "config/routes.yaml" 203 | ] 204 | }, 205 | "symfony/security-bundle": { 206 | "version": "4.4", 207 | "recipe": { 208 | "repo": "github.com/symfony/recipes", 209 | "branch": "master", 210 | "version": "4.4", 211 | "ref": "30efd98dd3b4ead6e9ad4713b1efc43bbe94bf77" 212 | }, 213 | "files": [ 214 | "config/packages/security.yaml" 215 | ] 216 | }, 217 | "symfony/security-core": { 218 | "version": "v4.4.3" 219 | }, 220 | "symfony/security-csrf": { 221 | "version": "v5.0.3" 222 | }, 223 | "symfony/security-guard": { 224 | "version": "v4.4.3" 225 | }, 226 | "symfony/security-http": { 227 | "version": "v4.4.3" 228 | }, 229 | "symfony/service-contracts": { 230 | "version": "v2.0.1" 231 | }, 232 | "symfony/translation": { 233 | "version": "3.3", 234 | "recipe": { 235 | "repo": "github.com/symfony/recipes", 236 | "branch": "master", 237 | "version": "3.3", 238 | "ref": "2ad9d2545bce8ca1a863e50e92141f0b9d87ffcd" 239 | }, 240 | "files": [ 241 | "config/packages/translation.yaml", 242 | "translations/.gitignore" 243 | ] 244 | }, 245 | "symfony/translation-contracts": { 246 | "version": "v2.0.1" 247 | }, 248 | "symfony/twig-bridge": { 249 | "version": "v4.4.3" 250 | }, 251 | "symfony/twig-bundle": { 252 | "version": "4.4", 253 | "recipe": { 254 | "repo": "github.com/symfony/recipes", 255 | "branch": "master", 256 | "version": "4.4", 257 | "ref": "15a41bbd66a1323d09824a189b485c126bbefa51" 258 | }, 259 | "files": [ 260 | "config/packages/test/twig.yaml", 261 | "config/packages/twig.yaml", 262 | "templates/base.html.twig" 263 | ] 264 | }, 265 | "symfony/validator": { 266 | "version": "4.3", 267 | "recipe": { 268 | "repo": "github.com/symfony/recipes", 269 | "branch": "master", 270 | "version": "4.3", 271 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 272 | }, 273 | "files": [ 274 | "config/packages/test/validator.yaml", 275 | "config/packages/validator.yaml" 276 | ] 277 | }, 278 | "symfony/var-dumper": { 279 | "version": "v5.0.3" 280 | }, 281 | "symfony/var-exporter": { 282 | "version": "v5.0.3" 283 | }, 284 | "symfony/webpack-encore-bundle": { 285 | "version": "1.6", 286 | "recipe": { 287 | "repo": "github.com/symfony/recipes", 288 | "branch": "master", 289 | "version": "1.6", 290 | "ref": "69e1d805ad95964088bd510c05995e87dc391564" 291 | }, 292 | "files": [ 293 | "assets/css/app.css", 294 | "assets/js/app.js", 295 | "config/packages/assets.yaml", 296 | "config/packages/prod/webpack_encore.yaml", 297 | "config/packages/test/webpack_encore.yaml", 298 | "config/packages/webpack_encore.yaml", 299 | "package.json", 300 | "webpack.config.js" 301 | ] 302 | }, 303 | "symfony/yaml": { 304 | "version": "v5.0.3" 305 | }, 306 | "tgalopin/html-sanitizer": { 307 | "version": "1.2.0" 308 | }, 309 | "tgalopin/html-sanitizer-bundle": { 310 | "version": "1.0", 311 | "recipe": { 312 | "repo": "github.com/symfony/recipes-contrib", 313 | "branch": "master", 314 | "version": "1.0", 315 | "ref": "26a72f38eede2c53b5d3ccbed5c150e10a93268d" 316 | }, 317 | "files": [ 318 | "config/packages/html_sanitizer.yaml" 319 | ] 320 | }, 321 | "twig/extensions": { 322 | "version": "1.0", 323 | "recipe": { 324 | "repo": "github.com/symfony/recipes", 325 | "branch": "master", 326 | "version": "1.0", 327 | "ref": "a86723ee8d8b2f9437c8ce60a5546a1c267da5ed" 328 | }, 329 | "files": [ 330 | "config/packages/twig_extensions.yaml" 331 | ] 332 | }, 333 | "twig/twig": { 334 | "version": "v2.12.3" 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /Tests/app/templates/base.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | This is the base template used as the application layout which contains the 3 | common elements and decorates all the other templates. 4 | See https://symfony.com/doc/current/book/templating.html#template-inheritance-and-layouts 5 | #} 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}Symfony Demo application{% endblock %} 12 | 13 | 14 | 15 | 16 | 17 |
18 | {% block body %} 19 |
20 |
21 | {% block main %}{% endblock %} 22 |
23 |
24 | {% endblock %} 25 |
26 | 27 | {% block javascripts %} 28 | {{ encore_entry_script_tags('app') }} 29 | {{ encore_entry_script_tags('FpJsFormElement') }} 30 | 31 | {{ js_validator_config() }} 32 | {{ init_js_validation() }} 33 | 34 | {% endblock %} 35 | 36 | 37 | -------------------------------------------------------------------------------- /Tests/app/templates/bundles/TwigBundle/Exception/error.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | This template is used to render any error different from 403, 404 and 500. 3 | 4 | This is the simplest way to customize error pages in Symfony applications. 5 | In case you need it, you can also hook into the internal exception handling 6 | made by Symfony. This allows you to perform advanced tasks and even recover 7 | your application from some errors. 8 | See https://symfony.com/doc/current/cookbook/controller/error_pages.html 9 | #} 10 | 11 | {% extends 'base.html.twig' %} 12 | 13 | {% block body_id 'error' %} 14 | 15 | {% block main %} 16 |

{{ 'http_error.name'|trans({ '%status_code%': status_code }) }}

17 | 18 |

19 | {{ 'http_error.description'|trans({ '%status_code%': status_code }) }} 20 |

21 |

22 | {{ 'http_error.suggestion'|trans({ '%url%': path('blog_index') })|raw }} 23 |

24 | {% endblock %} 25 | 26 | {% block sidebar %} 27 | {{ parent() }} 28 | 29 | {{ show_source_code(_self) }} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /Tests/app/templates/bundles/TwigBundle/Exception/error403.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | This template is used to render errors of type HTTP 403 (Forbidden) 3 | 4 | This is the simplest way to customize error pages in Symfony applications. 5 | In case you need it, you can also hook into the internal exception handling 6 | made by Symfony. This allows you to perform advanced tasks and even recover 7 | your application from some errors. 8 | See https://symfony.com/doc/current/cookbook/controller/error_pages.html 9 | #} 10 | 11 | {% extends 'base.html.twig' %} 12 | 13 | {% block body_id 'error' %} 14 | 15 | {% block main %} 16 |

{{ 'http_error.name'|trans({ '%status_code%': 403 }) }}

17 | 18 |

19 | {{ 'http_error_403.description'|trans }} 20 |

21 |

22 | {{ 'http_error_403.suggestion'|trans }} 23 |

24 | {% endblock %} 25 | 26 | {% block sidebar %} 27 | {{ parent() }} 28 | 29 | {{ show_source_code(_self) }} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /Tests/app/templates/bundles/TwigBundle/Exception/error404.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | This template is used to render errors of type HTTP 404 (Not Found) 3 | 4 | This is the simplest way to customize error pages in Symfony applications. 5 | In case you need it, you can also hook into the internal exception handling 6 | made by Symfony. This allows you to perform advanced tasks and even recover 7 | your application from some errors. 8 | See https://symfony.com/doc/current/cookbook/controller/error_pages.html 9 | #} 10 | 11 | {% extends 'base.html.twig' %} 12 | 13 | {% block body_id 'error' %} 14 | 15 | {% block main %} 16 |

{{ 'http_error.name'|trans({ '%status_code%': 404 }) }}

17 | 18 |

19 | {{ 'http_error_404.description'|trans }} 20 |

21 |

22 | {{ 'http_error_404.suggestion'|trans({ '%url%': path('blog_index') })|raw }} 23 |

24 | {% endblock %} 25 | 26 | {% block sidebar %} 27 | {{ parent() }} 28 | 29 | {{ show_source_code(_self) }} 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /Tests/app/templates/bundles/TwigBundle/Exception/error500.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | This template is used to render errors of type HTTP 500 (Internal Server Error) 3 | 4 | This is the simplest way to customize error pages in Symfony applications. 5 | In case you need it, you can also hook into the internal exception handling 6 | made by Symfony. This allows you to perform advanced tasks and even recover 7 | your application from some errors. 8 | See https://symfony.com/doc/current/cookbook/controller/error_pages.html 9 | #} 10 | 11 | {% extends 'base.html.twig' %} 12 | 13 | {% block stylesheets %} 14 | {{ parent() }} 15 | 16 | {% endblock %} 17 | 18 | {% block body_id 'error' %} 19 | 20 | {% block main %} 21 |

{{ 'http_error.name'|trans({ '%status_code%': 500 }) }}

22 | 23 |

24 | {{ 'http_error_500.description'|trans }} 25 |

26 |

27 | {{ 'http_error_500.suggestion'|trans({ '%url%': path('blog_index') })|raw }} 28 |

29 | {% endblock %} 30 | 31 | {% block sidebar %} 32 | {{ parent() }} 33 | 34 | {{ show_source_code(_self) }} 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /Tests/app/templates/default/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block main %} 4 |

Test form

5 | 6 | {{ form(testForm) }} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /Tests/app/templates/form/fields.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | Each field type is rendered by a template fragment, which is determined 3 | by the name of your form type class (DateTimePickerType -> date_time_picker) 4 | and the suffix "_widget". This can be controlled by overriding getBlockPrefix() 5 | in DateTimePickerType. 6 | 7 | See https://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-a-template-for-the-field 8 | #} 9 | 10 | {% block date_time_picker_widget %} 11 |
12 | {{ block('datetime_widget') }} 13 | 14 | 15 | 16 |
17 | {% endblock %} 18 | 19 | {% block tags_input_widget %} 20 |
21 | {{ form_widget(form, {'attr': {'data-toggle': 'tagsinput', 'data-tags': tags|json_encode}}) }} 22 | 23 | 24 | 25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /Tests/app/templates/form/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap_3_layout.html.twig' %} 2 | 3 | {# Errors #} 4 | 5 | {% block form_errors -%} 6 | {% if errors|length > 0 -%} 7 | {% if form is not rootform %}{% else %}
{% endif %} 8 |
    9 | {%- for error in errors -%} 10 | {# use font-awesome icon library #} 11 |
  • {{ error.message }}
  • 12 | {%- endfor -%} 13 |
14 | {% if form is not rootform %}{% else %}
{% endif %} 15 | {%- endif %} 16 | {%- endblock form_errors %} 17 | -------------------------------------------------------------------------------- /Tests/app/translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formapro/JsFormValidatorBundle/1683bb6317a267b125f75fb7a84266ab4034ac99/Tests/app/translations/.gitignore -------------------------------------------------------------------------------- /Tests/app/webpack.config.js: -------------------------------------------------------------------------------- 1 | // This project uses "Yarn" package manager for managing JavaScript dependencies along 2 | // with "Webpack Encore" library that helps working with the CSS and JavaScript files 3 | // that are stored in the "assets/" directory. 4 | // 5 | // Read https://symfony.com/doc/current/frontend.html to learn more about how 6 | // to manage CSS and JavaScript files in Symfony applications. 7 | var Encore = require('@symfony/webpack-encore'); 8 | 9 | Encore 10 | .setOutputPath('public/build/') 11 | .setPublicPath('/build') 12 | .cleanupOutputBeforeBuild() 13 | .autoProvidejQuery() 14 | // when versioning is enabled, each filename will include a hash that changes 15 | // whenever the contents of that file change. This allows you to use aggressive 16 | // caching strategies. Use Encore.isProduction() to enable it only for production. 17 | .enableVersioning(false) 18 | .addEntry('app', './assets/js/app.js') 19 | .addEntry('FpJsFormElement', './bundles/fpjsformvalidator/js/FpJsFormValidator.js') 20 | .splitEntryChunks() 21 | .enableSingleRuntimeChunk() 22 | .enableIntegrityHashes(Encore.isProduction()) 23 | .configureBabel(null, { 24 | useBuiltIns: 'usage', 25 | corejs: 3, 26 | }) 27 | ; 28 | 29 | module.exports = Encore.getWebpackConfig(); 30 | -------------------------------------------------------------------------------- /UPGRADE-1.1.md: -------------------------------------------------------------------------------- 1 | UPGRADE FROM 1.0 to 1.1 2 | ======================= 3 | 4 | You no longer needed to pass native form objects to views and to add any extra functional in views. 5 | 6 | 1. Just pass the formView object as it's recommended in Symfony documentation and remove ```{{ fp_jsfv(form) }}``` from your views. 7 | 2. Remove the ```translation_domain``` key in your ```config.yml``` under ```fp_js_form_validator``` if it is in there. 8 | 9 | 3. See the [documentation](https://github.com/formapro/JsFormValidatorBundle/blob/master/README.md) to learn about new features. 10 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fp/jsformvalidator-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Javascript validation for sf[2|3|4] forms.", 5 | "keywords": ["Symfony", "Form", "Javascript", "Validation"], 6 | "homepage": "https://github.com/formapro/JsFormValidatorBundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Ton Sharp", 11 | "email": "66ton99@gmail.com" 12 | }, 13 | { 14 | "name": "Yury Maltsev", 15 | "email": "dev.yury.maltsev@gmail.com" 16 | } 17 | 18 | ], 19 | 20 | "require": { 21 | "php": ">=5.5.9", 22 | "symfony/form": "^2.7|^3.0|^4.0", 23 | "symfony/validator": "^2.7|^3.0|^4.0" 24 | }, 25 | "require-dev": { 26 | "php": "^7.1.3", 27 | "ext-pdo_sqlite": "*", 28 | "erusev/parsedown": "^1.6", 29 | "sensio/framework-extra-bundle": "^5.1", 30 | "sensiolabs/security-checker": "^5.0", 31 | "symfony/asset": "*", 32 | "symfony/console": "*", 33 | "symfony/dotenv": "*", 34 | "symfony/expression-language": "*", 35 | "symfony/flex": "^1.17", 36 | "symfony/framework-bundle": "*", 37 | "symfony/intl": "*", 38 | "symfony/security-bundle": "*", 39 | "symfony/translation": "*", 40 | "symfony/twig-bundle": "*", 41 | "symfony/webpack-encore-bundle": "^1.4", 42 | "symfony/yaml": "*", 43 | "tgalopin/html-sanitizer-bundle": "^1.1", 44 | "twig/extensions": "^1.5", 45 | "twig/twig": "^2.6" 46 | }, 47 | 48 | "suggest": { 49 | "symfony/symfony": "For using as a Symfony Bundle and for using a default ajax validation" 50 | }, 51 | 52 | "autoload": { 53 | "psr-4": { 54 | "Fp\\JsFormValidatorBundle\\": "src" 55 | } 56 | }, 57 | "autoload-dev": { 58 | "psr-4": { 59 | "App\\": "Tests/app/src" 60 | } 61 | }, 62 | 63 | "extra": { 64 | "branch-alias": { 65 | "dev-master": "1.5-dev" 66 | }, 67 | "symfony": { 68 | "allow-contrib": true, 69 | "require": "4.0.*" 70 | } 71 | }, 72 | "scripts": { 73 | "auto-scripts": { 74 | "cache:clear": "symfony-cmd", 75 | "assets:install %PUBLIC_DIR%": "symfony-cmd", 76 | "security-checker security:check": "script" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cypress/integration/form_spec.js: -------------------------------------------------------------------------------- 1 | const submitForm = () => { 2 | cy.get('#test_form_save').click(); 3 | }; 4 | 5 | const getParent = (id) => cy.get('#' + id).parent().find('.form-errors'); 6 | 7 | const getErrors = (id) => getParent(id).children(); 8 | 9 | 10 | 11 | context('JsFormValidatorBundle', () => { 12 | beforeEach(() => { 13 | cy.visit('http://webserver/') 14 | }); 15 | 16 | describe('test js validation', () => { 17 | it('test not blank', () => { 18 | const fieldId = 'test_form_notBlank'; 19 | submitForm(); 20 | cy.get('.form-error-test-form-notBlank').contains('Please fill field'); 21 | getErrors(fieldId).should('have.length', 1); 22 | 23 | cy.get('#test_form_notBlank').type('abc').should('have.value', 'abc'); 24 | submitForm(); 25 | getErrors(fieldId).should('have.length', 0); 26 | }); 27 | 28 | it('test blank', () => { 29 | const fieldId = 'test_form_blank'; 30 | submitForm(); 31 | getParent(fieldId).should('have.length', 0); 32 | 33 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 34 | submitForm(); 35 | cy.get('.form-error-test-form-blank').contains('Please do not fill field'); 36 | getErrors(fieldId).should('have.length', 1); 37 | }); 38 | 39 | it('test choice', () => { 40 | const fieldId = 'test_form_choice'; 41 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 42 | submitForm(); 43 | getErrors(fieldId).should('have.length', 1); 44 | cy.get('.form-error-test-form-choice').contains('Please fill field correct value (1,2,3)'); 45 | 46 | cy.get('#' + fieldId).clear().type('1').should('have.value', '1'); 47 | submitForm(); 48 | getErrors(fieldId).should('have.length', 0); 49 | }); 50 | 51 | it('test date', () => { 52 | const fieldId = 'test_form_date'; 53 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 54 | submitForm(); 55 | getErrors(fieldId).should('have.length', 1); 56 | cy.get('.form-error-test-form-date').contains('Please fill valid date'); 57 | 58 | cy.get('#' + fieldId).clear().type('2020-01-01').should('have.value', '2020-01-01'); 59 | submitForm(); 60 | getErrors(fieldId).should('have.length', 0); 61 | }); 62 | 63 | it('test email', () => { 64 | const fieldId = 'test_form_email'; 65 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 66 | submitForm(); 67 | getErrors(fieldId).should('have.length', 1); 68 | cy.get('.form-error-test-form-email').contains('Please fill valid email'); 69 | 70 | cy.get('#' + fieldId).clear().type('js@validator.org').should('have.value', 'js@validator.org'); 71 | submitForm(); 72 | getErrors(fieldId).should('have.length', 0); 73 | }); 74 | 75 | it('test equal to', () => { 76 | const fieldId = 'test_form_equalTo'; 77 | cy.get('#' + fieldId).type('a').should('have.value', 'a'); 78 | submitForm(); 79 | getErrors(fieldId).should('have.length', 1); 80 | cy.get('.form-error-test-form-equalTo').contains('Please fill correct value (20)'); 81 | 82 | cy.get('#' + fieldId).clear().type('abc').should('have.value', 'abc'); 83 | submitForm(); 84 | getErrors(fieldId).should('have.length', 0); 85 | }); 86 | 87 | it('test greater than', () => { 88 | const fieldId = 'test_form_greaterThan'; 89 | cy.get('#' + fieldId).type('1').should('have.value', '1'); 90 | submitForm(); 91 | getErrors(fieldId).should('have.length', 1); 92 | cy.get('.form-error-test-form-greaterThan').contains('Please fill greater than 20 value'); 93 | 94 | cy.get('#' + fieldId).clear().type('25').should('have.value', '25'); 95 | submitForm(); 96 | getErrors(fieldId).should('have.length', 0); 97 | }); 98 | 99 | it('test greater than or equal', () => { 100 | const fieldId = 'test_form_greaterThanOrEqual'; 101 | cy.get('#' + fieldId).type('1').should('have.value', '1'); 102 | submitForm(); 103 | getErrors(fieldId).should('have.length', 1); 104 | cy.get('.form-error-test-form-greaterThanOrEqual').contains('Please fill greater than or equal 20 value'); 105 | 106 | cy.get('#' + fieldId).clear().type('20').should('have.value', '20'); 107 | submitForm(); 108 | getErrors(fieldId).should('have.length', 0); 109 | }); 110 | 111 | it('test ip', () => { 112 | const fieldId = 'test_form_ip'; 113 | cy.get('#' + fieldId).type('127.').should('have.value', '127.'); 114 | submitForm(); 115 | getErrors(fieldId).should('have.length', 1); 116 | cy.get('.form-error-test-form-ip').contains('Please fill valid IP'); 117 | 118 | cy.get('#' + fieldId).clear().type('127.0.0.1').should('have.value', '127.0.0.1'); 119 | submitForm(); 120 | getErrors(fieldId).should('have.length', 0); 121 | }); 122 | 123 | it('test is false', () => { 124 | const fieldId = 'test_form_isFalse'; 125 | cy.get('#' + fieldId).check().should('be.checked'); 126 | submitForm(); 127 | getErrors(fieldId).should('have.length', 1); 128 | cy.get('.form-error-test-form-isFalse').contains('Please choice false'); 129 | 130 | cy.get('#' + fieldId).uncheck().should('not.be.checked'); 131 | submitForm(); 132 | getErrors(fieldId).should('have.length', 0); 133 | }); 134 | 135 | it('test is true', () => { 136 | const fieldId = 'test_form_isTrue'; 137 | cy.get('#' + fieldId).uncheck().should('not.be.checked'); 138 | submitForm(); 139 | getErrors(fieldId).should('have.length', 1); 140 | cy.get('.form-error-test-form-isTrue').contains('Please choice true'); 141 | 142 | cy.get('#' + fieldId).check().should('be.checked'); 143 | submitForm(); 144 | getErrors(fieldId).should('have.length', 0); 145 | }); 146 | 147 | it('test less than', () => { 148 | const fieldId = 'test_form_lessThan'; 149 | cy.get('#' + fieldId).type('35').should('have.value', '35'); 150 | submitForm(); 151 | getErrors(fieldId).should('have.length', 1); 152 | cy.get('.form-error-test-form-lessThan').contains('Please fill least than 20 value'); 153 | 154 | cy.get('#' + fieldId).clear().type('15').should('have.value', '15'); 155 | submitForm(); 156 | getErrors(fieldId).should('have.length', 0); 157 | }); 158 | 159 | it('test less than or equal', () => { 160 | const fieldId = 'test_form_lessThanOrEqual'; 161 | cy.get('#' + fieldId).type('35').should('have.value', '35'); 162 | submitForm(); 163 | getErrors(fieldId).should('have.length', 1); 164 | cy.get('.form-error-test-form-lessThanOrEqual').contains('Please fill least than or equal 20 value'); 165 | 166 | cy.get('#' + fieldId).clear().type('20').should('have.value', '20'); 167 | submitForm(); 168 | getErrors(fieldId).should('have.length', 0); 169 | }); 170 | 171 | it('test not equal to', () => { 172 | const fieldId = 'test_form_notEqualTo'; 173 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 174 | submitForm(); 175 | getErrors(fieldId).should('have.length', 1); 176 | cy.get('.form-error-test-form-notEqualTo').contains('Please fill correct value (not abc)'); 177 | 178 | cy.get('#' + fieldId).clear().type('abcd').should('have.value', 'abcd'); 179 | submitForm(); 180 | getErrors(fieldId).should('have.length', 0); 181 | }); 182 | 183 | it('test range', () => { 184 | const fieldId = 'test_form_range'; 185 | cy.get('#' + fieldId).type('100').should('have.value', '100'); 186 | submitForm(); 187 | getErrors(fieldId).should('have.length', 1); 188 | cy.get('.form-error-test-form-range').contains('You must be at least 120'); 189 | 190 | cy.get('#' + fieldId).clear().type('200').should('have.value', '200'); 191 | submitForm(); 192 | getErrors(fieldId).should('have.length', 1); 193 | cy.get('.form-error-test-form-range').contains('You cannot be taller than 180'); 194 | 195 | cy.get('#' + fieldId).clear().type('150').should('have.value', '150'); 196 | submitForm(); 197 | getErrors(fieldId).should('have.length', 0); 198 | }); 199 | 200 | it('test url', () => { 201 | const fieldId = 'test_form_url'; 202 | cy.get('#' + fieldId).type('abc').should('have.value', 'abc'); 203 | submitForm(); 204 | getErrors(fieldId).should('have.length', 1); 205 | cy.get('.form-error-test-form-url').contains('Please fill valid url'); 206 | 207 | cy.get('#' + fieldId).clear().type('https://symfony.com').should('have.value', 'https://symfony.com'); 208 | submitForm(); 209 | getErrors(fieldId).should('have.length', 0); 210 | }); 211 | 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | services: 3 | 4 | webserver: 5 | image: nginx:alpine 6 | working_dir: /application 7 | volumes: 8 | - .:/application 9 | - ./phpdocker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf 10 | ports: 11 | - "8000:80" 12 | 13 | php-fpm: 14 | build: phpdocker/php-fpm 15 | working_dir: /application 16 | volumes: 17 | - .:/application 18 | - ./phpdocker/php-fpm/php-ini-overrides.ini:/etc/php/7.2/fpm/conf.d/99-overrides.ini 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsformvalidatorbundle", 3 | "version": "1.0.0", 4 | "description": "[![Build Status](https://travis-ci.org/formapro/JsFormValidatorBundle.svg?branch=master)](https://travis-ci.org/formapro/JsFormValidatorBundle) [![Total Downloads](https://poser.pugx.org/fp/jsformvalidator-bundle/downloads.png)](https://packagist.org/packages/fp/jsformvalidator-bundle)", 5 | "scripts": { 6 | "test": "npm run test:unit && npm run test:e2e", 7 | "test:unit": "jest", 8 | "test:e2e": "cypress run" 9 | }, 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@babel/core": "^7.8.3", 13 | "@babel/preset-env": "^7.8.3", 14 | "babel-jest": "^25.1.0", 15 | "cypress": "^3.8.3", 16 | "jest": "^25.1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpdocker/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | PHPDocker.io Readme 4 | 5 | 6 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

PHPDocker.io generated environment

21 | 22 |

Add to your project

23 | 24 |

Simply, unzip the file into your project, this will create docker-compose.yml on the root of your project and a folder named phpdocker containing nginx and php-fpm config for it.

25 | 26 |

Ensure the webserver config on docker\nginx.conf is correct for your project. PHPDocker.io will have customised this file according to the application type you chose on the generator, for instance web/app|app_dev.php on a Symfony project, or public/index.php on generic apps.

27 | 28 |

Note: you may place the files elsewhere in your project. Make sure you modify the locations for the php-fpm dockerfile, the php.ini overrides and nginx config on docker-compose.yml if you do so.

29 | 30 |

How to run

31 | 32 |

Dependencies:

33 | 34 | 38 | 39 |

Once you're done, simply cd to your project and run docker-compose up -d. This will initialise and start all the containers, then leave them running in the background.

40 | 41 |

Services exposed outside your environment

42 | 43 |

You can access your application via localhost, if you're running the containers directly, or through `` when run on a vm. nginx and mailhog both respond to any hostname, in case you want to add your own hostname on your /etc/hosts

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
ServiceAddress outside containers
Webserverlocalhost:8000
MySQLhost: localhost; port: 8002
63 | 64 |

Hosts within your environment

65 | 66 |

You'll need to configure your application to use any services you enabled:

67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
ServiceHostnamePort number
php-fpmphp-fpm9000
MySQLmysql3306 (default)
89 | 90 |

Docker compose cheatsheet

91 | 92 |

Note: you need to cd first to where your docker-compose.yml file lives.

93 | 94 |
    95 |
  • Start containers in the background: docker-compose up -d
  • 96 |
  • Start containers on the foreground: docker-compose up. You will see a stream of logs for every container running.
  • 97 |
  • Stop containers: docker-compose stop
  • 98 |
  • Kill containers: docker-compose kill
  • 99 |
  • View container logs: docker-compose logs
  • 100 |
  • Execute command inside of container: docker-compose exec SERVICE_NAME COMMAND where COMMAND is whatever you want to run. Examples: 101 | * Shell into the PHP container, docker-compose exec php-fpm bash 102 | * Run symfony console, docker-compose exec php-fpm bin/console 103 | * Open a mysql shell, docker-compose exec mysql mysql -uroot -pCHOSEN_ROOT_PASSWORD
  • 104 |
105 | 106 |

Recommendations

107 | 108 |

It's hard to avoid file permission issues when fiddling about with containers due to the fact that, from your OS point of view, any files created within the container are owned by the process that runs the docker engine (this is usually root). Different OS will also have different problems, for instance you can run stuff in containers using docker exec -it -u $(id -u):$(id -g) CONTAINER_NAME COMMAND to force your current user ID into the process, but this will only work if your host OS is Linux, not mac. Follow a couple of simple rules and save yourself a world of hurt.

109 | 110 |
    111 |
  • Run composer outside of the php container, as doing so would install all your dependencies owned by root within your vendor folder.
  • 112 |
  • Run commands (ie Symfony's console, or Laravel's artisan) straight inside of your container. You can easily open a shell as described above and do your thing from there.
  • 113 |
114 |
115 |
116 |
117 | 118 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /phpdocker/README.md: -------------------------------------------------------------------------------- 1 | PHPDocker.io generated environment 2 | ================================== 3 | 4 | # Add to your project# 5 | 6 | Simply, unzip the file into your project, this will create `docker-compose.yml` on the root of your project and a folder named `phpdocker` containing nginx and php-fpm config for it. 7 | 8 | Ensure the webserver config on `docker\nginx.conf` is correct for your project. PHPDocker.io will have customised this file according to the application type you chose on the generator, for instance `web/app|app_dev.php` on a Symfony project, or `public/index.php` on generic apps. 9 | 10 | Note: you may place the files elsewhere in your project. Make sure you modify the locations for the php-fpm dockerfile, the php.ini overrides and nginx config on `docker-compose.yml` if you do so. 11 | 12 | # How to run 13 | 14 | Dependencies: 15 | 16 | * Docker engine v1.13 or higher. Your OS provided package might be a little old, if you encounter problems, do upgrade. See [https://docs.docker.com/engine/installation](https://docs.docker.com/engine/installation) 17 | * Docker compose v1.12 or higher. See [docs.docker.com/compose/install](https://docs.docker.com/compose/install/) 18 | 19 | Once you're done, simply `cd` to your project and run `docker-compose up -d`. This will initialise and start all the containers, then leave them running in the background. 20 | 21 | ## Services exposed outside your environment 22 | 23 | You can access your application via **`localhost`**, if you're running the containers directly, or through **``** when run on a vm. nginx and mailhog both respond to any hostname, in case you want to add your own hostname on your `/etc/hosts` 24 | 25 | Service | Address outside containers 26 | ------ | --------- 27 | Webserver | [localhost:8000](http://localhost:8000) 28 | MySQL | **host:** `localhost`; **port:** `8002` 29 | 30 | ## Hosts within your environment 31 | 32 | You'll need to configure your application to use any services you enabled: 33 | 34 | Service|Hostname|Port number 35 | ------|---------|----------- 36 | php-fpm|php-fpm|9000 37 | MySQL|mysql|3306 (default) 38 | 39 | # Docker compose cheatsheet# 40 | 41 | **Note:** you need to cd first to where your docker-compose.yml file lives. 42 | 43 | * Start containers in the background: `docker-compose up -d` 44 | * Start containers on the foreground: `docker-compose up`. You will see a stream of logs for every container running. 45 | * Stop containers: `docker-compose stop` 46 | * Kill containers: `docker-compose kill` 47 | * View container logs: `docker-compose logs` 48 | * Execute command inside of container: `docker-compose exec SERVICE_NAME COMMAND` where `COMMAND` is whatever you want to run. Examples: 49 | * Shell into the PHP container, `docker-compose exec php-fpm bash` 50 | * Run symfony console, `docker-compose exec php-fpm bin/console` 51 | * Open a mysql shell, `docker-compose exec mysql mysql -uroot -pCHOSEN_ROOT_PASSWORD` 52 | 53 | # Recommendations 54 | 55 | It's hard to avoid file permission issues when fiddling about with containers due to the fact that, from your OS point of view, any files created within the container are owned by the process that runs the docker engine (this is usually root). Different OS will also have different problems, for instance you can run stuff in containers using `docker exec -it -u $(id -u):$(id -g) CONTAINER_NAME COMMAND` to force your current user ID into the process, but this will only work if your host OS is Linux, not mac. Follow a couple of simple rules and save yourself a world of hurt. 56 | 57 | * Run composer outside of the php container, as doing so would install all your dependencies owned by `root` within your vendor folder. 58 | * Run commands (ie Symfony's console, or Laravel's artisan) straight inside of your container. You can easily open a shell as described above and do your thing from there. 59 | -------------------------------------------------------------------------------- /phpdocker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default; 3 | 4 | client_max_body_size 108M; 5 | 6 | access_log /var/log/nginx/application.access.log; 7 | 8 | 9 | root /application/Tests/app/public; 10 | index index.php; 11 | 12 | if (!-e $request_filename) { 13 | rewrite ^.*$ /index.php last; 14 | } 15 | 16 | location ~ \.php$ { 17 | fastcgi_pass php-fpm:9000; 18 | fastcgi_index index.php; 19 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 20 | fastcgi_param PHP_VALUE "error_log=/var/log/nginx/application_php_errors.log"; 21 | fastcgi_buffers 16 16k; 22 | fastcgi_buffer_size 32k; 23 | include fastcgi_params; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /phpdocker/php-fpm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phpdockerio/php72-fpm:latest 2 | WORKDIR "/application" 3 | 4 | # Install selected extensions and other stuff 5 | RUN apt-get update \ 6 | && apt-get -y --no-install-recommends install php7.2-mysql php7.2-sqlite3 \ 7 | && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* 8 | 9 | # Install git 10 | RUN apt-get update \ 11 | && apt-get -y install git \ 12 | && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* 13 | 14 | # Install NodeJS 15 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash 16 | RUN apt-get update && apt-get install -y nodejs && apt-get clean 17 | 18 | # Cypress dependencies 19 | RUN apt-get -y install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb 20 | -------------------------------------------------------------------------------- /phpdocker/php-fpm/php-ini-overrides.ini: -------------------------------------------------------------------------------- 1 | upload_max_filesize = 100M 2 | post_max_size = 108M 3 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/formapro/JsFormValidatorBundle/1683bb6317a267b125f75fb7a84266ab4034ac99/src/Controller/.gitignore -------------------------------------------------------------------------------- /src/Controller/AjaxController.php: -------------------------------------------------------------------------------- 1 | request->all(); 27 | foreach ($data['data'] as $value) { 28 | // If field(s) has an empty value and it should be ignored 29 | if ((bool) $data['ignoreNull'] && ('' === $value || is_null($value))) { 30 | // Just return a positive result 31 | return new JsonResponse(true); 32 | } 33 | } 34 | 35 | $entity = $this 36 | ->get('doctrine') 37 | ->getRepository($data['entityName']) 38 | ->{$data['repositoryMethod']}($data['data']) 39 | ; 40 | 41 | return new JsonResponse(empty($entity)); 42 | } 43 | } -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | root('fp_js_form_validator'); 24 | } else { 25 | $treeBuilder = new TreeBuilder('fp_js_form_validator'); 26 | $rootNode = $treeBuilder->getRootNode(); 27 | } 28 | 29 | /** @noinspection PhpUndefinedMethodInspection */ 30 | $rootNode 31 | ->children() 32 | ->scalarNode('js_validation') 33 | ->defaultValue(true) 34 | ->end() 35 | ->arrayNode('routing') 36 | ->addDefaultsIfNotSet() 37 | ->children() 38 | ->scalarNode('check_unique_entity') 39 | ->isRequired() 40 | ->cannotBeEmpty() 41 | ->defaultValue('fp_js_form_validator.check_unique_entity') 42 | ->end() 43 | ->end() 44 | ->end() 45 | ->end(); 46 | 47 | return $treeBuilder; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DependencyInjection/FpJsFormValidatorExtension.php: -------------------------------------------------------------------------------- 1 | processConfiguration($configuration, $configs); 26 | 27 | $container->setParameter($this->getAlias() . '.config', $config); 28 | 29 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 30 | $loader->load('services.xml'); 31 | } 32 | 33 | /** 34 | * @codeCoverageIgnore 35 | * @return string 36 | */ 37 | public function getAlias() 38 | { 39 | return 'fp_js_form_validator'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Exception/UndefinedFormException.php: -------------------------------------------------------------------------------- 1 | entityName = $entityName; 24 | 25 | foreach ($base as $prop => $value) { 26 | $this->{$prop} = $value; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Form/Extension/FormExtension.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 29 | } 30 | 31 | /** 32 | * @param FormBuilderInterface $builder 33 | * @param array $options 34 | */ 35 | public function buildForm(FormBuilderInterface $builder, array $options) 36 | { 37 | $builder->addEventSubscriber(new SubscriberToQueue($this->factory)); 38 | } 39 | 40 | /** 41 | * @param OptionsResolver $resolver 42 | */ 43 | public function configureOptions(OptionsResolver $resolver) 44 | { 45 | $resolver->setDefaults(array('js_validation' => true)); 46 | } 47 | 48 | /** 49 | * Returns the name of the type being extended. 50 | * 51 | * @return string The name of the type being extended 52 | */ 53 | public function getExtendedType() 54 | { 55 | return FormType::class; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public static function getExtendedTypes() 62 | { 63 | yield FormType::class; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Form/Subscriber/SubscriberToQueue.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public static function getSubscribedEvents() 35 | { 36 | return array(FormEvents::POST_SET_DATA => array('onFormSetData', -10)); 37 | } 38 | 39 | /** 40 | * @param FormEvent $event 41 | */ 42 | public function onFormSetData(FormEvent $event) 43 | { 44 | /** @var Form $form */ 45 | $form = $event->getForm(); 46 | $globalSwitch = $this->factory->getConfig('js_validation'); 47 | $localSwitch = $form->getConfig()->getOption('js_validation'); 48 | 49 | // Add only parent forms which are not disabled 50 | if ($globalSwitch && $localSwitch) { 51 | $parent = $this->getParent($form); 52 | if (!$this->factory->inQueue($parent)) { 53 | $this->factory->addToQueue($this->getParent($form)); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * @param Form|FormInterface $element 60 | * 61 | * @return \Symfony\Component\Form\Form 62 | */ 63 | protected function getParent($element) 64 | { 65 | if (!$element->getParent()) { 66 | return $element; 67 | } else { 68 | return $this->getParent($element->getParent()); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/FpJsFormValidatorBundle.php: -------------------------------------------------------------------------------- 1 | toArray()); 22 | } 23 | 24 | /** 25 | * @return string 26 | */ 27 | public function __toString() 28 | { 29 | return $this->toJsString(); 30 | } 31 | 32 | /** 33 | * Convert php value to the Javascript formatted string 34 | * 35 | * @param mixed $value 36 | * 37 | * @return string 38 | */ 39 | public static function phpValueToJs($value) 40 | { 41 | // For object which has own __toString method 42 | if ($value instanceof JsModelAbstract) { 43 | return $value->toJsString(); 44 | } 45 | // For object which has own __toString method 46 | elseif (is_object($value) && method_exists($value, '__toString')) { 47 | return self::phpValueToJs($value->__toString()); 48 | } 49 | // For an object or associative array 50 | elseif (is_object($value) || (is_array($value) && array_values($value) !== $value)) { 51 | $jsObject = array(); 52 | foreach ($value as $paramName => $paramValue) { 53 | $paramName = addcslashes($paramName, '\'\\'); 54 | $jsObject[] = "'$paramName':" . self::phpValueToJs($paramValue); 55 | } 56 | 57 | return sprintf('{%1$s}', implode(',', $jsObject)); 58 | } 59 | // For a sequential array 60 | elseif (is_array($value)) { 61 | $jsArray = array(); 62 | foreach ($value as $item) { 63 | $jsArray[] = self::phpValueToJs($item); 64 | } 65 | 66 | return sprintf('[%1$s]', implode(',', $jsArray)); 67 | } 68 | // For string 69 | elseif (is_string($value)) { 70 | $value = addcslashes($value, '\'\\'); 71 | 72 | return "'$value'"; 73 | } 74 | // For boolean 75 | elseif (is_bool($value)) { 76 | return true === $value ? 'true' : 'false'; 77 | } 78 | // For numbers 79 | elseif (is_numeric($value)) { 80 | return $value; 81 | } 82 | // For null 83 | elseif (is_null($value)) { 84 | return 'null'; 85 | } 86 | // Otherwise 87 | else { 88 | return 'undefined'; 89 | } 90 | } 91 | 92 | /** 93 | * @return array 94 | */ 95 | public function toArray() 96 | { 97 | $result = array(); 98 | foreach ($this as $key => $value) { 99 | $result[$key] = $value; 100 | } 101 | 102 | return $result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Resources/config/routing.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FpJsFormValidatorBundle:Ajax:checkUniqueEntity 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/services.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Fp\JsFormValidatorBundle\Twig\Extension\JsFormValidatorTwigExtension 9 | Fp\JsFormValidatorBundle\Factory\JsFormValidatorFactory 10 | Fp\JsFormValidatorBundle\Form\Extension\FormExtension 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | %fp_js_form_validator.config% 29 | %validator.translation_domain% 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Resources/doc/2_1.md: -------------------------------------------------------------------------------- 1 | ### 2.1 Disabling validation 2 | 3 | You can disable the validation in three ways: 4 | 5 | 1) globally 6 | ```yaml 7 | //app/config/config.yml 8 | # ... 9 | fp_js_form_validator: 10 | js_validation: false 11 | ``` 12 | 2) for the specified form: 13 | ```php 14 | namespace Acme\DemoBundle\Form; 15 | 16 | class UserFormType extends AbstractType 17 | { 18 | public function setDefaultOptions(OptionsResolverInterface $resolver) 19 | { 20 | $resolver->setDefaults(array( 21 | 'js_validation' => false 22 | )); 23 | } 24 | } 25 | ``` 26 | 3) for the [specified field](3_1.md) -------------------------------------------------------------------------------- /src/Resources/doc/2_2.md: -------------------------------------------------------------------------------- 1 | ### 2.2 Issue with sub-requests 2 | 3 | All the necessary data for validation forms are initialized in the included template (initializer) that was defined on the step [1.3](../../README.md#p_1_3) 4 | So if your form was rendered in sub-request, e.g.: 5 | ```twig 6 |
7 | {{ render(controller('AcmeDemoBundle:Default:sendEmail')) }} 8 |
9 | ``` 10 | in this way the initializer does not know anything about that form. 11 | To fix it, you have to add the initialization to your sub-template manually: 12 | ```twig 13 | {# AcmeDemoBundle:Default:sendEmail.html.twig #} 14 | 15 | {{ init_js_validation() }} 16 | 17 | {{ form(form) }} 18 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/2_3.md: -------------------------------------------------------------------------------- 1 | ### 2.3 Custom initialization 2 | 3 | So, you have added this part into your template 4 | ```twig 5 | 6 | 7 | {{ include('FpJsFormValidatorBundle::javascripts.html.twig') }} 8 | 9 | 10 | 11 | 12 | 13 | ``` 14 | 15 | Let's look at what's inside: 16 | ```twig 17 | 18 | {{ js_validator_config() }} 19 | {{ init_js_validation() }} 20 | ``` 21 | 22 | - the first line loads JS library 23 | - the second one sends some parameters to this library 24 | - the third line - this function processes all the forms which have been added to a queue before, and creates for each one its own JS validation model. So that if you have two or more forms on a page - all of them are processed by this function. 25 | 26 | And if you want customize the initialization, you should leave the first and second lines at the header 27 | ```twig 28 | 29 | 30 | 31 | {{ js_validator_config() }} 32 | 33 | 34 | 35 | 36 | 37 | ``` 38 | and use the third line right at those places where you need it. 39 | 40 | This function has three parameters: 41 | ```js 42 | init_js_validation(form = null, onLoad = true, wrapped = true) 43 | ``` 44 | 45 | - ```form``` By default this function processes all the forms which are added to the current queue. But if you want process only one specified form - put here a form name or a FormView instance of the necessary form. 46 | For example, you have ```comment``` and ```user``` forms on a page: 47 | ```twig 48 | {{ form(comment) }} 49 | init_js_validation(comment) 50 | ... 51 | ... 52 | {{ form(user) }} 53 | init_js_validation(user) 54 | ``` 55 | 56 | - ```onLoad``` By default JS validation models are initialized on DocumentReady event, but if you need to init it later, on some other event, then you have to disable default behavior: 57 | ```twig 58 | {{ form(form) }} 59 | init_js_validation(null, false) 60 | ``` 61 | - ```wrapper``` this parameter just defines if the result should be wrapped with `````` tag 62 | 63 | For example, you need to initialize JS validation only after an ajax request: 64 | ```twig 65 | {{ form(user) }} 66 | 75 | ``` 76 | - ```null``` means that all the available forms will be initialized 77 | - first ```false``` means that initialization will be performed right at the moment when the 'complete' function is called 78 | - second ```false``` means that JS code will not be wrapped with the 'script' tag -------------------------------------------------------------------------------- /src/Resources/doc/3_0.md: -------------------------------------------------------------------------------- 1 | ### Rendering of nested forms 2 | 3 | If you render difficult nested forms without the general [form()](http://symfony.com/doc/current/book/forms.html#rendering-the-form) function, but using [a precision customization](http://symfony.com/doc/current/book/forms.html#rendering-each-field-by-hand), in this case make sure that you have DOM elements for all the necessary form elements. 4 | For example, you have a form like in [this entry](http://symfony.com/doc/current/cookbook/form/form_collections.html) with the next structure: 5 | ```yaml 6 | task: 7 | ... 8 | tags: 9 | collection: 10 | tag_1: 11 | name 12 | tag_2: 13 | name 14 | ... 15 | ``` 16 | 17 | If you render this form without any customization, just using the default ```form()``` function, in this case you will receive a structure like this: 18 | ```html 19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 | ... 45 | 46 | ``` 47 | As you see, all the tags are placed in the id=form_task_tags holder. 48 | This is important, because this element is an implementation of the direct form child ```tags```, and it contains such importent data as prototype for each tag, also it can contains some validations rules which you have sent for the ```tags``` field in the ```Task``` entity. 49 | 50 | For this reason, if you render this form manually, and do not create the ```#form_task_tags``` field: 51 | ```twig 52 | {{ form_start(form) }} 53 | {{ form_row(form.description) }} 54 | 55 |

Tags

56 |
    57 | {% for tag in form.tags %} 58 |
  • {{ form_row(tag.name) }}
  • 59 | {% endfor %} 60 |
61 | {{ form_end(form) }} 62 | ``` 63 | in this case you can lose important validation data. 64 | 65 | So we suggest to add the ids manually: 66 | ```twig 67 | {{ form_start(form) }} 68 | {{ form_row(form.description) }} 69 | 70 |

Tags

71 |
    72 | {% for tag in form.tags %} 73 |
  • 74 | {{ form_row(tag.title) }} 75 |
  • 76 | {% endfor %} 77 |
78 | {{ form_end(form) }} 79 | ``` 80 | -------------------------------------------------------------------------------- /src/Resources/doc/3_1.md: -------------------------------------------------------------------------------- 1 | ### 3.1 Disable validation for a specified field 2 | 3 | jQuery plugin: 4 | ```js 5 | $('#user_email').jsFormValidator({ 6 | disabled: true 7 | }); 8 | ``` 9 | 10 | Pure Javascript: 11 | ```js 12 | var field = document.getElementById('user_email'); 13 | FpJsFormValidator.customize(field, { 14 | disabled: true 15 | }); 16 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_10.md: -------------------------------------------------------------------------------- 1 | ### 3.10 Form submit by Javascript 2 | 3 | If you want to submit your form by click on link or by another Javascript action: 4 | ```js 5 | $('a#link_submit').click(function(event){ 6 | $('form#user').jsFormValidator('submitForm', event); 7 | }); 8 | ``` 9 | 10 | Pure Javascript: 11 | ```js 12 | var link = document.getElementById('link_submit'); 13 | link.addEventListener('click', function (event) { 14 | var form = document.getElementById('user'); 15 | FpJsFormValidator.customize(form, 'submitForm', event); 16 | }); 17 | ``` 18 | -------------------------------------------------------------------------------- /src/Resources/doc/3_11.md: -------------------------------------------------------------------------------- 1 | ### 3.11 onValidate callback 2 | 3 | To add an extra action that will be run after the validation: 4 | ```js 5 | $('form#user').jsFormValidator({ 6 | onValidate: function(errors, event) { 7 | // event - a form submittion event 8 | // errors - an object that looks like: 9 | /* 10 | errors = { 11 | element_1_id: { 12 | source_1_id: ['error_1', 'error_2', 'error_3'], 13 | source_2_id: ['error_4', 'error_5'] 14 | }, 15 | element_2_id: { 16 | ... 17 | }, 18 | ... 19 | } 20 | */ 21 | 22 | // Here your code 23 | } 24 | }); 25 | ``` 26 | 27 | Pure Javascript: 28 | ```js 29 | var field = document.getElementById('user'); 30 | FpJsFormValidator.customize(field, { 31 | onValidate: function(errors, event) { 32 | ... 33 | } 34 | }); 35 | ``` 36 | 37 | **NB:** this option should be defined for the parent form element -------------------------------------------------------------------------------- /src/Resources/doc/3_12.md: -------------------------------------------------------------------------------- 1 | ### 3.12 Run validation on custom event 2 | 3 | This is a real example, how to validate text fields on their change, and add error-markers instead of showing errors: 4 | ```css 5 | input[type=text].error, textarea.error { 6 | border: 1px solid red; 7 | } 8 | input[type=text].ready, textarea.ready { 9 | border: 1px solid green; 10 | } 11 | ``` 12 | ```js 13 | $('form') 14 | .find('input[type=text], textarea') 15 | .blur(function(){ 16 | // Run validation for this field 17 | $(this).jsFormValidator('validate') 18 | }) 19 | .focus(function() { 20 | // Reset markers when focus on a field 21 | $(this).removeClass('error'); 22 | $(this).removeClass('ready'); 23 | }) 24 | .jsFormValidator({ 25 | 'showErrors': function(errors) { 26 | if (errors.length) { 27 | $(this).removeClass('ready'); 28 | $(this).addClass('error'); 29 | } else { 30 | $(this).removeClass('error'); 31 | $(this).addClass('ready'); 32 | } 33 | } 34 | }); 35 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_13.md: -------------------------------------------------------------------------------- 1 | ### 3.13 Collections validation 2 | 3 | **NB**: Read [this note](Resources/doc/3_0.md) - it's important for this task. 4 | 5 | So, if you went [here](http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype) to the ```addTagForm``` JavaScript function, 6 | now you can add definition for the validator: 7 | ```js 8 | function addTagForm($collectionHolder, $newLinkLi) { 9 | // Get the data-prototype explained earlier 10 | var prototype = $collectionHolder.data('prototype'); 11 | 12 | // get the new index 13 | var index = $collectionHolder.data('index'); 14 | 15 | // Replace '__name__' in the prototype's HTML to 16 | // instead be a number based on how many items we have 17 | var newForm = prototype.replace(/__name__/g, index); 18 | 19 | // increase the index with one for the next item 20 | $collectionHolder.data('index', index + 1); 21 | 22 | // Display the form in the page in an li, before the "Add a tag" link li 23 | var $newFormLi = $('
  • ').append(newForm); 24 | $newLinkLi.before($newFormLi); 25 | 26 | 27 | // And here we add a validation using jQuery: 28 | $($collectionHolder).jsFormValidator('addPrototype', index); 29 | // Or using pure JS: 30 | FpJsFormValidator.customize($collectionHolder, 'addPrototype', index); 31 | } 32 | ``` 33 | 34 | Deleting of elements is as simple as adding, e.g. for the [addTagFormDeleteLink](http://symfony.com/doc/current/cookbook/form/form_collections.html#templates-modifications) function: 35 | ```js 36 | function addTagFormDeleteLink($tagFormLi) { 37 | var $removeFormA = $('delete this tag'); 38 | $tagFormLi.append($removeFormA); 39 | 40 | $removeFormA.on('click', function(e) { 41 | // prevent the link from creating a "#" on the URL 42 | e.preventDefault(); 43 | 44 | // Here you should receive two important variable which are mentioned in the previous function: 45 | // $collectionHolder - DOM element of collection holder 46 | // index - the current tag name, e.g. can be matched from id 47 | $($collectionHolder).jsFormValidator('delPrototype', index); 48 | 49 | // Or using pure JS: 50 | FpJsFormValidator.customize($collectionHolder, 'delPrototype', index); 51 | 52 | // remove the li for the tag form 53 | $tagFormLi.remove(); 54 | }); 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /src/Resources/doc/3_2.md: -------------------------------------------------------------------------------- 1 | ### 3.2 Error display 2 | 3 | The showErrors function should delete previous errors and display new ones. 4 | The example below shows you how it works in our default implementation. 5 | The ```sourceId``` variable is an identifier of validation source. 6 | It can be used to prevent any confusion between the field's errors and other errors which have come from other sources. 7 | For example, this field (user_email) may contain the Email constraint, and its own parent may contain the UniqueEntity constraint by this field. 8 | Both of these errors should be displayed for the email field, but the first one will be displayed/deleted by the 'user_email' validator and the second one - by the parent. 9 | By default we use this variable to add it as a class name to 'li' tags, and then we use it to remove the errors by this class name: 10 | 11 | ```js 12 | $('#user_email').jsFormValidator({ 13 | showErrors: function(errors, sourceId) { 14 | var list = $(this).prev('ul.form-errors'); 15 | if (!list.length) { 16 | list = $('
      '); 17 | $(this).before(list); 18 | } 19 | list.find('.' + sourceId).remove(); 20 | 21 | for (var i in errors) { 22 | var li = $('
    • ', { 23 | 'class': sourceId, 24 | 'text': 'custom_'+ errors[i] 25 | }); 26 | list.append(li); 27 | } 28 | } 29 | }); 30 | ``` 31 | 32 | Pure Javascript: 33 | ```js 34 | var field = document.getElementById('user_email'); 35 | FpJsFormValidator.customize(field, { 36 | showErrors: function(errors, sourceId) { 37 | for (var i in errors) { 38 | // do something with each error 39 | } 40 | } 41 | }); 42 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_3.md: -------------------------------------------------------------------------------- 1 | ### 3.3 Get validation groups from a closure 2 | 3 | If you have defined validation groups as a callback: 4 | 5 | ```php 6 | namespace Acme\DemoBundle\Form; 7 | 8 | class UserType extends AbstractType 9 | { 10 | // ... 11 | 12 | public function setDefaultOptions(OptionsResolverInterface $resolver) 13 | { 14 | $resolver->setDefaults( 15 | array( 16 | 'data_class' => 'Acme\DemoBundle\Entity\User', 17 | 'validation_groups' => function () { 18 | if (...) { 19 | return array(...); 20 | } else { 21 | return array(...); 22 | } 23 | } 24 | ) 25 | ); 26 | } 27 | } 28 | ``` 29 | 30 | Then you have to implement it on the JS side: 31 | ```js 32 | $('form#user').jsFormValidator({ 33 | groups: function () { 34 | if (...) { 35 | return [...]; 36 | } else { 37 | return [...]; 38 | } 39 | } 40 | }); 41 | ``` 42 | 43 | Pure Javascript: 44 | ```js 45 | var field = document.getElementById('user'); 46 | FpJsFormValidator.customize(field, { 47 | groups: function () { 48 | if (...) { 49 | return [...]; 50 | } else { 51 | return [...]; 52 | } 53 | } 54 | }); 55 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_4.md: -------------------------------------------------------------------------------- 1 | ### 3.4 Getters validation 2 | 3 | If you have getters validation: 4 | ```php 5 | namespace Acme\DemoBundle\Entity; 6 | class User 7 | { 8 | // ... 9 | 10 | /** 11 | * @return bool 12 | * 13 | * @Assert\True(message="The password cannot match your first name") 14 | */ 15 | public function isPasswordLegal() 16 | { 17 | return $this->firstName != $this->password; 18 | } 19 | } 20 | ``` 21 | 22 | then you have to create a callback: 23 | 24 | Then you have to implement it on the JS side: 25 | ```js 26 | $('form#user').jsFormValidator({ 27 | callbacks: { 28 | 'isPasswordLegal': function() { 29 | var firstName = $('#user_firstName').val(); 30 | var password = $('#user_password').val(); 31 | return firstName != password; 32 | } 33 | } 34 | }); 35 | ``` 36 | 37 | Pure Javascript: 38 | ```js 39 | var field = document.getElementById('user'); 40 | FpJsFormValidator.customize(field, { 41 | callbacks: { 42 | 'isPasswordLegal': function() { 43 | var firstName = document.getElementById('user_firstName').value; 44 | var password = document.getElementById('user_password').value; 45 | return firstName != password; 46 | } 47 | } 48 | }); 49 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_5.md: -------------------------------------------------------------------------------- 1 | ### 3.5 The Callback constraint 2 | 3 | #### 3.5.1 Callback by a method name 4 | 5 | For the next cases: 6 | 7 | ```php 8 | namespace Acme\DemoBundle\Entity; 9 | 10 | use Symfony\Component\Validator\Constraints as Assert; 11 | 12 | /** 13 | * @Assert\Callback("checkEmail") 14 | * or 15 | * @Assert\Callback({"checkEmail"}) 16 | */ 17 | class User 18 | { 19 | // ... 20 | 21 | public function checkEmail() 22 | { 23 | if (...) { 24 | $context->addViolationAt('email', 'Email is not valid'); 25 | } 26 | } 27 | } 28 | ``` 29 | or 30 | ```php 31 | /** 32 | * @Assert\Callback 33 | */ 34 | public function checkEmail() 35 | { 36 | if (...) { 37 | $context->addViolationAt('email', 'Email is not valid'); 38 | } 39 | } 40 | ``` 41 | 42 | You have to create the next callback (pay attention that here you should pass the [sourceId](3_2.md) parameter): 43 | ```js 44 | $('form#user').jsFormValidator({ 45 | callbacks: { 46 | 'checkEmail': function() { 47 | var errors = []; 48 | if (...) { 49 | errors.push('Email is not valid'); 50 | } 51 | $('#form_email').jsFormValidator('showErrors', { 52 | errors: errors, 53 | sourceId: 'check-email-callback' 54 | }); 55 | } 56 | } 57 | }); 58 | ``` 59 | 60 | Pure Javascript: 61 | ```js 62 | var field = document.getElementById('user'); 63 | FpJsFormValidator.customize(field, { 64 | callbacks: { 65 | 'checkEmail': function() { 66 | var errors = []; 67 | if (...) { 68 | errors.push('Email is not valid'); 69 | } 70 | var email = document.getElementById('user_email'); 71 | FpJsFormValidator.customize(email, 'showErrors', { 72 | errors: errors, 73 | sourceId: 'check-email-callback' 74 | }); 75 | } 76 | } 77 | }); 78 | ``` 79 | 80 | #### 3.5.2 Callback by class and method names 81 | 82 | In case if you have defined a callback like this: 83 | ```php 84 | namespace Acme\DemoBundle\Entity; 85 | 86 | /** 87 | * @Assert\Callback({"Acme\DemoBundle\Validator\ExternalValidator", "checkEmail"}) 88 | */ 89 | class User 90 | { 91 | // ... 92 | } 93 | ``` 94 | 95 | then you can define it on the JS side like: 96 | ```js 97 | // ... 98 | callbacks: { 99 | 'Acme\\DemoBundle\\Validator\\ExternalValidator': { 100 | 'checkEmail': function () { 101 | // ... 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | or you can also define it without nesting (like in the [3.5.1](#p_3_5_1) paragraph), but only if the method name is unique: 108 | ```js 109 | // ... 110 | callbacks: { 111 | 'checkEmail': function () { 112 | // ... 113 | } 114 | } 115 | ``` -------------------------------------------------------------------------------- /src/Resources/doc/3_6.md: -------------------------------------------------------------------------------- 1 | ### 3.6 The Choice constraint. How to get the choices list from a callback 2 | 3 | In general, it works in the same way as the previous step. 4 | In case if you have: 5 | ```php 6 | namespace Acme\DemoBundle\Entity; 7 | 8 | use Symfony\Component\Validator\Constraints as Assert; 9 | 10 | class User 11 | { 12 | /** 13 | * @Assert\Choice(callback = {"Acme\DemoBundle\Entity\Util", "getGenders"}) 14 | */ 15 | protected $gender; 16 | } 17 | ``` 18 | ```php 19 | namespace Acme\DemoBundle\Entity; 20 | 21 | class Util 22 | { 23 | public static function getGenders() 24 | { 25 | return array('male', 'female'); 26 | } 27 | } 28 | ``` 29 | 30 | Then: 31 | ```js 32 | $('form#user').jsFormValidator({ 33 | callbacks: { 34 | 'Acme\\DemoBundle\\Entity\\Util': { 35 | 'getGenders': function() { 36 | return ['male', 'female']; 37 | } 38 | } 39 | } 40 | }); 41 | ``` 42 | 43 | Pure Javascript: 44 | ```js 45 | var field = document.getElementById('user'); 46 | FpJsFormValidator.customize(field, { 47 | callbacks: { 48 | 'Acme\\DemoBundle\\Entity\\Util': { 49 | 'getGenders': function() { 50 | return ['male', 'female']; 51 | } 52 | } 53 | } 54 | }); 55 | ``` 56 | 57 | also, you can simplify it as it was described [here](3_5.md#p_3_5_2_1) -------------------------------------------------------------------------------- /src/Resources/doc/3_7.md: -------------------------------------------------------------------------------- 1 | ### 3.7 Custom constraints 2 | 3 | If you have your own constraint, you have to implement it on the JS side too. 4 | Just add a JS class with a name, similar to the full class name of the related constraint (but without slashes). 5 | For example, you have created the next constraint: 6 | 7 | ```php 8 | // src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumeric.php 9 | namespace Acme\DemoBundle\Validator\Constraints; 10 | 11 | use Symfony\Component\Validator\Constraint; 12 | 13 | class ContainsAlphanumeric extends Constraint 14 | { 15 | public $message = 'The string "%string%" contains an illegal character: it can only contain letters or numbers.'; 16 | } 17 | 18 | // src/Acme/DemoBundle/Validator/Constraints/ContainsAlphanumericValidator.php 19 | namespace Acme\DemoBundle\Validator\Constraints; 20 | 21 | use Symfony\Component\Validator\Constraint; 22 | use Symfony\Component\Validator\ConstraintValidator; 23 | 24 | class ContainsAlphanumericValidator extends ConstraintValidator 25 | { 26 | public function validate($value, Constraint $constraint) 27 | { 28 | if (!preg_match('/^[a-zA-Za0-9]+$/', $value, $matches)) { 29 | $this->context->addViolation($constraint->message, array('%string%' => $value)); 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | To cover it on JS side, you have to create: 36 | 37 | ```js 38 | '; 74 | } 75 | 76 | return $jsModels; 77 | } 78 | 79 | /** 80 | * Returns the name of the extension. 81 | * 82 | * @return string The extension name 83 | * @codeCoverageIgnore 84 | */ 85 | public function getName() 86 | { 87 | return 'fp_js_form_validator'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "composer/ca-bundle": { 3 | "version": "1.2.6" 4 | }, 5 | "doctrine/annotations": { 6 | "version": "1.0", 7 | "recipe": { 8 | "repo": "github.com/symfony/recipes", 9 | "branch": "master", 10 | "version": "1.0", 11 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672" 12 | }, 13 | "files": [ 14 | "config/routes/annotations.yaml" 15 | ] 16 | }, 17 | "doctrine/lexer": { 18 | "version": "1.2.0" 19 | }, 20 | "erusev/parsedown": { 21 | "version": "1.7.4" 22 | }, 23 | "league/uri-parser": { 24 | "version": "1.4.1" 25 | }, 26 | "masterminds/html5": { 27 | "version": "2.7.0" 28 | }, 29 | "php": { 30 | "version": "7.2" 31 | }, 32 | "psr/cache": { 33 | "version": "1.0.1" 34 | }, 35 | "psr/container": { 36 | "version": "1.0.0" 37 | }, 38 | "psr/log": { 39 | "version": "1.1.2" 40 | }, 41 | "sensio/framework-extra-bundle": { 42 | "version": "5.2", 43 | "recipe": { 44 | "repo": "github.com/symfony/recipes", 45 | "branch": "master", 46 | "version": "5.2", 47 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" 48 | }, 49 | "files": [ 50 | "config/packages/sensio_framework_extra.yaml" 51 | ] 52 | }, 53 | "sensiolabs/security-checker": { 54 | "version": "4.0", 55 | "recipe": { 56 | "repo": "github.com/symfony/recipes", 57 | "branch": "master", 58 | "version": "4.0", 59 | "ref": "160c9b600564faa1224e8f387d49ef13ceb8b793" 60 | }, 61 | "files": [ 62 | "config/packages/security_checker.yaml" 63 | ] 64 | }, 65 | "symfony/asset": { 66 | "version": "v5.0.3" 67 | }, 68 | "symfony/cache": { 69 | "version": "v5.0.3" 70 | }, 71 | "symfony/cache-contracts": { 72 | "version": "v2.0.1" 73 | }, 74 | "symfony/config": { 75 | "version": "v5.0.3" 76 | }, 77 | "symfony/console": { 78 | "version": "4.4", 79 | "recipe": { 80 | "repo": "github.com/symfony/recipes", 81 | "branch": "master", 82 | "version": "4.4", 83 | "ref": "ea8c0eda34fda57e7d5cd8cbd889e2a387e3472c" 84 | }, 85 | "files": [ 86 | "bin/console", 87 | "config/bootstrap.php" 88 | ] 89 | }, 90 | "symfony/debug": { 91 | "version": "v4.4.3" 92 | }, 93 | "symfony/dependency-injection": { 94 | "version": "v5.0.3" 95 | }, 96 | "symfony/dotenv": { 97 | "version": "v5.0.3" 98 | }, 99 | "symfony/error-handler": { 100 | "version": "v4.4.3" 101 | }, 102 | "symfony/event-dispatcher": { 103 | "version": "v4.4.3" 104 | }, 105 | "symfony/event-dispatcher-contracts": { 106 | "version": "v1.1.7" 107 | }, 108 | "symfony/expression-language": { 109 | "version": "v5.0.3" 110 | }, 111 | "symfony/filesystem": { 112 | "version": "v5.0.3" 113 | }, 114 | "symfony/finder": { 115 | "version": "v5.0.3" 116 | }, 117 | "symfony/flex": { 118 | "version": "1.0", 119 | "recipe": { 120 | "repo": "github.com/symfony/recipes", 121 | "branch": "master", 122 | "version": "1.0", 123 | "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" 124 | }, 125 | "files": [ 126 | ".env" 127 | ] 128 | }, 129 | "symfony/form": { 130 | "version": "v4.4.3" 131 | }, 132 | "symfony/framework-bundle": { 133 | "version": "4.4", 134 | "recipe": { 135 | "repo": "github.com/symfony/recipes", 136 | "branch": "master", 137 | "version": "4.4", 138 | "ref": "23ecaccc551fe2f74baf613811ae529eb07762fa" 139 | }, 140 | "files": [ 141 | "config/bootstrap.php", 142 | "config/packages/cache.yaml", 143 | "config/packages/framework.yaml", 144 | "config/packages/test/framework.yaml", 145 | "config/routes/dev/framework.yaml", 146 | "config/services.yaml", 147 | "public/index.php", 148 | "src/Controller/.gitignore", 149 | "src/Kernel.php" 150 | ] 151 | }, 152 | "symfony/http-foundation": { 153 | "version": "v5.0.3" 154 | }, 155 | "symfony/http-kernel": { 156 | "version": "v4.4.3" 157 | }, 158 | "symfony/inflector": { 159 | "version": "v5.0.3" 160 | }, 161 | "symfony/intl": { 162 | "version": "v5.0.3" 163 | }, 164 | "symfony/mime": { 165 | "version": "v5.0.3" 166 | }, 167 | "symfony/options-resolver": { 168 | "version": "v5.0.3" 169 | }, 170 | "symfony/polyfill-ctype": { 171 | "version": "v1.13.1" 172 | }, 173 | "symfony/polyfill-intl-icu": { 174 | "version": "v1.13.1" 175 | }, 176 | "symfony/polyfill-intl-idn": { 177 | "version": "v1.13.1" 178 | }, 179 | "symfony/polyfill-mbstring": { 180 | "version": "v1.13.1" 181 | }, 182 | "symfony/polyfill-php72": { 183 | "version": "v1.13.1" 184 | }, 185 | "symfony/polyfill-php73": { 186 | "version": "v1.13.1" 187 | }, 188 | "symfony/property-access": { 189 | "version": "v5.0.3" 190 | }, 191 | "symfony/routing": { 192 | "version": "4.2", 193 | "recipe": { 194 | "repo": "github.com/symfony/recipes", 195 | "branch": "master", 196 | "version": "4.2", 197 | "ref": "683dcb08707ba8d41b7e34adb0344bfd68d248a7" 198 | }, 199 | "files": [ 200 | "config/packages/prod/routing.yaml", 201 | "config/packages/routing.yaml", 202 | "config/routes.yaml" 203 | ] 204 | }, 205 | "symfony/security-bundle": { 206 | "version": "4.4", 207 | "recipe": { 208 | "repo": "github.com/symfony/recipes", 209 | "branch": "master", 210 | "version": "4.4", 211 | "ref": "30efd98dd3b4ead6e9ad4713b1efc43bbe94bf77" 212 | }, 213 | "files": [ 214 | "config/packages/security.yaml" 215 | ] 216 | }, 217 | "symfony/security-core": { 218 | "version": "v4.4.3" 219 | }, 220 | "symfony/security-csrf": { 221 | "version": "v5.0.3" 222 | }, 223 | "symfony/security-guard": { 224 | "version": "v4.4.3" 225 | }, 226 | "symfony/security-http": { 227 | "version": "v4.4.3" 228 | }, 229 | "symfony/service-contracts": { 230 | "version": "v2.0.1" 231 | }, 232 | "symfony/translation": { 233 | "version": "3.3", 234 | "recipe": { 235 | "repo": "github.com/symfony/recipes", 236 | "branch": "master", 237 | "version": "3.3", 238 | "ref": "2ad9d2545bce8ca1a863e50e92141f0b9d87ffcd" 239 | }, 240 | "files": [ 241 | "config/packages/translation.yaml", 242 | "translations/.gitignore" 243 | ] 244 | }, 245 | "symfony/translation-contracts": { 246 | "version": "v2.0.1" 247 | }, 248 | "symfony/twig-bridge": { 249 | "version": "v4.4.3" 250 | }, 251 | "symfony/twig-bundle": { 252 | "version": "4.4", 253 | "recipe": { 254 | "repo": "github.com/symfony/recipes", 255 | "branch": "master", 256 | "version": "4.4", 257 | "ref": "15a41bbd66a1323d09824a189b485c126bbefa51" 258 | }, 259 | "files": [ 260 | "config/packages/test/twig.yaml", 261 | "config/packages/twig.yaml", 262 | "templates/base.html.twig" 263 | ] 264 | }, 265 | "symfony/validator": { 266 | "version": "4.3", 267 | "recipe": { 268 | "repo": "github.com/symfony/recipes", 269 | "branch": "master", 270 | "version": "4.3", 271 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 272 | }, 273 | "files": [ 274 | "config/packages/test/validator.yaml", 275 | "config/packages/validator.yaml" 276 | ] 277 | }, 278 | "symfony/var-dumper": { 279 | "version": "v5.0.3" 280 | }, 281 | "symfony/var-exporter": { 282 | "version": "v5.0.3" 283 | }, 284 | "symfony/webpack-encore-bundle": { 285 | "version": "1.6", 286 | "recipe": { 287 | "repo": "github.com/symfony/recipes", 288 | "branch": "master", 289 | "version": "1.6", 290 | "ref": "69e1d805ad95964088bd510c05995e87dc391564" 291 | }, 292 | "files": [ 293 | "assets/css/app.css", 294 | "assets/js/app.js", 295 | "config/packages/assets.yaml", 296 | "config/packages/prod/webpack_encore.yaml", 297 | "config/packages/test/webpack_encore.yaml", 298 | "config/packages/webpack_encore.yaml", 299 | "package.json", 300 | "webpack.config.js" 301 | ] 302 | }, 303 | "symfony/yaml": { 304 | "version": "v5.0.3" 305 | }, 306 | "tgalopin/html-sanitizer": { 307 | "version": "1.2.0" 308 | }, 309 | "tgalopin/html-sanitizer-bundle": { 310 | "version": "1.0", 311 | "recipe": { 312 | "repo": "github.com/symfony/recipes-contrib", 313 | "branch": "master", 314 | "version": "1.0", 315 | "ref": "26a72f38eede2c53b5d3ccbed5c150e10a93268d" 316 | }, 317 | "files": [ 318 | "config/packages/html_sanitizer.yaml" 319 | ] 320 | }, 321 | "twig/extensions": { 322 | "version": "1.0", 323 | "recipe": { 324 | "repo": "github.com/symfony/recipes", 325 | "branch": "master", 326 | "version": "1.0", 327 | "ref": "a86723ee8d8b2f9437c8ce60a5546a1c267da5ed" 328 | }, 329 | "files": [ 330 | "config/packages/twig_extensions.yaml" 331 | ] 332 | }, 333 | "twig/twig": { 334 | "version": "v2.12.3" 335 | } 336 | } 337 | --------------------------------------------------------------------------------