From 15c1a6c617b9da2ace4b1f5df6c94d299ccfa5c6 Mon Sep 17 00:00:00 2001 From: salsa aruba Date: Tue, 8 Dec 2015 12:53:46 +0100 Subject: [PATCH 001/194] fixed falsy checkboxes not being saved by adding a hidden input with the same name --- src/views/checkbox.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/checkbox.php b/src/views/checkbox.php index 7a4a055d..4d81ff0e 100644 --- a/src/views/checkbox.php +++ b/src/views/checkbox.php @@ -5,6 +5,7 @@ + From 0744492196674ba65710a491715fb0f953770b39 Mon Sep 17 00:00:00 2001 From: saeidraei Date: Fri, 10 Feb 2017 12:51:20 +0330 Subject: [PATCH 002/194] adds createByArray function to formBuilder class --- src/Kris/LaravelFormBuilder/FormBuilder.php | 68 +++++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index e241c920..fa790216 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -1,6 +1,4 @@ -container + ->make(Form::class) + ->addData($data) + ->setRequest($this->container->make('request')) + ->setFormHelper($this->formHelper) + ->setEventDispatcher($this->eventDispatcher) + ->setFormBuilder($this) + ->setValidator($this->container->make('validator')) + ->setFormOptions($options); + + $this->buildFormByArray($form, $items); + + $this->eventDispatcher->fire(new AfterFormCreation($form)); + + return $form; + } + + /** + * @param $form + * @param $items + */ + public function buildFormByArray($form, $items) + { + foreach ($items as $item) { + if (!isset($item['name'])) { + throw new \InvalidArgumentException( + 'Name is not set in form array.' + ); + } + $name = $item['name']; + $type = isset($item['type']) && $item['type'] ? $item['type'] : ''; + $modify = isset($item['modify']) && $item['modify'] ? $item['modify'] : false; + unset($item['name']); + unset($item['type']); + unset($item['modify']); + $form->add($name, $type, $item, $modify); + } + } + + /** + * Get the namespace from the config * * @return string */ @@ -88,11 +130,11 @@ protected function getNamespaceFromConfig() } /** - * Get instance of the empty form which can be modified. + * Get instance of the empty form which can be modified * * @param array $options * @param array $data - * @return \Kris\LaravelFormBuilder\Form + * @return Form */ public function plain(array $options = [], array $data = []) { From 4f4ec45ae9d6edc854319f5365fbcce09b260eaf Mon Sep 17 00:00:00 2001 From: saeidraei Date: Fri, 10 Feb 2017 13:02:57 +0330 Subject: [PATCH 003/194] adds new ability to README file --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index 5334e523..d96ba945 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,45 @@ Go to `/songs/create`; above code will generate this html: ``` +Or you can generate forms easier by using simple array +```php +createByArray([ + [ + 'name' => 'name', + 'type' => 'text', + ], + [ + 'name' => 'lyrics', + 'type' => 'textarea', + ], + [ + 'name' => 'publish', + 'type' => 'checkbox' + ], + ] + ,[ + 'method' => 'POST', + 'url' => route('song.store') + ]); + + return view('song.create', compact('form')); + } +} +``` + + ### Contributing Project follows [PSR-2](http://www.php-fig.org/psr/psr-2/) standard and it's covered with PHPUnit tests. Pull requests should include tests and pass [Travis CI](https://travis-ci.org/kristijanhusak/laravel-form-builder) build. From 05fb3a72d925e934c8bd2d33895039e0dee99ec7 Mon Sep 17 00:00:00 2001 From: Saeid Date: Mon, 20 Feb 2017 21:07:21 +0330 Subject: [PATCH 004/194] adds test for creating form by array --- tests/FormBuilderTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/FormBuilderTest.php b/tests/FormBuilderTest.php index 5af61c0f..6a15d571 100644 --- a/tests/FormBuilderTest.php +++ b/tests/FormBuilderTest.php @@ -28,6 +28,25 @@ public function it_creates_plain_form_and_sets_options_on_it() $this->assertNull($plainForm->buildForm()); } + /** @test */ + public function it_creates_form_with_array_and_compares_it_with_created_form_by_class() + { + $form = $this->formBuilder->create(CustomDummyForm::class); + $arrayForm = $this->formBuilder->createByArray([ + [ + 'type' => 'text', + 'name' => 'title', + ], + [ + 'type' => 'textarea', + 'name' => 'body', + ] + ]); + + $this->assertEquals($form->getField('title')->getType(), $arrayForm->getField('title')->getType()); + $this->assertEquals($form->getField('body')->getType(), $arrayForm->getField('body')->getType()); + } + /** @test */ public function it_creates_custom_form_and_sets_options_on_it() { From 02b6ac8e7c4bcbd2cbd1a4baf3a2ea2de02b3d62 Mon Sep 17 00:00:00 2001 From: Saeid Date: Tue, 21 Feb 2017 22:33:27 +0330 Subject: [PATCH 005/194] updates Form::class to $this->plainFormClass in FormBuilder class --- src/Kris/LaravelFormBuilder/FormBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index 72c4b72a..5616fcf9 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -81,7 +81,7 @@ public function create($formClass, array $options = [], array $data = []) public function createByArray($items, array $options = [], array $data = []) { $form = $this->container - ->make(Form::class) + ->make($this->plainFormClass) ->addData($data) ->setRequest($this->container->make('request')) ->setFormHelper($this->formHelper) From b9edf8b27d4ad14d1385c776be8856e7bfc813a8 Mon Sep 17 00:00:00 2001 From: Sergey Chursin Date: Tue, 14 Mar 2017 11:22:07 +0300 Subject: [PATCH 006/194] ChoiceType template: deal with attr --- src/views/choice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/choice.php b/src/views/choice.php index 63395b59..faba78af 100644 --- a/src/views/choice.php +++ b/src/views/choice.php @@ -10,7 +10,7 @@ - render(['selected' => $options['selected']], true, true, false) ?> + render(['attr' => $options['attr'], 'selected' => $options['selected']], true, true, false) ?> From a4ee9b3a94ceafda487bf7d2030f3661ecb0164b Mon Sep 17 00:00:00 2001 From: Sergey Chursin Date: Tue, 14 Mar 2017 16:24:49 +0300 Subject: [PATCH 007/194] $options['choice_options'] --- src/views/choice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/choice.php b/src/views/choice.php index faba78af..69f51c81 100644 --- a/src/views/choice.php +++ b/src/views/choice.php @@ -10,7 +10,7 @@ - render(['attr' => $options['attr'], 'selected' => $options['selected']], true, true, false) ?> + render($options['choice_options'], true, true, false) ?> From ac1e4ad31b2f8a229e11c34cda86bede8794541a Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Thu, 30 Mar 2017 23:27:28 +0200 Subject: [PATCH 008/194] +Extensible RulesParser --- src/Kris/LaravelFormBuilder/Fields/FormField.php | 3 +-- src/Kris/LaravelFormBuilder/FormHelper.php | 12 +++++++++++- tests/RulesParserTest.php | 3 +-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index ae63a815..fd5a9a89 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -5,7 +5,6 @@ use Kris\LaravelFormBuilder\Form; use Illuminate\Database\Eloquent\Model; use Kris\LaravelFormBuilder\FormHelper; -use Kris\LaravelFormBuilder\RulesParser; use Illuminate\Database\Eloquent\Collection; /** @@ -234,7 +233,7 @@ protected function transformKey($key) protected function prepareOptions(array $options = []) { $helper = $this->formHelper; - $rulesParser = new RulesParser($this); + $rulesParser = $helper->createRulesParser($this); $rules = $this->getOption('rules'); $parsedRules = $rules ? $rulesParser->parse($rules) : []; diff --git a/src/Kris/LaravelFormBuilder/FormHelper.php b/src/Kris/LaravelFormBuilder/FormHelper.php index 71381a2d..6fe9681d 100644 --- a/src/Kris/LaravelFormBuilder/FormHelper.php +++ b/src/Kris/LaravelFormBuilder/FormHelper.php @@ -7,9 +7,10 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Illuminate\Translation\Translator; use Kris\LaravelFormBuilder\Fields\FormField; use Kris\LaravelFormBuilder\Form; -use Illuminate\Translation\Translator; +use Kris\LaravelFormBuilder\RulesParser; class FormHelper { @@ -260,6 +261,15 @@ public function formatLabel($name) return ucfirst(str_replace('_', ' ', $name)); } + /** + * @param FormField $field + * @return RulesParser + */ + public function createRulesParser(FormField $field) + { + return new RulesParser($field); + } + /** * @param FormField[] $fields * @return array diff --git a/tests/RulesParserTest.php b/tests/RulesParserTest.php index bef74ecc..d7396067 100644 --- a/tests/RulesParserTest.php +++ b/tests/RulesParserTest.php @@ -1,7 +1,6 @@ plainForm); - $this->parser = new RulesParser($field); + $this->parser = $this->formHelper->createRulesParser($field); } /** @test */ From 972b99cb1cdb07712b2c5f6e1ad8556a9e473a79 Mon Sep 17 00:00:00 2001 From: Jay Fletcher Date: Wed, 5 Apr 2017 18:03:02 -0400 Subject: [PATCH 009/194] Bug Fix - ButtonGroupType.php Button group field type was not working out of the box due to a non-existent path being set. Fixed the button group template path to match the rest of the types. --- src/Kris/LaravelFormBuilder/Fields/ButtonGroupType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/ButtonGroupType.php b/src/Kris/LaravelFormBuilder/Fields/ButtonGroupType.php index af6ab38f..f090fbfe 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ButtonGroupType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ButtonGroupType.php @@ -12,7 +12,7 @@ class ButtonGroupType extends FormField */ protected function getTemplate() { - return 'fields.buttongroup'; + return 'buttongroup'; } /** From 8af9e5c7dfdfae33795f5c9bd4b3ab3f5956da43 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Thu, 6 Apr 2017 13:40:59 +0200 Subject: [PATCH 010/194] Add ability to automatically validate Form classes when they get validated. --- .../FormBuilderServiceProvider.php | 15 ++++++++ .../Traits/ValidatesWhenResolved.php | 8 +++++ tests/Fixtures/TestController.php | 11 ++++++ tests/Fixtures/TestForm.php | 16 +++++++++ tests/FormBuilderValidationTest.php | 36 +++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 src/Kris/LaravelFormBuilder/Traits/ValidatesWhenResolved.php create mode 100644 tests/Fixtures/TestController.php create mode 100644 tests/Fixtures/TestForm.php create mode 100644 tests/FormBuilderValidationTest.php diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index 42f7482a..5281d4d1 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -7,6 +7,7 @@ use Collective\Html\HtmlBuilder; use Illuminate\Support\ServiceProvider; use Illuminate\Foundation\Application; +use Kris\LaravelFormBuilder\Traits\ValidatesWhenResolved; class FormBuilderServiceProvider extends ServiceProvider { @@ -36,6 +37,20 @@ public function register() }); $this->app->alias('laravel-form-builder', 'Kris\LaravelFormBuilder\FormBuilder'); + + $this->app->resolving(function ($object, $app) { + $request = $app->make('request'); + + if (in_array(ValidatesWhenResolved::class, class_uses($object)) && $request->method() !== 'GET') { + $object->setEventDispatcher($app->make('events')); + $object->setFormHelper($app->make('laravel-form-helper')); + $object->setRequest($request); + $object->setFormBuilder($app->make('laravel-form-builder')); + $object->setValidator($app->make('validator')); + $object->buildForm(); + $object->redirectIfNotValid(); + } + }); } /** diff --git a/src/Kris/LaravelFormBuilder/Traits/ValidatesWhenResolved.php b/src/Kris/LaravelFormBuilder/Traits/ValidatesWhenResolved.php new file mode 100644 index 00000000..72e6cffd --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Traits/ValidatesWhenResolved.php @@ -0,0 +1,8 @@ +add('name', 'text', ['rules' => ['required', 'min:3']]); + $this->add('email', 'text', ['rules' => ['required', 'email', 'min:3']]); + } + +} \ No newline at end of file diff --git a/tests/FormBuilderValidationTest.php b/tests/FormBuilderValidationTest.php new file mode 100644 index 00000000..18467d99 --- /dev/null +++ b/tests/FormBuilderValidationTest.php @@ -0,0 +1,36 @@ +app->make('Illuminate\Contracts\Http\Kernel')->pushMiddleware('Illuminate\Session\Middleware\StartSession'); + } + + public function testItValidatesWhenResolved() + { + Route::post('/test', TestController::class.'@validate'); + + $this->post('/test', ['email' => 'foo@bar.com']) + ->assertRedirect('/') + ->assertSessionHasErrors(['name']); + } + + public function testItDoesNotValidateGetRequests() + { + Route::get('/test', TestController::class.'@validate'); + + $this->get('/test', ['email' => 'foo@bar.com']) + ->assertStatus(200); + } + } +} \ No newline at end of file From 41822718680dee04e26783666cd3a0ff3186d7d7 Mon Sep 17 00:00:00 2001 From: Nathan Brittain Date: Fri, 7 Apr 2017 12:35:04 +1000 Subject: [PATCH 011/194] Bug-fix: CollectionType.php Use input() to correctly return the data from the current request so that, if it contains multiple rows, those rows can be read in as children of the Collection field. --- src/Kris/LaravelFormBuilder/Fields/CollectionType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index b22582ce..0500c8f8 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -77,7 +77,7 @@ protected function createChildren() $this->children = []; $type = $this->getOption('type'); $oldInput = $this->parent->getRequest()->old($this->getNameKey()); - $currentInput = $this->parent->getRequest()->get($this->getNameKey()); + $currentInput = $this->parent->getRequest()->input($this->getNameKey()); try { $fieldType = $this->formHelper->getFieldType($type); From fdb6fd5ced6330ed90c0a7634abdbe3c353d62a1 Mon Sep 17 00:00:00 2001 From: saeidraei Date: Sat, 15 Apr 2017 09:22:59 +0430 Subject: [PATCH 012/194] updating comments by new version --- src/Kris/LaravelFormBuilder/FormBuilder.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index 5616fcf9..66eb25f6 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -1,4 +1,6 @@ - Date: Sun, 16 Apr 2017 09:56:51 +0300 Subject: [PATCH 013/194] Improve README --- README.md | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5334e523..df78abca 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,24 @@ Form builder for Laravel 5 inspired by Symfony's form builder. With help of Lara By default it supports Bootstrap 3. ## Laravel 4 -For laravel 4 version check [laravel4-form-builder](https://github.com/kristijanhusak/laravel4-form-builder) +For Laravel 4 version check [laravel4-form-builder](https://github.com/kristijanhusak/laravel4-form-builder). ## Upgrade to 1.6 If you upgraded to `>1.6.*` from `1.5.*` or earlier, and having problems with form value binding, rename `default_value` to `value`. -More info in [changelog](https://github.com/kristijanhusak/laravel-form-builder/blob/master/CHANGELOG.md) +More info in [changelog](https://github.com/kristijanhusak/laravel-form-builder/blob/master/CHANGELOG.md). ## Documentation For detailed documentation refer to [http://kristijanhusak.github.io/laravel-form-builder/](http://kristijanhusak.github.io/laravel-form-builder/). ## Changelog -Changelog can be found [here](https://github.com/kristijanhusak/laravel-form-builder/blob/master/CHANGELOG.md) +Changelog can be found [here](https://github.com/kristijanhusak/laravel-form-builder/blob/master/CHANGELOG.md). -###Installation +## Installation -``` +### Using Composer + +```sh composer require kris/laravel-form-builder ``` @@ -41,7 +43,7 @@ Or manually by modifying `composer.json` file: } ``` -run `composer install` +And run `composer install` Then add Service provider to `config/app.php` @@ -62,10 +64,10 @@ And Facade (also in `config/app.php`) ``` -**Notice**: This package will add `laravelcollective/html` package and load aliases (Form, Html) if they do not exist in the IoC container +**Notice**: This package will add `laravelcollective/html` package and load aliases (Form, Html) if they do not exist in the IoC container. -### Quick start +## Quick start Creating form classes is easy. With a simple artisan command: @@ -193,23 +195,18 @@ class SongsController extends BaseController { } ``` - - - - - Create the routes ```php // app/Http/routes.php Route::get('songs/create', [ - 'uses' => 'SongsController@create', - 'as' => 'song.create' + 'uses' => 'SongsController@create', + 'as' => 'song.create' ]); Route::post('songs', [ - 'uses' => 'SongsController@store', - 'as' => 'song.store' + 'uses' => 'SongsController@store', + 'as' => 'song.store' ]); ``` @@ -245,7 +242,7 @@ Go to `/songs/create`; above code will generate this html: ``` -### Contributing +## Contributing Project follows [PSR-2](http://www.php-fig.org/psr/psr-2/) standard and it's covered with PHPUnit tests. Pull requests should include tests and pass [Travis CI](https://travis-ci.org/kristijanhusak/laravel-form-builder) build. From 593be4b4527ad05371a1aa24dcdcdc7ea69a9b4f Mon Sep 17 00:00:00 2001 From: alamcordeiro Date: Thu, 20 Apr 2017 17:25:51 -0500 Subject: [PATCH 014/194] Update Form.php Check if has custom field --- src/Kris/LaravelFormBuilder/Form.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Kris/LaravelFormBuilder/Form.php b/src/Kris/LaravelFormBuilder/Form.php index ab960ccc..7a1f6842 100644 --- a/src/Kris/LaravelFormBuilder/Form.php +++ b/src/Kris/LaravelFormBuilder/Form.php @@ -430,6 +430,17 @@ public function has($name) { return array_key_exists($name, $this->fields); } + + /** + * Check if form has custom field + * + * @param $name + * @return bool + */ + public function hasCustom($name) + { + return array_key_exists($name, $this->formHelper->customTypes); + } /** * Get all form options. @@ -691,6 +702,10 @@ public function getFormHelper() */ public function addCustomField($name, $class) { + if ($this->rebuilding && $this->hasCustom($name)) { + return $this; + } + $this->formHelper->addCustomField($name, $class); } From 7b6a7b1452d027d5f02bfb9ca458299f3ea5329d Mon Sep 17 00:00:00 2001 From: alamcordeiro Date: Thu, 20 Apr 2017 17:28:07 -0500 Subject: [PATCH 015/194] Update FormHelper.php turn customTypes public --- src/Kris/LaravelFormBuilder/FormHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/FormHelper.php b/src/Kris/LaravelFormBuilder/FormHelper.php index 6fe9681d..01c6661f 100644 --- a/src/Kris/LaravelFormBuilder/FormHelper.php +++ b/src/Kris/LaravelFormBuilder/FormHelper.php @@ -86,7 +86,7 @@ class FormHelper * * @var array */ - private $customTypes = []; + public $customTypes = []; /** * @param View $view From ff8e37b6c135b951bc80d5b21cf7e15563eac970 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 4 May 2017 22:13:34 +0100 Subject: [PATCH 016/194] Adds form_fields helper --- src/helpers.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/helpers.php b/src/helpers.php index 0a0617a0..51d5208d 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -92,3 +92,12 @@ function form_errors(FormField $formField, array $options = []) } } + +if (!function_exists('form_fields')) { + + function form_fields(Form $form, array $options = []) + { + return $form->renderForm($options, false, true, false); + } + +} From 74f937efc601cae6cc7c0a1e65b5231e06555091 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sun, 28 May 2017 21:37:34 +0200 Subject: [PATCH 017/194] Move custom field check to form helper, extract setters to own method, use extracted method for validates when resolving. --- src/Kris/LaravelFormBuilder/Form.php | 15 +----- src/Kris/LaravelFormBuilder/FormBuilder.php | 52 ++++++++++--------- .../FormBuilderServiceProvider.php | 10 ++-- src/Kris/LaravelFormBuilder/FormHelper.php | 16 ++++-- tests/FormBuilderValidationTest.php | 6 ++- 5 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/Form.php b/src/Kris/LaravelFormBuilder/Form.php index 7a1f6842..cf1493fc 100644 --- a/src/Kris/LaravelFormBuilder/Form.php +++ b/src/Kris/LaravelFormBuilder/Form.php @@ -430,17 +430,6 @@ public function has($name) { return array_key_exists($name, $this->fields); } - - /** - * Check if form has custom field - * - * @param $name - * @return bool - */ - public function hasCustom($name) - { - return array_key_exists($name, $this->formHelper->customTypes); - } /** * Get all form options. @@ -702,10 +691,10 @@ public function getFormHelper() */ public function addCustomField($name, $class) { - if ($this->rebuilding && $this->hasCustom($name)) { + if ($this->rebuilding && $this->formHelper->hasCustomField($name)) { return $this; } - + $this->formHelper->addCustomField($name, $class); } diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index 66eb25f6..e3d94140 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -60,15 +60,7 @@ public function create($formClass, array $options = [], array $data = []) ); } - $form = $this->container - ->make($class) - ->addData($data) - ->setRequest($this->container->make('request')) - ->setFormHelper($this->formHelper) - ->setEventDispatcher($this->eventDispatcher) - ->setFormBuilder($this) - ->setValidator($this->container->make('validator')) - ->setFormOptions($options); + $form = $this->setDependenciesAndOptions($this->container->make($class), $options, $data); $form->buildForm(); @@ -85,15 +77,11 @@ public function create($formClass, array $options = [], array $data = []) */ public function createByArray($items, array $options = [], array $data = []) { - $form = $this->container - ->make($this->plainFormClass) - ->addData($data) - ->setRequest($this->container->make('request')) - ->setFormHelper($this->formHelper) - ->setEventDispatcher($this->eventDispatcher) - ->setFormBuilder($this) - ->setValidator($this->container->make('validator')) - ->setFormOptions($options); + $form = $this->setDependenciesAndOptions( + $this->container->make($this->plainFormClass), + $options, + $data + ); $this->buildFormByArray($form, $items); @@ -173,8 +161,28 @@ public function setFormClass($class) { */ public function plain(array $options = [], array $data = []) { - $form = $this->container - ->make($this->plainFormClass) + $form = $this->setDependenciesAndOptions( + $this->container->make($this->plainFormClass), + $options, + $data + ); + + $this->eventDispatcher->fire(new AfterFormCreation($form)); + + return $form; + } + + /** + * Set depedencies and options on existing form instance + * + * @param \Kris\LaravelFormBuilder\Form $instance + * @param array $options + * @param array $data + * @return \Kris\LaravelFormBuilder\Form + */ + public function setDependenciesAndOptions($instance, array $options = [], array $data = []) + { + return $instance ->addData($data) ->setRequest($this->container->make('request')) ->setFormHelper($this->formHelper) @@ -182,9 +190,5 @@ public function plain(array $options = [], array $data = []) ->setFormBuilder($this) ->setValidator($this->container->make('validator')) ->setFormOptions($options); - - $this->eventDispatcher->fire(new AfterFormCreation($form)); - - return $form; } } diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index 5281d4d1..aff647b9 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -42,13 +42,9 @@ public function register() $request = $app->make('request'); if (in_array(ValidatesWhenResolved::class, class_uses($object)) && $request->method() !== 'GET') { - $object->setEventDispatcher($app->make('events')); - $object->setFormHelper($app->make('laravel-form-helper')); - $object->setRequest($request); - $object->setFormBuilder($app->make('laravel-form-builder')); - $object->setValidator($app->make('validator')); - $object->buildForm(); - $object->redirectIfNotValid(); + $form = $app->make('laravel-form-builder')->setDependenciesAndOptions($object); + $form->buildForm(); + $form->redirectIfNotValid(); } }); } diff --git a/src/Kris/LaravelFormBuilder/FormHelper.php b/src/Kris/LaravelFormBuilder/FormHelper.php index 01c6661f..4cb11755 100644 --- a/src/Kris/LaravelFormBuilder/FormHelper.php +++ b/src/Kris/LaravelFormBuilder/FormHelper.php @@ -86,7 +86,7 @@ class FormHelper * * @var array */ - public $customTypes = []; + private $customTypes = []; /** * @param View $view @@ -145,7 +145,7 @@ public function getFieldType($type) throw new \InvalidArgumentException('Field type must be provided.'); } - if (array_key_exists($type, $this->customTypes)) { + if ($this->hasCustomField($type)) { return $this->customTypes[$type]; } @@ -196,7 +196,7 @@ public function prepareAttributes($options) */ public function addCustomField($name, $class) { - if (!array_key_exists($name, $this->customTypes)) { + if (!$this->hasCustomField($name)) { return $this->customTypes[$name] = $class; } @@ -217,6 +217,16 @@ private function loadCustomTypes() } } + /** + * Check if custom field with provided name exists + * @param string $name + * @return boolean + */ + public function hasCustomField($name) + { + return array_key_exists($name, $this->customTypes); + } + /** * @param object $model * @return object|null diff --git a/tests/FormBuilderValidationTest.php b/tests/FormBuilderValidationTest.php index 18467d99..fcaf0713 100644 --- a/tests/FormBuilderValidationTest.php +++ b/tests/FormBuilderValidationTest.php @@ -13,7 +13,9 @@ class FormBuilderValidationTest extends FormBuilderTestCase public function setUp() { parent::setUp(); - $this->app->make('Illuminate\Contracts\Http\Kernel')->pushMiddleware('Illuminate\Session\Middleware\StartSession'); + $this->app + ->make('Illuminate\Contracts\Http\Kernel') + ->pushMiddleware('Illuminate\Session\Middleware\StartSession'); } public function testItValidatesWhenResolved() @@ -33,4 +35,4 @@ public function testItDoesNotValidateGetRequests() ->assertStatus(200); } } -} \ No newline at end of file +} From 772f3db2004c4dc9b356eaf49ad6ff48ee75fb7a Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sun, 28 May 2017 22:01:06 +0200 Subject: [PATCH 018/194] Update readme with new version changelog. --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0799c83d..6d09fe15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.12.0 +- Add `createByArray` to Form builder form building forms with simple array - #316 (Thanks to [@saeidraei](https://github.com/saeidraei)) +- Add ability to automatically validate form classes when they are instantiated by adding ValidatesWhenResolved trait - #345 (Thanks to [@mpociot](https://github.com/mpociot)) +- Allow configuring plain form class - #319 (Thanks to [@rudiedirkx](https://github.com/rudiedirkx)) +- Allow creating custom validation rules parser - #345 (Thanks to [@rudiedirkx](https://github.com/rudiedirkx)) +- Use primary key as default property_key for EntityType field - #334 (Thanks to Thanks to [@pimlie](https://github.com/pimlie)) +- Check if custom field already defined on rebuild form - #348 (Thanks to [@alamcordeiro](https://github.com/alamcordeiro)) +- Fix child models not being bound correctly in collection forms - #325 (Thanks to [@njbarrett](https://github.com/njbarrett)) +- Fix passing `choice_options` from view - #336 - (Thanks to Thanks to [@schursin](https://github.com/schursin)) +- Fix ButtonGroupType having wrong template - #344 (Thanks to [@jayjfletcher](https://github.com/jayjfletcher)) +- Fix CollectionType using request's `get()` instead of `input()` method - #346 (Thanks to [@unfalln](https://github.com/unfalln)) + ## 1.10.0 - Add `buttongroup` field type - #298 (Thanks to [@noxify](https://github.com/noxify)) - Allow custom `id` and `for` attributes for a field - #285 From f0f0757107ca079a567a426451d9348e03c18733 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sun, 28 May 2017 22:03:10 +0200 Subject: [PATCH 019/194] Update dev master branch alias version. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cc3b2b18..628864f4 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.6.x-dev" + "dev-master": "1.12.x-dev" } }, "autoload": { From 97eb09512b31cb41787e315ef2f4f6a6137c5337 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Mon, 29 May 2017 10:16:44 +0200 Subject: [PATCH 020/194] Fix #354. --- CHANGELOG.md | 3 +++ src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d09fe15..6b963438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.12.1 +- Fix issue #354 + ## 1.12.0 - Add `createByArray` to Form builder form building forms with simple array - #316 (Thanks to [@saeidraei](https://github.com/saeidraei)) - Add ability to automatically validate form classes when they are instantiated by adding ValidatesWhenResolved trait - #345 (Thanks to [@mpociot](https://github.com/mpociot)) diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index aff647b9..23b919ae 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -8,6 +8,7 @@ use Illuminate\Support\ServiceProvider; use Illuminate\Foundation\Application; use Kris\LaravelFormBuilder\Traits\ValidatesWhenResolved; +use Kris\LaravelFormBuilder\Form; class FormBuilderServiceProvider extends ServiceProvider { @@ -38,7 +39,7 @@ public function register() $this->app->alias('laravel-form-builder', 'Kris\LaravelFormBuilder\FormBuilder'); - $this->app->resolving(function ($object, $app) { + $this->app->afterResolving(Form::class, function ($object, $app) { $request = $app->make('request'); if (in_array(ValidatesWhenResolved::class, class_uses($object)) && $request->method() !== 'GET') { From a2923c23b40fb1793fd8ba1b8f612f6903775519 Mon Sep 17 00:00:00 2001 From: pimlie Date: Tue, 30 May 2017 15:39:55 +0200 Subject: [PATCH 021/194] ParentType not pushing options to children The ParentType FormField's only copies the options supplied on FormField creation to their children. If you call 'setOption/setOptions' after creating e.g. an 'EntityField' the option is never passed to the child. This commit changes that behaviour by calling 'setOption/setOptions' on all children as well. See added test in EntityType for an use-case. --- .../LaravelFormBuilder/Fields/ParentType.php | 24 +++++++++++++++ tests/Fields/EntityTypeTest.php | 30 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/Kris/LaravelFormBuilder/Fields/ParentType.php b/src/Kris/LaravelFormBuilder/Fields/ParentType.php index 4a283bd0..e963e915 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ParentType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ParentType.php @@ -93,6 +93,30 @@ public function removeChild($key) return $this; } + /** + * @inheritdoc + */ + public function setOption($name, $value) + { + parent::setOption($name, $value); + + foreach ((array) $this->children as $key => $child) { + $this->children[$key]->setOption($name, $value); + } + } + + /** + * @inheritdoc + */ + public function setOptions($options) + { + parent::setOptions($options); + + foreach ((array) $this->children as $key => $child) { + $this->children[$key]->setOptions($options); + } + } + /** * @inheritdoc */ diff --git a/tests/Fields/EntityTypeTest.php b/tests/Fields/EntityTypeTest.php index 29713132..257c66f3 100644 --- a/tests/Fields/EntityTypeTest.php +++ b/tests/Fields/EntityTypeTest.php @@ -78,6 +78,36 @@ public function it_uses_query_builder_to_filter_choices() $this->assertEquals($expected, $choice->getOption('choices')); } + + /** @test */ + public function options_are_passed_to_the_children() + { + $mdl = new DummyModel(); + $options = [ + 'class' => 'DummyModel' + ]; + + $choice = new EntityType('entity_choice', 'entity', $this->plainForm, $options); + $choice->setOption('attr.data-key', 'value'); + + $field = $choice->render(); + + $expectedField = '
+ + + + + + + + + + + +
'; + + $this->assertEquals(trim($field), $expectedField); + } } class DummyModel { From 53776638e3fdc1c53565ca013dd262439f47d391 Mon Sep 17 00:00:00 2001 From: Oliver Brunsmann Date: Sun, 30 Jul 2017 10:51:26 +0200 Subject: [PATCH 022/194] Update FormBuilderServiceProvider.php --- src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index 23b919ae..cd3e6ad0 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -124,7 +124,7 @@ private function registerFormIfHeeded() $form = new LaravelForm($app[ 'html' ], $app[ 'url' ], $app[ 'session.store' ]->getToken()); } else { - $form = new LaravelForm($app['html'], $app['url'], $app['view'], $app['session.store']->getToken()); + $form = new LaravelForm($app['html'], $app['url'], $app['view'], $app['session.store']->token()); } return $form->setSessionStore($app['session.store']); From 2b855cc0bb303a47e4bc21b62001911ab623481a Mon Sep 17 00:00:00 2001 From: Oliver Brunsmann Date: Sun, 30 Jul 2017 10:54:05 +0200 Subject: [PATCH 023/194] Update FormBuilderServiceProvider.php --- src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index cd3e6ad0..6c9fe99c 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -121,7 +121,7 @@ private function registerFormIfHeeded() $form = new LaravelForm($app[ 'html' ], $app[ 'url' ], $app[ 'view' ], $app[ 'session.store' ]->token()); } else if (str_is('5.0', $version) || str_is('5.1', $version)) { - $form = new LaravelForm($app[ 'html' ], $app[ 'url' ], $app[ 'session.store' ]->getToken()); + $form = new LaravelForm($app[ 'html' ], $app[ 'url' ], $app[ 'session.store' ]->token()); } else { $form = new LaravelForm($app['html'], $app['url'], $app['view'], $app['session.store']->token()); From 62d3d8716bd6fee8b61ccbc77a8f41479a73aa4c Mon Sep 17 00:00:00 2001 From: Djordje Stojiljkovic Date: Mon, 28 Aug 2017 00:13:58 +0200 Subject: [PATCH 024/194] [NEW][Feature] Added support for field filters and request input values mutating --- .../Events/AfterFormCreation.php | 13 +- .../LaravelFormBuilder/Fields/FormField.php | 158 +++++++++++++++++- .../Filters/Collection/StringToUpper.php | 33 ++++ .../Filters/Collection/StringTrim.php | 32 ++++ .../FilterAlreadyBindedException.php | 21 +++ .../Exception/InvalidInstanceException.php | 21 +++ .../UnableToResolveFilterException.php | 20 +++ .../Filters/FilterInterface.php | 32 ++++ .../Filters/FilterResolver.php | 84 ++++++++++ src/Kris/LaravelFormBuilder/Form.php | 77 ++++++++- 10 files changed, 487 insertions(+), 4 deletions(-) create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Exception/FilterAlreadyBindedException.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/FilterInterface.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/FilterResolver.php diff --git a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php index 6c2d30c3..ccc6dd65 100644 --- a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php +++ b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php @@ -16,11 +16,12 @@ class AfterFormCreation /** * Create a new after form creation instance. * - * @param Form $form + * @param Form $form * @return void */ public function __construct(Form $form) { $this->form = $form; + $this->filterFields(); } /** @@ -31,4 +32,14 @@ public function __construct(Form $form) { public function getForm() { return $this->form; } + + /** + * Init filter field process on Form creation. + * + * @return void + */ + public function filterFields() + { + $this->form->filterFields(); + } } diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index fd5a9a89..9bed65ad 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -2,6 +2,9 @@ namespace Kris\LaravelFormBuilder\Fields; +use Kris\LaravelFormBuilder\Filters\Exception\FilterAlreadyBindedException; +use Kris\LaravelFormBuilder\Filters\FilterInterface; +use Kris\LaravelFormBuilder\Filters\FilterResolver; use Kris\LaravelFormBuilder\Form; use Illuminate\Database\Eloquent\Model; use Kris\LaravelFormBuilder\FormHelper; @@ -83,6 +86,20 @@ abstract class FormField */ protected $valueClosure = null; + /** + * Array of filters key => objects. + * + * @var array + */ + protected $filters = []; + + /** + * Override filters with same alias/name for field. + * + * @var bool + */ + protected $filtersOverride = false; + /** * @param string $name * @param string $type @@ -98,6 +115,7 @@ public function __construct($name, $type, Form $parent, array $options = []) $this->setTemplate(); $this->setDefaultOptions($options); $this->setupValue(); + $this->initFilters(); } @@ -518,7 +536,6 @@ protected function addErrorClass() } } - /** * Merge all defaults with field specific defaults and set template if passed. * @@ -694,4 +711,143 @@ protected function isValidValue($value) { return $value !== null; } + + /** + * Method initFilters used to initialize filters + * from field options and bind it to the same. + * + * @return $this + */ + protected function initFilters() + { + // If override status is set in field options to true + // we will change filtersOverride property value to true + // so we can override existing filters with registered + // alias/name in addFilter method. + $overrideStatus = $this->getOption('filters_override', false); + if ($overrideStatus) { + $this->filtersOverride = true; + } + + // Get filters and bind it to field. + $filters = $this->getOption('filters', []); + foreach ($filters as $filter) { + $this->addFilter($filter); + } + + return $this; + } + + /** + * Method setFilters used to set filters to current filters property. + * + * @param array $filters + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + */ + public function setFilters(array $filters) + { + $this->clearFilters(); + foreach ($filters as $filter) { + $this->addFilter($filter); + } + + return $this; + } + + /** + * Method getFilters returns array of binded filters + * if there are any binded. Otherwise empty array. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * @param string|FilterInterface $filter + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + * + * @throws FilterAlreadyBindedException + */ + public function addFilter($filter) + { + // Resolve filter object from string, object or throw Ex. + $filterObj = FilterResolver::instance($filter); + + // If filtersOverride is allowed we will override filter + // with same alias/name if there is one with new resolved filter. + if ($this->filtersOverride) { + if ($key = array_search($filterObj->getName(), $this->getFilters())) { + $this->filters[$key] = $filterObj; + } else { + $this->filters[$filterObj->getName()] = $filterObj; + } + } else { + // If filtersOverride is disabled and we found + // equal alias defined we will throw Ex. + if (array_key_exists($filterObj->getName(), $this->getFilters())) { + $ex = new FilterAlreadyBindedException($filterObj->getName(), $this->getName()); + throw $ex; + } + + // Filter with resolvedFilter alias/name doesn't exist + // so we will bind it as new one to field. + $this->filters[$filterObj->getName()] = $filter; + } + + return $this; + } + + /** + * Method removeFilter used to remove filter by provided alias/name. + * + * @param string $name + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + */ + public function removeFilter($name) + { + $filters = $this->getFilters(); + if (array_key_exists($name, $filters)) { + unset($filters[$name]); + $this->filters = $filters; + } + + return $this; + } + + /** + * Method removeFilters used to remove filters by provided aliases/names. + * + * @param array $filterNames + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + */ + public function removeFilters(array $filterNames) + { + $filters = $this->getFilters(); + foreach ($filterNames as $filterName) { + if (array_key_exists($filterName, $filters)) { + unset($filters[$filterName]); + $this->filters = $filters; + } + } + + return $this; + } + + /** + * Method clearFilters used to empty current filters property. + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + */ + public function clearFilters() + { + $this->filters = []; + return $this; + } } diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php new file mode 100644 index 00000000..8b3ba5dd --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php @@ -0,0 +1,33 @@ + + */ +class StringToUpper implements FilterInterface +{ + /** + * @param mixed $value + * @param array $options + * @return string + */ + public function filter($value, $options = []) + { + return strtoupper((string) $value); + } + + /** + * @return string + */ + public function getName() + { + return 'StringToUpper'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php new file mode 100644 index 00000000..b8c8a93d --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php @@ -0,0 +1,32 @@ + + */ +class StringTrim implements FilterInterface +{ + /** + * @param mixed $value + * @param array $options + * @return string + */ + public function filter($value, $options = []) + { + return trim((string) $value); + } + + /** + * @return string + */ + public function getName() + { + return 'StringTrim'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Exception/FilterAlreadyBindedException.php b/src/Kris/LaravelFormBuilder/Filters/Exception/FilterAlreadyBindedException.php new file mode 100644 index 00000000..e9438098 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Exception/FilterAlreadyBindedException.php @@ -0,0 +1,21 @@ + + */ +class FilterAlreadyBindedException extends \Exception +{ + public function __construct($filter, $field) + { + $message = sprintf( + 'Filter with name: %filter already assigned for field: %field', + $filter, $field + ); + parent::__construct($message); + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php b/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php new file mode 100644 index 00000000..34fc5359 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php @@ -0,0 +1,21 @@ + + */ +class InvalidInstanceException extends \Exception +{ + public function __construct($message = "", $code = 0, Throwable $previous = null) + { + $message = 'Filter object must implement ' . FilterInterface::class; + parent::__construct($message, $code, $previous); + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php b/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php new file mode 100644 index 00000000..b8f4b5c7 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php @@ -0,0 +1,20 @@ + + */ +class UnableToResolveFilterException extends \Exception +{ + public function __construct($message = "", $code = 0, Throwable $previous = null) + { + $message = "Passed filter can't be resolved."; + parent::__construct($message, $code, $previous); + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/FilterInterface.php b/src/Kris/LaravelFormBuilder/Filters/FilterInterface.php new file mode 100644 index 00000000..3c80b88c --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/FilterInterface.php @@ -0,0 +1,32 @@ + + */ +interface FilterInterface +{ + /** + * Returns the result of filtering $value. + * + * @param mixed $value + * @param array $options + * + * @throws \Exception If filtering $value is impossible. + * + * @return mixed + */ + public function filter($value, $options = []); + + /** + * Returns the filter name so we can use this as alias for easily + * removing, adding filters. + * + * @return string + */ + public function getName(); +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php b/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php new file mode 100644 index 00000000..3e7d972e --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php @@ -0,0 +1,84 @@ + + */ +class FilterResolver +{ + /** + * Method instance used to resolve filter parameter to + * FilterInterface object from filter Alias or object itself. + * + * @param mixed $filter + * + * @return FilterInterface + * + * @throws Exception\UnableToResolveFilterException + * @throws Exception\InvalidInstanceException + */ + public static function instance($filter) + { + if (is_string($filter)) { + if (class_exists($filter)) { + $filter = new $filter(); + self::validateFilterInstance($filter); + } elseif ($filter = FilterResolver::resolveFromCollection($filter)) { + self::validateFilterInstance($filter); + } else { + $ex = new UnableToResolveFilterException(); + throw $ex; + } + } elseif (self::validateFilterInstance($filter)) { + return $filter; + } else { + $ex = new UnableToResolveFilterException(); + throw $ex; + } + + return $filter; + } + + /** + * @param $filter + * + * @throws \Exception + * + * @return mixed + */ + private static function validateFilterInstance($filter) + { + if (!$filter instanceof FilterInterface) { + $ex = new InvalidInstanceException(); + throw $ex; + } + return true; + } + + /** + * @param $filterName + * @return FilterInterface|null + */ + public static function resolveFromCollection($filterName) + { + $filterClass = self::getCollectionNamespace() . $filterName; + if (class_exists($filterClass)) { + return new $filterClass; + } + } + + /** + * @return string + */ + public static function getCollectionNamespace() + { + return "\\Kris\\LaravelFormBuilder\\Filters\\Collection\\"; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Form.php b/src/Kris/LaravelFormBuilder/Form.php index cf1493fc..8b5ed785 100644 --- a/src/Kris/LaravelFormBuilder/Form.php +++ b/src/Kris/LaravelFormBuilder/Form.php @@ -12,6 +12,7 @@ use Kris\LaravelFormBuilder\Events\AfterFormValidation; use Kris\LaravelFormBuilder\Events\BeforeFormValidation; use Kris\LaravelFormBuilder\Fields\FormField; +use Kris\LaravelFormBuilder\Filters\FilterResolver; class Form { @@ -122,6 +123,13 @@ class Form */ protected $languageName; + /** + * To filter and mutate request values or not. + * + * @var bool + */ + protected $lockFiltering = false; + /** * Build the form. * @@ -995,7 +1003,6 @@ protected function setupNamedModel() return false; } - /** * Set form builder instance on helper so we can use it later. * @@ -1054,7 +1061,6 @@ public function exclude(array $fields) return $this; } - /** * If form is named form, modify names to be contained in single key (parent[child_field_name]). * @@ -1266,4 +1272,71 @@ protected function fieldDoesNotExist($name) { throw new \InvalidArgumentException('Field ['.$name.'] does not exist in '.get_class($this)); } + + /** + * Method filterFields used as *Main* method for starting + * filtering and request field mutating process. + * + * @return \Kris\LaravelFormBuilder\Form + */ + public function filterFields() + { + // If filtering is unlocked/allowed we can start with filtering process. + if (!$this->isFilteringLocked()) { + // Init required vars. + $filters = $this->getFilters(); + $request = $this->getRequest(); + if (!empty($filters)) { + foreach ($filters as $field => $fieldFilters) { + // If field exist in request object, try to mutate/filter + // it to filtered value if there is one. + if (array_key_exists($field, $request->all())) { + foreach ($fieldFilters as $filter) { + $filterObj = FilterResolver::instance($filter); + $request[$field] = $filterObj->filter($request[$field]); + } + } + } + } + + } + } + + /** + * Method getFilters used to return array of all binded filters to form fields. + * + * @return array + */ + public function getFilters() + { + $filters = []; + foreach ($this->fields as $field) { + $filters[$field->getName()] = $field->getFilters(); + } + + return $filters; + } + + /** + * If lockFiltering is set to true then we will not + * filter fields and mutate request data binded to fields. + * + * @return \Kris\LaravelFormBuilder\Form + */ + public function lockFiltering() + { + $this->lockFiltering = true; + return $this; + } + + /** + * Method isFilteringLocked used to check + * if current filteringLocked property status is set to true. + * + * @return bool + */ + public function isFilteringLocked() + { + return !$this->lockFiltering ? false : true; + } } From 4bf12393e0a65408d92ed1e977aeb71ae3a9e270 Mon Sep 17 00:00:00 2001 From: Djordje Stojiljkovic Date: Tue, 29 Aug 2017 01:15:21 +0200 Subject: [PATCH 025/194] Wrote initial tests, added unlockFiltering method and [CS] changes --- .../Events/AfterFormCreation.php | 11 -- .../LaravelFormBuilder/Fields/FormField.php | 33 ++++-- .../Filters/FilterResolver.php | 32 +++--- src/Kris/LaravelFormBuilder/Form.php | 17 ++- src/Kris/LaravelFormBuilder/FormBuilder.php | 6 + tests/Fields/FormFieldTest.php | 103 ++++++++++++++++++ tests/Filters/FilterResolverTest.php | 56 ++++++++++ tests/FormBuilderTestCase.php | 10 +- tests/FormTest.php | 56 ++++++++++ 9 files changed, 286 insertions(+), 38 deletions(-) create mode 100644 tests/Filters/FilterResolverTest.php diff --git a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php index ccc6dd65..0312e2bc 100644 --- a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php +++ b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php @@ -21,7 +21,6 @@ class AfterFormCreation */ public function __construct(Form $form) { $this->form = $form; - $this->filterFields(); } /** @@ -32,14 +31,4 @@ public function __construct(Form $form) { public function getForm() { return $this->form; } - - /** - * Init filter field process on Form creation. - * - * @return void - */ - public function filterFields() - { - $this->form->filterFields(); - } } diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index 9bed65ad..f51def98 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -6,9 +6,7 @@ use Kris\LaravelFormBuilder\Filters\FilterInterface; use Kris\LaravelFormBuilder\Filters\FilterResolver; use Kris\LaravelFormBuilder\Form; -use Illuminate\Database\Eloquent\Model; use Kris\LaravelFormBuilder\FormHelper; -use Illuminate\Database\Eloquent\Collection; /** * Class FormField @@ -87,7 +85,7 @@ abstract class FormField protected $valueClosure = null; /** - * Array of filters key => objects. + * Array of filters key(alias/name) => objects. * * @var array */ @@ -726,7 +724,7 @@ protected function initFilters() // alias/name in addFilter method. $overrideStatus = $this->getOption('filters_override', false); if ($overrideStatus) { - $this->filtersOverride = true; + $this->setFiltersOverride(true); } // Get filters and bind it to field. @@ -775,12 +773,12 @@ public function getFilters() */ public function addFilter($filter) { - // Resolve filter object from string, object or throw Ex. + // Resolve filter object from string/object or throw Ex. $filterObj = FilterResolver::instance($filter); // If filtersOverride is allowed we will override filter // with same alias/name if there is one with new resolved filter. - if ($this->filtersOverride) { + if ($this->getFiltersOverride()) { if ($key = array_search($filterObj->getName(), $this->getFilters())) { $this->filters[$key] = $filterObj; } else { @@ -796,7 +794,7 @@ public function addFilter($filter) // Filter with resolvedFilter alias/name doesn't exist // so we will bind it as new one to field. - $this->filters[$filterObj->getName()] = $filter; + $this->filters[$filterObj->getName()] = $filterObj; } return $this; @@ -850,4 +848,25 @@ public function clearFilters() $this->filters = []; return $this; } + + /** + * Method used to set FiltersOverride status to provided value. + * + * @param $status + * + * @return \Kris\LaravelFormBuilder\Fields\FormField + */ + public function setFiltersOverride($status) + { + $this->filtersOverride = $status; + return $this; + } + + /** + * @return bool + */ + public function getFiltersOverride() + { + return $this->filtersOverride; + } } diff --git a/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php b/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php index 3e7d972e..7ab8e4d7 100644 --- a/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php +++ b/src/Kris/LaravelFormBuilder/Filters/FilterResolver.php @@ -26,24 +26,20 @@ class FilterResolver */ public static function instance($filter) { - if (is_string($filter)) { - if (class_exists($filter)) { - $filter = new $filter(); - self::validateFilterInstance($filter); - } elseif ($filter = FilterResolver::resolveFromCollection($filter)) { - self::validateFilterInstance($filter); - } else { - $ex = new UnableToResolveFilterException(); - throw $ex; - } - } elseif (self::validateFilterInstance($filter)) { - return $filter; - } else { - $ex = new UnableToResolveFilterException(); - throw $ex; + if (!is_string($filter)) { + return self::validateFilterInstance($filter); } - return $filter; + if (class_exists($filter)) { + return self::validateFilterInstance(new $filter()); + } + + if ($filter = FilterResolver::resolveFromCollection($filter)) { + return self::validateFilterInstance($filter); + } + + $ex = new UnableToResolveFilterException(); + throw $ex; } /** @@ -59,11 +55,13 @@ private static function validateFilterInstance($filter) $ex = new InvalidInstanceException(); throw $ex; } - return true; + + return $filter; } /** * @param $filterName + * * @return FilterInterface|null */ public static function resolveFromCollection($filterName) diff --git a/src/Kris/LaravelFormBuilder/Form.php b/src/Kris/LaravelFormBuilder/Form.php index 8b5ed785..5f2e7242 100644 --- a/src/Kris/LaravelFormBuilder/Form.php +++ b/src/Kris/LaravelFormBuilder/Form.php @@ -1286,6 +1286,7 @@ public function filterFields() // Init required vars. $filters = $this->getFilters(); $request = $this->getRequest(); + if (!empty($filters)) { foreach ($filters as $field => $fieldFilters) { // If field exist in request object, try to mutate/filter @@ -1298,8 +1299,9 @@ public function filterFields() } } } - } + + return $this; } /** @@ -1310,7 +1312,7 @@ public function filterFields() public function getFilters() { $filters = []; - foreach ($this->fields as $field) { + foreach ($this->getFields() as $field) { $filters[$field->getName()] = $field->getFilters(); } @@ -1329,6 +1331,17 @@ public function lockFiltering() return $this; } + /** + * Unlock fields filtering/mutating. + * + * @return \Kris\LaravelFormBuilder\Form + */ + public function unlockFiltering() + { + $this->lockFiltering = false; + return $this; + } + /** * Method isFilteringLocked used to check * if current filteringLocked property status is set to true. diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index e3d94140..7030e883 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -66,6 +66,8 @@ public function create($formClass, array $options = [], array $data = []) $this->eventDispatcher->fire(new AfterFormCreation($form)); + $form->filterFields(); + return $form; } @@ -87,6 +89,8 @@ public function createByArray($items, array $options = [], array $data = []) $this->eventDispatcher->fire(new AfterFormCreation($form)); + $form->filterFields(); + return $form; } @@ -169,6 +173,8 @@ public function plain(array $options = [], array $data = []) $this->eventDispatcher->fire(new AfterFormCreation($form)); + $form->filterFields(); + return $form; } diff --git a/tests/Fields/FormFieldTest.php b/tests/Fields/FormFieldTest.php index 13995ea5..90a85cbc 100644 --- a/tests/Fields/FormFieldTest.php +++ b/tests/Fields/FormFieldTest.php @@ -219,4 +219,107 @@ public function it_fallbacks_to_simple_format_if_no_translation_and_custom_label $customPlainForm->custom->getOption('label') ); } + + /** @test */ + public function it_initialize_all_defined_field_filters() + { + $customPlainForm = $this->formBuilder->plain(); + + $filters = ['StringTrim', 'StringToUpper']; + $customPlainForm->add('test_field', 'text', [ + 'filters' => $filters + ]); + + $testField = $customPlainForm->getField('test_field'); + + foreach ($filters as $filterName) { + $this->assertArrayHasKey($filterName, $testField->getFilters()); + } + } + + /** @test */ + public function it_enables_overriding_existing_filters() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->add('test_field', 'text', [ + 'filters_override' => true + ]); + + $testField = $customPlainForm->getField('test_field'); + $this->assertTrue( + $testField->getFiltersOverride() + ); + } + + /** + * @test + * @expectedException \Kris\LaravelFormBuilder\Filters\Exception\FilterAlreadyBindedException + */ + public function it_throws_an_exception_if_filters_override_is_false_but_passed_already_binded_filter() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->add('test_field', 'text', [ + 'filters' => ['StringTrim'] + ]); + + $testField = $customPlainForm->getField('test_field'); + $testField->addFilter('StringTrim'); + } + + /** @test */ + public function it_overrides_already_existing_filter() + { + $customPlainForm = $this->formBuilder->plain(); + $filter = 'StringTrim'; + $customPlainForm->add('test_field', 'text', [ + 'filters' => [$filter], + 'filters_override' => true + ]); + + // TODO: Find out for mocking object or stubing new with same alias/name but different implementation. + $testField = $customPlainForm->getField('test_field'); + $testField->addFilter($filter); + $this->assertArrayHasKey($filter, $testField->getFilters()); + } + + /** @test */ + public function it_removes_binded_filter() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->add('test_field', 'text', [ + 'filters' => ['StringTrim', 'StringToUpper'] + ]); + + $testField = $customPlainForm->getField('test_field'); + $testField->removeFilter('StringTrim'); + $this->assertTrue(count($testField->getFilters()) == 1); + $this->assertArrayHasKey('StringToUpper', $testField->getFilters()); + } + + /** @test */ + public function it_removes_multiple_filters() + { + $customPlainForm = $this->formBuilder->plain(); + $filters = ['StringTrim', 'StringToUpper']; + $customPlainForm->add('test_field', 'text', [ + 'filters' => $filters + ]); + + $testField = $customPlainForm->getField('test_field'); + $testField->removeFilters($filters); + $this->assertEmpty($testField->getFilters()); + } + + /** @test */ + public function it_clears_all_filters() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->add('test_field', 'text', [ + 'filters' => ['StringTrim', 'StringToUpper'] + ]); + + $testField = $customPlainForm->getField('test_field'); + $testField->clearFilters(); + $this->assertEmpty($testField->getFilters()); + } } diff --git a/tests/Filters/FilterResolverTest.php b/tests/Filters/FilterResolverTest.php new file mode 100644 index 00000000..f1522c77 --- /dev/null +++ b/tests/Filters/FilterResolverTest.php @@ -0,0 +1,56 @@ +filtersResolver; + $this->assertInstanceOf( + $expected, $resolver::instance('StringTrim') + ); + } + + /** @test */ + public function it_resolve_object_based_filter() + { + $expected = \Kris\LaravelFormBuilder\Filters\FilterInterface::class; + $filterObj = new StringTrim(); + $resolver = $this->filtersResolver; + + $resolvedFilterObj = $resolver::instance($filterObj); + + $this->assertInstanceOf( + $expected, $filterObj + ); + + $this->assertEquals( + $filterObj, $resolvedFilterObj + ); + } + + /** + * @test + * @expectedException \Kris\LaravelFormBuilder\Filters\Exception\InvalidInstanceException + */ + public function it_throws_an_exception_if_object_is_not_instance_of_filterinterface() + { + $invalidFilterObj = new stdClass(); + $resolver = $this->filtersResolver; + $resolver::instance($invalidFilterObj); + } + + /** + * @test + * @expectedException \Kris\LaravelFormBuilder\Filters\Exception\UnableToResolveFilterException + */ + public function it_throws_an_exception_if_filter_cant_be_resolved() + { + $invalidFilterClass = "\\Test\\Not\\Existing\\Class\\"; + $resolver = $this->filtersResolver; + $resolver::instance($invalidFilterClass); + } +} \ No newline at end of file diff --git a/tests/FormBuilderTestCase.php b/tests/FormBuilderTestCase.php index e4e52348..0fb25a64 100644 --- a/tests/FormBuilderTestCase.php +++ b/tests/FormBuilderTestCase.php @@ -2,12 +2,12 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Validation\Factory; -use Illuminate\Contracts\Validation\Validator; use Kris\LaravelFormBuilder\FormBuilder; use Kris\LaravelFormBuilder\FormHelper; use Kris\LaravelFormBuilder\Form; use Orchestra\Testbench\TestCase; use Illuminate\Database\Eloquent\Model; +use Kris\LaravelFormBuilder\Filters\FilterResolver; class TestModel extends Model { protected $fillable = ['m', 'f']; @@ -70,6 +70,11 @@ abstract class FormBuilderTestCase extends TestCase { */ protected $plainForm; + /** + * @var FilterResolver $filtersResolver + */ + protected $filtersResolver; + public function setUp() { parent::setUp(); @@ -87,6 +92,8 @@ public function setUp() $this->formBuilder = new FormBuilder($this->app, $this->formHelper, $this->eventDispatcher); $this->plainForm = $this->formBuilder->plain(); + + $this->filtersResolver = new FilterResolver(); } public function tearDown() @@ -99,6 +106,7 @@ public function tearDown() $this->formHelper = null; $this->formBuilder = null; $this->plainForm = null; + $this->filtersResolver = null; } protected function getDefaults($attr = [], $label = '', $defaultValue = null, $helpText = null) diff --git a/tests/FormTest.php b/tests/FormTest.php index 7214099d..d6eb4da5 100644 --- a/tests/FormTest.php +++ b/tests/FormTest.php @@ -1054,4 +1054,60 @@ public function it_uses_the_template_prefix() $form->setFormHelper($helper); $form->renderForm(); } + + /** @test */ + public function it_locks_filtering() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->lockFiltering(); + + $this->assertTrue( + $customPlainForm->isFilteringLocked() + ); + } + + /** @test */ + public function it_returns_binded_field_filters() + { + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm + ->add('test_field', 'text', [ + 'filters' => ['StringTrim', 'StringToUpper'] + ]) + ->add('test_field2', 'text', [ + 'filters' => ['StringToUpper'] + ]) + ; + + $expected = [ + 'test_field' => [ + 'StringTrim' => new \Kris\LaravelFormBuilder\Filters\Collection\StringTrim(), + 'StringToUpper' => new \Kris\LaravelFormBuilder\Filters\Collection\StringToUpper() + ], + 'test_field2' => [ + 'StringToUpper' => new \Kris\LaravelFormBuilder\Filters\Collection\StringToUpper() + ] + ]; + + $bindedFields = $customPlainForm->getFilters(); + + $this->assertEquals( + $expected, $bindedFields + ); + } + + /** @test */ + public function it_filter_and_mutate_fields_request_values() + { + $toMutateValue = ' test '; + $this->request['test_field'] = $toMutateValue; + + $customPlainForm = $this->formBuilder->plain(); + $customPlainForm->add('test_field', 'text', [ + 'filters' => ['StringTrim', 'StringToUpper'] + ]); + $customPlainForm->filterFields(); + + $this->assertEquals('TEST', $this->request['test_field']); + } } From 34bf8c8dc23bf2336a3d7cfa9b9c70a122424547 Mon Sep 17 00:00:00 2001 From: Djordje Stojiljkovic Date: Wed, 30 Aug 2017 00:36:19 +0200 Subject: [PATCH 026/194] Added new Filters to Collection --- .../Filters/Collection/BaseName.php | 34 +++ .../Filters/Collection/HtmlEntities.php | 196 +++++++++++++ .../Filters/Collection/Integer.php | 34 +++ .../Filters/Collection/PregReplace.php | 116 ++++++++ .../Filters/Collection/StringToLower.php | 99 +++++++ .../Filters/Collection/StringToUpper.php | 64 ++++- .../Filters/Collection/StringTrim.php | 68 ++++- .../Filters/Collection/StripNewlines.php | 33 +++ .../Filters/Collection/StripTags.php | 262 ++++++++++++++++++ .../Filters/Collection/XSS.php | 136 +++++++++ 10 files changed, 1039 insertions(+), 3 deletions(-) create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/BaseName.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/HtmlEntities.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/Integer.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/PregReplace.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/StringToLower.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/StripTags.php create mode 100644 src/Kris/LaravelFormBuilder/Filters/Collection/XSS.php diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/BaseName.php b/src/Kris/LaravelFormBuilder/Filters/Collection/BaseName.php new file mode 100644 index 00000000..b2b1e5c6 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/BaseName.php @@ -0,0 +1,34 @@ + + */ +class BaseName implements FilterInterface +{ + /** + * @param string $value + * @param array $options + * + * @return string + */ + public function filter($value, $options = []) + { + $value = (string) $value; + return basename($value); + } + + /** + * @return string + */ + public function getName() + { + return 'BaseName'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/HtmlEntities.php b/src/Kris/LaravelFormBuilder/Filters/Collection/HtmlEntities.php new file mode 100644 index 00000000..8486a040 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/HtmlEntities.php @@ -0,0 +1,196 @@ + + */ +class HtmlEntities implements FilterInterface +{ + + /** + * Second arg of htmlentities function. + * + * @var integer + */ + protected $quoteStyle; + + /** + * Third arg of htmlentities function. + * + * @var string + */ + protected $encoding; + + /** + * Fourth arg of htmlentities function. + * + * @var string + */ + protected $doubleQuote; + + /** + * HtmlEntities constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + if (!isset($options['quotestyle'])) { + $options['quotestyle'] = ENT_COMPAT; + } + + if (!isset($options['encoding'])) { + $options['encoding'] = 'UTF-8'; + } + + if (isset($options['charset'])) { + $options['encoding'] = $options['charset']; + } + + if (!isset($options['doublequote'])) { + $options['doublequote'] = true; + } + + $this->setQuoteStyle($options['quotestyle']); + $this->setEncoding($options['encoding']); + $this->setDoubleQuote($options['doublequote']); + } + + /** + * @return integer + */ + public function getQuoteStyle() + { + return $this->quoteStyle; + } + + /** + * @param integer $style + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities + */ + public function setQuoteStyle($style) + { + $this->quoteStyle = $style; + return $this; + } + + /** + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param string $encoding + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities + */ + public function setEncoding($encoding) + { + $this->encoding = (string) $encoding; + return $this; + } + + /** + * Returns the charSet property + * + * Proxies to {@link getEncoding()} + * + * @return string + */ + public function getCharSet() + { + return $this->getEncoding(); + } + + /** + * Sets the charSet property. + * + * Proxies to {@link setEncoding()}. + * + * @param string $charSet + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities + */ + public function setCharSet($charSet) + { + return $this->setEncoding($charSet); + } + + /** + * Returns the doubleQuote property. + * + * @return boolean + */ + public function getDoubleQuote() + { + return $this->doubleQuote; + } + + /** + * Sets the doubleQuote property. + * + * @param boolean $doubleQuote + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities + */ + public function setDoubleQuote($doubleQuote) + { + $this->doubleQuote = (boolean) $doubleQuote; + return $this; + } + + /** + * @param string $value + * @param array $options + * + * @return mixed + * + * @throws \Exception + */ + public function filter($value, $options = []) + { + $value = (string) $value; + $filtered = htmlentities( + $value, + $this->getQuoteStyle(), + $this->getEncoding(), + $this->getDoubleQuote() + ); + + if (strlen((string) $value) && !strlen($filtered)) { + if (!function_exists('iconv')) { + $ex = new \Exception('Encoding mismatch has resulted in htmlentities errors.'); + throw $ex; + } + + $enc = $this->getEncoding(); + $value = iconv('', $enc . '//IGNORE', (string) $value); + $filtered = htmlentities($value, $this->getQuoteStyle(), $enc, $this->getDoubleQuote()); + + if (!strlen($filtered)) { + $ex = new \Exception('Encoding mismatch has resulted in htmlentities errors.'); + throw $ex; + } + } + + return $filtered; + } + + /** + * @return string + */ + public function getName() + { + return 'HtmlEntities'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/Integer.php b/src/Kris/LaravelFormBuilder/Filters/Collection/Integer.php new file mode 100644 index 00000000..72e1d3c2 --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/Integer.php @@ -0,0 +1,34 @@ + + */ +class Integer implements FilterInterface +{ + /** + * @param mixed $value + * @param array $options + * + * @return mixed + */ + public function filter($value, $options = []) + { + $value = (int) ((string) $value); + return $value; + } + + /** + * @return string + */ + public function getName() + { + return 'Integer'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/PregReplace.php b/src/Kris/LaravelFormBuilder/Filters/Collection/PregReplace.php new file mode 100644 index 00000000..579ab0be --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/PregReplace.php @@ -0,0 +1,116 @@ + + */ +class PregReplace implements FilterInterface +{ + /** + * Pattern to match + * + * @var mixed $pattern + */ + protected $pattern = null; + + /** + * Replacement against matches. + * + * @var mixed $replacement + */ + protected $replacement = ''; + + /** + * PregReplace constructor. + * + * @param null $options + */ + public function __construct($options = null) + { + if (array_key_exists('pattern', $options)) { + $this->setPattern($options['pattern']); + } + + if (array_key_exists('replace', $options)) { + $this->setReplacement($options['replace']); + } + } + + /** + * Set the match pattern for the regex being called within filter(). + * + * @param mixed $pattern - first arg of preg_replace + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\PregReplace + */ + public function setPattern($pattern) + { + $this->pattern = $pattern; + return $this; + } + + /** + * Get currently set match pattern. + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set the Replacement pattern/string for the preg_replace called in filter. + * + * @param mixed $replacement - same as the second argument of preg_replace + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\PregReplace + */ + public function setReplacement($replacement) + { + $this->replacement = $replacement; + return $this; + } + + /** + * Get currently set replacement value. + * + * @return string + */ + public function getReplacement() + { + return $this->replacement; + } + + /** + * @param mixed $value + * @param array $options + * + * @return mixed + * + * @throws \Exception + */ + public function filter($value, $options = []) + { + if ($this->getPattern() == null) { + $ex = new \Exception(get_class($this) . ' does not have a valid MatchPattern set.'); + throw $ex; + } + + return preg_replace($this->getPattern(), $this->getReplacement(), $value); + } + + /** + * @return string + */ + public function getName() + { + return 'PregReplace'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StringToLower.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToLower.php new file mode 100644 index 00000000..190d768c --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToLower.php @@ -0,0 +1,99 @@ + + */ +class StringToLower implements FilterInterface +{ + /** + * Encoding for string input. + * + * @var string $encoding + */ + protected $encoding = null; + + /** + * StringToLower constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + if (!array_key_exists('encoding', $options) && function_exists('mb_internal_encoding')) { + $options['encoding'] = mb_internal_encoding(); + } + + if (array_key_exists('encoding', $options)) { + $this->setEncoding($options['encoding']); + } + } + + /** + * Returns current encoding. + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @param null $encoding + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\StringToLower + * + * @throws \Exception + */ + public function setEncoding($encoding = null) + { + if ($encoding !== null) { + if (!function_exists('mb_strtolower')) { + $ex = new \Exception('mbstring extension is required for value mutating.'); + throw $ex; + } + + $encoding = (string) $encoding; + if (!in_array(strtolower($encoding), array_map('strtolower', mb_list_encodings()))) { + $ex = new \Exception('The given encoding '.$encoding.' is not supported by mbstring ext.'); + throw $ex; + } + } + + $this->encoding = $encoding; + return $this; + } + + /** + * Returns the string lowercased $value. + * + * @param mixed $value + * @param array $options + * + * @return mixed + */ + public function filter($value, $options = []) + { + $value = (string) $value; + if ($this->getEncoding() !== null) { + return mb_strtolower($value, $this->getEncoding()); + } + + return strtolower($value); + } + + /** + * @return string + */ + public function getName() + { + return 'StringToLower'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php index 8b3ba5dd..03c9c533 100644 --- a/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StringToUpper.php @@ -4,7 +4,6 @@ use Kris\LaravelFormBuilder\Filters\FilterInterface; - /** * Class StringTrim * @@ -13,14 +12,75 @@ */ class StringToUpper implements FilterInterface { + /** + * @var string $encoding + */ + protected $encoding = null; + + /** + * StringToUpper constructor. + * + * @param null $options + */ + public function __construct($options = null) + { + if (!array_key_exists('encoding', $options) && function_exists('mb_internal_encoding')) { + $options['encoding'] = mb_internal_encoding(); + } + + if (array_key_exists('encoding', $options)) { + $this->setEncoding($options['encoding']); + } + } + + /** + * @param null $encoding + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\StringToUpper + * + * @throws \Exception + */ + public function setEncoding($encoding) + { + if ($encoding !== null) { + if (!function_exists('mb_strtoupper')) { + $ex = new \Exception('mbstring extension is required for value mutating.'); + throw $ex; + } + + $encoding = (string) $encoding; + if (!in_array(strtolower($encoding), array_map('strtolower', mb_list_encodings()))) { + $ex = new \Exception('The given encoding '.$encoding.' is not supported by mbstring ext.'); + throw $ex; + } + } + + $this->encoding = $encoding; + return $this; + } + + /** + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + /** * @param mixed $value * @param array $options + * * @return string */ public function filter($value, $options = []) { - return strtoupper((string) $value); + $value = (string) $value; + if ($this->getEncoding()) { + return mb_strtoupper($value, $this->getEncoding()); + } + + return strtoupper($value); } /** diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php index b8c8a93d..ea875e0c 100644 --- a/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StringTrim.php @@ -12,6 +12,46 @@ */ class StringTrim implements FilterInterface { + /** + * List of characters provided to the trim() function + * + * If null the trim will be invoked with default behaviours (trimming whitespace) + * + * @var string|null + */ + protected $charList; + + /** + * StringTrim constructor. + * + * @param null $options + */ + public function __construct($options = null) + { + if (array_key_exists('charlist', $options)) { + $this->setCharList($options['charlist']); + } + } + + /** + * @param $charList + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\StringTrim + */ + public function setCharList($charList) + { + $this->charList = $charList; + return $this; + } + + /** + * @return null|string + */ + public function getCharList() + { + return $this->charList; + } + /** * @param mixed $value * @param array $options @@ -19,7 +59,33 @@ class StringTrim implements FilterInterface */ public function filter($value, $options = []) { - return trim((string) $value); + $value = (string) $value; + if ($this->getCharList() === null) { + return $this->trimUnicode($value); + } + + return $this->trimUnicode($value, $this->getCharList()); + } + + /** + * Unicode aware trim method + * Fixes a PHP problem + * + * @param string $value + * @param string $charList + * + * @return string + */ + protected function trimUnicode($value, $charList = '\\\\s') + { + $chars = preg_replace( + array( '/[\^\-\]\\\]/S', '/\\\{4}/S', '/\//'), + array( '\\\\\\0', '\\', '\/' ), + $charList + ); + + $pattern = '^[' . $chars . ']*|[' . $chars . ']*$'; + return preg_replace("/$pattern/sSD", '', $value); } /** diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php new file mode 100644 index 00000000..e11b1f3c --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php @@ -0,0 +1,33 @@ + + */ +class StripNewlines implements FilterInterface +{ + /** + * @param mixed $value + * @param array $options + * + * @return mixed + */ + public function filter($value, $options = []) + { + return str_replace(array("\n", "\r"), '', $value); + } + + /** + * @return string + */ + public function getName() + { + return 'StripNewlines'; + } +} \ No newline at end of file diff --git a/src/Kris/LaravelFormBuilder/Filters/Collection/StripTags.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StripTags.php new file mode 100644 index 00000000..dae2c56d --- /dev/null +++ b/src/Kris/LaravelFormBuilder/Filters/Collection/StripTags.php @@ -0,0 +1,262 @@ + + */ +class StripTags implements FilterInterface +{ + /** + * Array of allowed tags and allowed attributes for each allowed tag. + * + * Tags are stored in the array keys, and the array values are themselves + * arrays of the attributes allowed for the corresponding tag. + * + * @var array $allowedTags + */ + protected $allowedTags = []; + + /** + * + * Array of allowed attributes for all allowed tags. + * + * Attributes stored here are allowed for all of the allowed tags. + * + * @var array $allowedAttributes + */ + protected $allowedAttributes = []; + + /** + * StripTags constructor. + * + * @param null $options + */ + public function __construct($options = null) + { + if (array_key_exists('allowedTags', $options)) { + $this->setAllowedTags($options['allowedTags']); + } + + if (array_key_exists('allowedAttribs', $options)) { + $this->setAllowedAttributes($options['allowedAttribs']); + } + } + + /** + * Sets the allowedTags property. + * + * @param array|string $allowedTags + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\StripTags + */ + public function setAllowedTags($allowedTags) + { + if (!is_array($allowedTags)) { + $allowedTags = array($allowedTags); + } + + foreach ($allowedTags as $index => $element) { + + // If the tag was provided without attributes + if (is_int($index) && is_string($element)) { + // Canonicalize the tag name + $tagName = strtolower($element); + // Store the tag as allowed with no attributes + $this->allowedTags[$tagName] = []; + } + + // Otherwise, if a tag was provided with attributes + else if (is_string($index) && (is_array($element) || is_string($element))) { + + // Canonicalize the tag name + $tagName = strtolower($index); + // Canonicalize the attributes + if (is_string($element)) { + $element = [$element]; + } + + // Store the tag as allowed with the provided attributes + $this->allowedTags[$tagName] = []; + foreach ($element as $attribute) { + if (is_string($attribute)) { + // Canonicalize the attribute name + $attributeName = strtolower($attribute); + $this->allowedTags[$tagName][$attributeName] = null; + } + } + + } + } + + return $this; + } + + /** + * @return array + */ + public function getAllowedTags() + { + return $this->allowedTags; + } + + /** + * Sets the allowedAttributes property. + * + * @param array|string $allowedAttribs + * + * @return \Kris\LaravelFormBuilder\Filters\Collection\StripTags + */ + public function setAllowedAttributes($allowedAttribs) + { + if (!is_array($allowedAttribs)) { + $allowedAttribs = array($allowedAttribs); + } + + // Store each attribute as allowed. + foreach ($allowedAttribs as $attribute) { + if (is_string($attribute)) { + // Canonicalize the attribute name. + $attributeName = strtolower($attribute); + $this->allowedAttributes[$attributeName] = null; + } + } + + return $this; + } + + /** + * @param mixed $value + * @param array $options + * + * @return string + */ + public function filter($value, $options = []) + { + $value = (string) $value; + + // Strip HTML comments first + while (strpos($value, ' + From 1437e11f0fd13b455031a2f11ff7886e41bd294e Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Mon, 23 Jan 2023 00:12:48 +0100 Subject: [PATCH 171/194] RulesParser for file fields+ --- src/Kris/LaravelFormBuilder/RulesParser.php | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/RulesParser.php b/src/Kris/LaravelFormBuilder/RulesParser.php index ea93cf10..f9de6898 100644 --- a/src/Kris/LaravelFormBuilder/RulesParser.php +++ b/src/Kris/LaravelFormBuilder/RulesParser.php @@ -43,7 +43,7 @@ public function __construct(FormField $field) public function parse($rules) { $attributes = array(); - $rules = $rule = $this->getRulesAsArray($rules); + $rules = $this->getRulesAsArray($rules); foreach ($rules as $rule) { list($rule, $parameters) = $this->parseRule($rule); @@ -273,6 +273,10 @@ protected function min($param) return ['min' => $min]; } + if ($this->isFile()) { + return []; + } + return [ 'minlength' => $min, ]; @@ -298,6 +302,10 @@ protected function max($param) return ['max' => $max]; } + if ($this->isFile()) { + return []; + } + return ['maxlength' => $max]; } @@ -324,6 +332,10 @@ protected function between($param) ]; } + if ($this->isFile()) { + return []; + } + return [ 'minlength' => $min, 'maxlength' => $max, @@ -504,6 +516,16 @@ protected function isNumeric() return $this->isType(['number', 'range']); } + /** + * Check if the field is a file. + * + * @return bool + */ + protected function isFile() + { + return $this->isType(['file']); + } + /** * Format a date to the correct format, based on the current field. * From a1388bf54df3c3c9d263816dfd1a70194044d693 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Wed, 25 Jan 2023 18:33:04 +0100 Subject: [PATCH 172/194] dev-master = 1.52.x-dev --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 967414cb..97cc31d3 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ "orchestra/testbench": "^6.13" }, "extra": { + "branch-alias": { + "dev-master": "1.51.x-dev" + }, "laravel": { "providers": [ "Kris\\LaravelFormBuilder\\FormBuilderServiceProvider" From f5ccfc66277283dd5f714328e8e553b8ff7e2a43 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Wed, 15 Feb 2023 15:55:00 +0100 Subject: [PATCH 173/194] laravel 10, php 7.4+, testbench 8 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 97cc31d3..0810ff43 100644 --- a/composer.json +++ b/composer.json @@ -10,13 +10,13 @@ } ], "require": { - "php": ">=7.1", - "laravelcollective/html": "^5.6|^6|^7|^8|^9", - "illuminate/database": "^5.6@dev|^6|^7|^8|^9", - "illuminate/validation": "^5.6@dev|^6|^7|^8|^9" + "php": ">=7.4", + "laravelcollective/html": "^6", + "illuminate/database": "^6 || ^7 || ^8 || ^9 || ^10", + "illuminate/validation": "^6 || ^7 || ^8 || ^9 || ^10" }, "require-dev": { - "orchestra/testbench": "^6.13" + "orchestra/testbench": "^6.13 || ^7.0 || ^8.0" }, "extra": { "branch-alias": { From 870cd4a3610d723b0e32865de546493aed2e9674 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Wed, 15 Feb 2023 16:22:33 +0100 Subject: [PATCH 174/194] auto tests fixes --- composer.json | 2 +- phpunit.xml | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 0810ff43..d539b096 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "illuminate/validation": "^6 || ^7 || ^8 || ^9 || ^10" }, "require-dev": { - "orchestra/testbench": "^6.13 || ^7.0 || ^8.0" + "orchestra/testbench": "^6.13 || ^7.0" }, "extra": { "branch-alias": { diff --git a/phpunit.xml b/phpunit.xml index 1b1e3e6c..a631ba06 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -14,9 +14,7 @@ ./tests/ - - ./tests/resources/views - + ./tests/resources/views/ From 2fbe58bfdaa728d13f2f383dd27ed78935c89cd8 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Wed, 15 Feb 2023 16:26:53 +0100 Subject: [PATCH 175/194] v1.52 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d539b096..8ce7ce6c 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.51.x-dev" + "dev-master": "1.52-dev" }, "laravel": { "providers": [ From e65c3643023177e17955685604121debb6919989 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Tue, 28 Feb 2023 16:08:30 +0100 Subject: [PATCH 176/194] smarter formatInputIntoModels for non-Models (#681) --- .../Fields/CollectionType.php | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index 7b635852..18f4527a 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -2,6 +2,7 @@ namespace Kris\LaravelFormBuilder\Fields; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; class CollectionType extends ParentType @@ -189,7 +190,7 @@ protected function formatInputIntoModels(array $input, array $originalData = []) $newData = []; foreach ($input as $k => $inputItem) { if (is_array($inputItem)) { - $newData[$k] = tap($originalData[$k] ?? $this->makeNewEmptyModel())->forceFill($inputItem); + $newData[$k] = $this->formatInputIntoModel($originalData[$k] ?? $this->makeNewEmptyModel(), $inputItem); } else { $newData[$k] = $inputItem; @@ -199,6 +200,26 @@ protected function formatInputIntoModels(array $input, array $originalData = []) return $newData; } + protected function formatInputIntoModel($model, $input) + { + if ($model instanceof Model) { + $model->forceFill($input); + } + elseif (is_object($model)) { + foreach ($input as $key => $value) { + $model->$key = $value; + } + } + elseif (is_array($model)) { + $model = $input + $model; + } + else { + $model = $input; + } + + return $model; + } + /** * Set up a single child element for a collection. * From a28f76b1e5c800e118c3e6a95de67d31e19e9bf3 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Fri, 10 Mar 2023 03:09:04 +0100 Subject: [PATCH 177/194] white space --- .editorconfig | 9 ++++++ .../Fields/CollectionType.php | 30 +++++++++---------- src/Kris/LaravelFormBuilder/RulesParser.php | 6 ++-- 3 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..622f93fd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.php] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index 18f4527a..f72ece46 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -202,22 +202,22 @@ protected function formatInputIntoModels(array $input, array $originalData = []) protected function formatInputIntoModel($model, $input) { - if ($model instanceof Model) { - $model->forceFill($input); - } - elseif (is_object($model)) { - foreach ($input as $key => $value) { - $model->$key = $value; + if ($model instanceof Model) { + $model->forceFill($input); } - } - elseif (is_array($model)) { - $model = $input + $model; - } - else { - $model = $input; - } - - return $model; + elseif (is_object($model)) { + foreach ($input as $key => $value) { + $model->$key = $value; + } + } + elseif (is_array($model)) { + $model = $input + $model; + } + else { + $model = $input; + } + + return $model; } /** diff --git a/src/Kris/LaravelFormBuilder/RulesParser.php b/src/Kris/LaravelFormBuilder/RulesParser.php index f9de6898..e0bb9ce8 100644 --- a/src/Kris/LaravelFormBuilder/RulesParser.php +++ b/src/Kris/LaravelFormBuilder/RulesParser.php @@ -274,7 +274,7 @@ protected function min($param) } if ($this->isFile()) { - return []; + return []; } return [ @@ -303,7 +303,7 @@ protected function max($param) } if ($this->isFile()) { - return []; + return []; } return ['maxlength' => $max]; @@ -333,7 +333,7 @@ protected function between($param) } if ($this->isFile()) { - return []; + return []; } return [ From 7775b3e5794037dd6fb973562c2078429bc2b647 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Sun, 26 Mar 2023 03:21:49 +0200 Subject: [PATCH 178/194] Update test.yml (#708) --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70e38129..4127d4c0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: package-release: [dist] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Setup Composer cache - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.package-release }}-${{ hashFiles('**/composer.json') }} From b9ee07c4fb05433dc89caeeaa1be886f19b53623 Mon Sep 17 00:00:00 2001 From: Janusz Date: Sun, 16 Apr 2023 00:03:36 +0200 Subject: [PATCH 179/194] refs #447: adding `same:` rule automatically (#710) --- .gitignore | 4 +- .../LaravelFormBuilder/Fields/FormField.php | 14 ++- .../Fields/RepeatedType.php | 19 +++- tests/Fields/RepeatedTypeTest.php | 90 ++++++++++++++++++- 4 files changed, 123 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 5b2fa806..4fb34c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ composer.phar composer.lock .DS_Store .idea +.vscode coverage *.taskpaper -NOTES.md \ No newline at end of file +NOTES.md +.phpunit.result.cache diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index 28f90e18..be9b44f1 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -361,7 +361,7 @@ protected function prepareRules(array &$sourceOptions = []) // Append rules if ($rulesToBeAppended = Arr::pull($sourceOptions, 'rules_append')) { - $mergedRules = array_values(array_unique(array_merge($options['rules'], $rulesToBeAppended), SORT_REGULAR)); + $mergedRules = $this->mergeRules($options['rules'], $rulesToBeAppended); $options['rules'] = $mergedRules; } @@ -390,6 +390,18 @@ protected function normalizeRules($rules) return $rules; } + /** + * Merges two sets of rules into one + * + * @param array $first first set of rules + * @param array $second second set of rules + * @return array merged set of rules without duplicates + */ + protected function mergeRules($first, $second) + { + return array_values(array_unique(array_merge($first, $second), SORT_REGULAR)); + } + /** * Get name of the field. diff --git a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php index 6e9c34fc..0afaff78 100644 --- a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php +++ b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php @@ -2,6 +2,8 @@ namespace Kris\LaravelFormBuilder\Fields; +use Illuminate\Support\Arr; + class RepeatedType extends ParentType { @@ -42,6 +44,8 @@ public function getAllAttributes() */ protected function createChildren() { + $this->prepareOptions(); + $firstName = $this->getRealName(); $secondName = $this->getOption('second_name'); @@ -49,14 +53,27 @@ protected function createChildren() $secondName = $firstName.'_confirmation'; } + // merge field rules and first field rules + $firstOptions = $this->getOption('first_options'); + $firstOptions['rules'] = $this->normalizeRules(Arr::pull($firstOptions, 'rules', [])); + if ($mainRules = $this->getOption('rules')) { + $firstOptions['rules'] = $this->mergeRules($mainRules, $firstOptions['rules']); + } + + $sameRule = 'same:' . $secondName; + if (!in_array($sameRule, $firstOptions['rules'])) { + $firstOptions['rules'][] = $sameRule; + } + $form = $this->parent->getFormBuilder()->plain([ 'name' => $this->parent->getName(), 'model' => $this->parent->getModel() ]) - ->add($firstName, $this->getOption('type'), $this->getOption('first_options')) + ->add($firstName, $this->getOption('type'), $firstOptions) ->add($secondName, $this->getOption('type'), $this->getOption('second_options')); $this->children['first'] = $form->getField($firstName); $this->children['second'] = $form->getField($secondName); } + } diff --git a/tests/Fields/RepeatedTypeTest.php b/tests/Fields/RepeatedTypeTest.php index f9b448a8..d96b5f25 100644 --- a/tests/Fields/RepeatedTypeTest.php +++ b/tests/Fields/RepeatedTypeTest.php @@ -1,7 +1,8 @@ assertTrue($this->plainForm->getFormOption('files')); } + + /** @test */ + public function handles_validation_rules_properly() + { + // has no other rules + $plainForm = $this->formBuilder->plain(); + $plainForm->add('password', 'repeated'); + $repeated = $plainForm->getField('password'); + + $this->assertContains('same:password_confirmation', $repeated->first->getOption('rules')); + + $this->request['password'] = '123'; + $this->request['password_confirmation'] = '124'; + + $valid = $plainForm->isValid(); + $this->assertFalse($valid); + + $errors = [ + 'password' => ['The Password and password confirmation must match.'], + ]; + $this->assertEquals($errors, $plainForm->getErrors()); + + // has own rules + $plainForm = $this->formBuilder->plain(); + $plainForm->add('password', 'repeated', [ + 'rules' => 'required|min:5', + ]); + $plainForm->renderForm(); + + $rules = ['password' => ['required', 'min:5', 'same:password_confirmation']]; + $this->assertEquals($rules, $plainForm->getRules()); + + $valid = $plainForm->isValid(); + $this->assertFalse($valid); + + $errors = [ + 'password' => [ + 'The Password must be at least 5 characters.', + 'The Password and password confirmation must match.', + ] + ]; + $this->assertEquals($errors, $plainForm->getErrors()); + + // has own rules on field and in first field options + $plainForm = $this->formBuilder->plain(); + $plainForm->add('password', 'repeated', [ + 'rules' => 'required', + 'first_options' => [ + 'rules' => 'min:5', + ] + ]); + $rules = ['password' => ['required', 'min:5', 'same:password_confirmation']]; + $this->assertEquals($rules, $plainForm->getRules()); + $valid = $plainForm->isValid(); + $this->assertFalse($valid); + + $errors = [ + 'password' => [ + 'The Password must be at least 5 characters.', + 'The Password and password confirmation must match.', + ] + ]; + $this->assertEquals($errors, $plainForm->getErrors()); + + // has rules only in field options + $plainForm = $this->formBuilder->plain(); + $plainForm->add('password', 'repeated', [ + 'rules' => 'required', + 'first_options' => [ + 'rules' => 'required|min:5', + ] + ]); + $rules = ['password' => ['required', 'min:5', 'same:password_confirmation']]; + $this->assertEquals($rules, $plainForm->getRules()); + + $valid = $plainForm->isValid(); + $this->assertFalse($valid); + + $errors = [ + 'password' => [ + 'The Password must be at least 5 characters.', + 'The Password and password confirmation must match.', + ] + ]; + $this->assertEquals($errors, $plainForm->getErrors()); + } + } From f714e4984fdbb3b98ff37f272ee17de1cd17def6 Mon Sep 17 00:00:00 2001 From: Janusz Date: Mon, 17 Apr 2023 16:56:00 +0200 Subject: [PATCH 180/194] ParentField disable/enable fixed (#705) - Parent Field when disabled or enabled did only populate information to its children and forgot about it itself. - fixed some typing error - extended tests for disable/enable functionality - added `attr` population for some ParentType derivatives - added variant specific config (wrapper_class, label_class, field_class) for choice type --- .../LaravelFormBuilder/Fields/ChoiceType.php | 24 ++- .../Fields/CollectionType.php | 2 +- .../LaravelFormBuilder/Fields/ParentType.php | 4 + src/config/config.php | 16 ++ tests/Fields/ChoiceTypeTest.php | 181 ++++++++++++++++++ tests/Fields/CollectionTypeTest.php | 49 +++++ tests/Fields/EntityTypeTest.php | 62 +++++- 7 files changed, 329 insertions(+), 9 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php index 6b85dc1c..026d4a46 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php @@ -2,6 +2,8 @@ namespace Kris\LaravelFormBuilder\Fields; +use Illuminate\Support\Arr; + class ChoiceType extends ParentType { /** @@ -32,7 +34,7 @@ protected function determineChoiceField() $expanded = $this->options['expanded']; $multiple = $this->options['multiple']; - if ($multiple) { + if (!$expanded && $multiple) { $this->options['attr']['multiple'] = true; } @@ -102,12 +104,14 @@ protected function buildCheckableChildren($fieldType) { $multiple = $this->getOption('multiple') ? '[]' : ''; + $attr = $this->options['attr']?? []; + $attr = Arr::except($attr, ['class', 'multiple', 'id', 'name']); foreach ((array)$this->options['choices'] as $key => $choice) { $id = str_replace('.', '_', $this->getNameKey()) . '_' . $key; $options = $this->formHelper->mergeOptions( $this->getOption('choice_options'), [ - 'attr' => array_merge(['id' => $id], $this->options['option_attributes'][$key] ?? []), + 'attr' => array_merge(['id' => $id], $this->options['option_attributes'][$key] ?? $attr), 'label_attr' => ['for' => $id], 'label' => $choice, 'checked' => in_array($key, (array)$this->options[$this->valueProperty]), @@ -148,15 +152,25 @@ protected function setDefaultClasses(array $options = []) { $defaults = parent::setDefaultClasses($options); $choice_type = $this->determineChoiceField(); + Arr::forget($defaults, 'attr.class'); $wrapper_class = $this->formHelper->getConfig('defaults.' . $this->type . '.' . $choice_type . '_wrapper_class', ''); if ($wrapper_class) { $defaults['wrapper']['class'] = (isset($defaults['wrapper']['class']) ? $defaults['wrapper']['class'] . ' ' : '') . $wrapper_class; } - $choice_wrapper_class = $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.wrapper_class', ''); - $choice_label_class = $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.label_class', ''); - $choice_field_class = $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.field_class', ''); + $choice_wrapper_class = $this->formHelper->getConfig( + 'defaults.' . $this->type . '.choice_options.wrapper_class', + $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.wrapper_class', '') + ); + $choice_label_class = $this->formHelper->getConfig( + 'defaults.' . $this->type . '.choice_options.label_class', + $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.label_class', '') + ); + $choice_field_class = $this->formHelper->getConfig( + 'defaults.' . $this->type . '.choice_options.field_class', + $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.field_class', '') + ); if ($choice_wrapper_class) { $defaults['choice_options']['wrapper']['class'] = $choice_wrapper_class; diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index f72ece46..77e61974 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -234,7 +234,7 @@ protected function setupChild(FormField $field, $name, $value = null) $firstFieldOptions = $this->formHelper->mergeOptions( $this->getOption('options'), - ['attr' => ['id' => $newFieldName]] + ['attr' => array_merge(['id' => $newFieldName], $this->getOption('attr'))] ); $field->setName($newFieldName); diff --git a/src/Kris/LaravelFormBuilder/Fields/ParentType.php b/src/Kris/LaravelFormBuilder/Fields/ParentType.php index aebfe9d9..80352d0c 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ParentType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ParentType.php @@ -175,9 +175,11 @@ public function __clone() */ public function disable() { + parent::disable(); foreach ($this->children as $field) { $field->disable(); } + return $this; } /** @@ -185,9 +187,11 @@ public function disable() */ public function enable() { + parent::enable(); foreach ($this->children as $field) { $field->enable(); } + return $this; } /** diff --git a/src/config/config.php b/src/config/config.php index a2d1df61..0288d3de 100644 --- a/src/config/config.php +++ b/src/config/config.php @@ -24,7 +24,23 @@ // 'wrapper' => ['class' => 'form-radio'], // 'label' => ['class' => 'form-radio-label'], // 'field' => ['class' => 'form-radio-field'], + // ] //], + // + // 'choice' => [ + // 'choice_options' => [ + // 'wrapper_class' => 'choice-wrapper-class', + // 'label_class' => 'choice-label-class', + // 'field_class' => 'choice-field-class' + // + // # For choice type you may overwrite default choice options for each variant (checkbox, radio or select) + // 'checkbox' => [ + // 'wrapper_class' => 'choice-checkbox-wrapper-class', + // 'label_class' => 'choice-checkbox-label-class', + // 'field_class' => 'choice-checkbox-field-class', + // ] + // ] + //] ], // Templates 'form' => 'laravel-form-builder::form', diff --git a/tests/Fields/ChoiceTypeTest.php b/tests/Fields/ChoiceTypeTest.php index 4bd10945..62c1c96f 100644 --- a/tests/Fields/ChoiceTypeTest.php +++ b/tests/Fields/ChoiceTypeTest.php @@ -106,4 +106,185 @@ public function it_can_override_choices() $this->assertEquals('test', $choice->getOption('selected')); } + + /** @test */ + public function it_disables_select() + { + $options = [ + 'attr' => ['class' => 'choice-class'], + 'choices' => ['yes' => 'Yes', 'no' => 'No'], + 'selected' => 'yes' + ]; + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be no 'disabled' attribute set beforehand + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + + $field->disable(); + + // there shall be 'disabled' attribute set after + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + } + + /** @test */ + public function it_disables_checkbox_list() + { + $options = [ + 'attr' => ['class' => 'choice-class-something'], + 'choices' => [1 => 'monday', 2 => 'tuesday'], + 'selected' => 'tuesday', + 'multiple' => true, + 'expanded' => true + ]; + + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be no 'disabled' attribute set beforehand + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + + $field->disable(); + + // there shall be 'disabled' attribute set after + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + } + + /** @test */ + public function it_disables_radios_list() + { + $options = [ + 'attr' => ['class' => 'choice-class-something'], + 'choices' => [1 => 'yes', 2 => 'no'], + 'selected' => 'no', + 'expanded' => true + ]; + + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be no 'disabled' attribute set beforehand + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + + $field->disable(); + + // there shall be 'disabled' attribute set after + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + } + + /** @test */ + public function it_enables_select() + { + $options = [ + 'attr' => ['class' => 'choice-class', 'disabled' => 'disabled'], + 'choices' => ['yes' => 'Yes', 'no' => 'No'], + 'selected' => 'yes' + ]; + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be 'disabled' attribute set beforehand + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + + + $field->enable(); + + // there shall be no 'disabled' attribute set after + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + } + + /** @test */ + public function it_enables_checkbox_list() + { + $options = [ + 'attr' => ['class' => 'choice-class-something', 'disabled' => 'disabled'], + 'choices' => [1 => 'monday', 2 => 'tuesday'], + 'selected' => 'tuesday', + 'multiple' => true, + 'expanded' => true + ]; + + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be 'disabled' attribute set beforehand + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + + + $field->enable(); + + // there shall be no 'disabled' attribute set after + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + } + + /** @test */ + public function it_enables_radios_list() + { + $options = [ + 'attr' => ['class' => 'choice-class-something', 'disabled' => 'disabled'], + 'choices' => [1 => 'yes', 2 => 'no'], + 'selected' => 'no', + 'expanded' => true + ]; + + $field = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); + $children = $field->getChildren(); + + // there shall be 'disabled' attribute set beforehand + $this->assertArrayHasKey('disabled', $field->getOption('attr')); + $this->assertEquals('disabled', $field->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + + + $field->enable(); + + // there shall be no 'disabled' attribute set after + $this->assertArrayNotHasKey('disabled', $field->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + } } diff --git a/tests/Fields/CollectionTypeTest.php b/tests/Fields/CollectionTypeTest.php index f52c27fb..15c59983 100644 --- a/tests/Fields/CollectionTypeTest.php +++ b/tests/Fields/CollectionTypeTest.php @@ -297,6 +297,55 @@ public function it_uses_empty_model_for_new_collection_children_after_validation } } + /** @test */ + public function it_disables() + { + $options = [ + 'type' => 'text' + ]; + $emailsCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); + $children = $emailsCollection->getChildren(); + + $this->assertArrayNotHasKey('disabled', $emailsCollection->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + + $emailsCollection->disable(); + + $this->assertArrayHasKey('disabled', $emailsCollection->getOption('attr')); + $this->assertEquals('disabled', $emailsCollection->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + } + + /** @test */ + public function it_enables() + { + $options = [ + 'type' => 'text', + 'attr' => ['disabled' => 'disabled'], + ]; + $collection = new CollectionType('emails', 'collection', $this->plainForm, $options); + $children = $collection->getChildren(); + + $this->assertArrayHasKey('disabled', $collection->getOption('attr')); + $this->assertEquals('disabled', $collection->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + + $collection->enable(); + + $this->assertArrayNotHasKey('disabled', $collection->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + } + } class DummyEloquentModel extends Model { diff --git a/tests/Fields/EntityTypeTest.php b/tests/Fields/EntityTypeTest.php index e47de97c..27b1e0fe 100644 --- a/tests/Fields/EntityTypeTest.php +++ b/tests/Fields/EntityTypeTest.php @@ -92,12 +92,68 @@ public function options_are_passed_to_the_children() $choice->setOption('attr.data-key', 'value'); $field = $choice->render(); - + $expectedField = '
-
'; + '; + + $this->assertXmlStringEqualsXmlString(trim($field), $expectedField); + } + + /** @test */ + public function it_disables() + { + $choices = ['yes' => 'Yes', 'no' => 'No']; + $options = [ + 'attr' => ['class' => 'choice-class'], + 'choices' => $choices, + 'selected' => 'yes' + ]; + + $choice = new EntityType('some_entity', 'entity', $this->plainForm, $options); + $children = $choice->getChildren(); + + $this->assertArrayNotHasKey('disabled', $choice->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } + + $choice->disable(); + + $this->assertArrayHasKey('disabled', $choice->getOption('attr')); + $this->assertEquals('disabled', $choice->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + } + + /** @test */ + public function it_enables() + { + $choices = ['yes' => 'Yes', 'no' => 'No']; + $options = [ + 'attr' => ['class' => 'choice-class', 'disabled' => 'disabled'], + 'choices' => $choices, + 'selected' => 'yes' + ]; + + $choice = new EntityType('some_entity', 'entity', $this->plainForm, $options); + $children = $choice->getChildren(); + + $this->assertArrayHasKey('disabled', $choice->getOption('attr')); + $this->assertEquals('disabled', $choice->getOption('attr')['disabled']); + foreach ($children as $child) { + $this->assertArrayHasKey('disabled', $child->getOption('attr')); + $this->assertEquals('disabled', $child->getOption('attr')['disabled']); + } + + $choice->enable(); - $this->assertXmlStringEqualsXmlString(trim($field), $expectedField); + $this->assertArrayNotHasKey('disabled', $choice->getOption('attr')); + foreach ($children as $child) { + $this->assertArrayNotHasKey('disabled', $child->getOption('attr')); + } } } From 60a198a4c1b8669f81f033634c4320233b7a1e35 Mon Sep 17 00:00:00 2001 From: ukeloop <45733259+ukeloop@users.noreply.github.com> Date: Mon, 17 Apr 2023 23:58:17 +0900 Subject: [PATCH 181/194] Added InvalidArgumentException (#692) --- src/Kris/LaravelFormBuilder/Fields/EntityType.php | 12 ++++++++++++ src/Kris/LaravelFormBuilder/Fields/FormField.php | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/EntityType.php b/src/Kris/LaravelFormBuilder/Fields/EntityType.php index 00db6c73..56d87baa 100644 --- a/src/Kris/LaravelFormBuilder/Fields/EntityType.php +++ b/src/Kris/LaravelFormBuilder/Fields/EntityType.php @@ -103,6 +103,12 @@ protected function pluck($value, $key, $data) //laravel 5.2.* return $data->lists($value, $key); } + + throw new \InvalidArgumentException(sprintf( + 'Please provide valid "property" option for entity field [%s] in form class [%s]', + $this->getRealName(), + get_class($this->parent) + )); } protected function get($data) @@ -115,5 +121,11 @@ protected function get($data) //laravel 5.3.* return $data->get(); } + + throw new \InvalidArgumentException(sprintf( + 'Please provide valid "query_builder" option for entity field [%s] in form class [%s]', + $this->getRealName(), + get_class($this->parent) + )); } } diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index be9b44f1..b5032140 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -239,13 +239,24 @@ protected function getRenderData() protected function getModelValueAttribute($model, $name) { $transformedName = $this->transformKey($name); + + if (is_null($model)) { + return null; + } + if (is_string($model)) { return $model; - } elseif (is_object($model)) { + } + + if (is_object($model)) { return object_get($model, $transformedName); - } elseif (is_array($model)) { + } + + if (is_array($model)) { return Arr::get($model, $transformedName); } + + throw new \InvalidArgumentException('Invalid model given to field'); } /** From d8c2ba682bbfc965ee10da2eeec54627a22c2a8a Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:21:15 +0200 Subject: [PATCH 182/194] Laravel 8 - 10 testing (#711) --- .github/workflows/test.yml | 9 +- .gitignore | 5 +- composer.json | 10 +- phpunit.xml | 26 ++-- tests/Fields/ButtonGroupTypeTest.php | 2 +- tests/Fields/CheckableTypeTest.php | 2 +- tests/Fields/FormFieldTest.php | 14 +- tests/Fields/RepeatedTypeTest.php | 24 ++-- tests/FormBuilderTestCase.php | 15 ++ tests/FormTest.php | 12 +- tests/resources/lang/en/validation.php | 184 +++++++++++++++++++++++++ 11 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 tests/resources/lang/en/validation.php diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4127d4c0..a1202972 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,12 +13,15 @@ jobs: runs-on: ubuntu-latest strategy: max-parallel: 12 + fail-fast: false matrix: php: ['7.4', '8.0', '8.1', '8.2'] package-release: [dist] steps: - name: Checkout repository uses: actions/checkout@v3 + with: + fetch-depth: 2 - name: Setup PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 @@ -28,7 +31,7 @@ jobs: - name: Get user-level Composer cache id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Setup Composer cache uses: actions/cache@v3 @@ -50,5 +53,5 @@ jobs: - name: Upload to Scrutinizer continue-on-error: true run: | - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover coverage.clover + composer global require scrutinizer/ocular + ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover diff --git a/.gitignore b/.gitignore index 4fb34c2a..37dc081e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ composer.lock coverage *.taskpaper NOTES.md -.phpunit.result.cache +/.phpunit.result.cache +/.phpunit.cache/ +/phpunit.xml.bak +/coverage.clover diff --git a/composer.json b/composer.json index 8ce7ce6c..80d14d8e 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "illuminate/validation": "^6 || ^7 || ^8 || ^9 || ^10" }, "require-dev": { - "orchestra/testbench": "^6.13 || ^7.0" + "orchestra/testbench": "^6.13 || ^7.0 || ^8" }, "extra": { "branch-alias": { @@ -35,13 +35,15 @@ "psr-0": { "Kris\\LaravelFormBuilder": "src/" }, - "classmap": [ - "tests/FormBuilderTestCase.php" - ], "files": [ "src/helpers.php" ] }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/phpunit.xml b/phpunit.xml index a631ba06..0545ef35 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,20 +1,22 @@ - + ./tests/ ./tests/resources/views/ + ./tests/resources/lang/ + ./tests/Fixtures/ + ./tests/FormBuilderTestCase.php diff --git a/tests/Fields/ButtonGroupTypeTest.php b/tests/Fields/ButtonGroupTypeTest.php index f01f2abc..b18cbaeb 100644 --- a/tests/Fields/ButtonGroupTypeTest.php +++ b/tests/Fields/ButtonGroupTypeTest.php @@ -104,4 +104,4 @@ public function it_handles_buttons(): void $this->assertSame($buttons, $buttongroup->getOption('buttons')); } -} \ No newline at end of file +} diff --git a/tests/Fields/CheckableTypeTest.php b/tests/Fields/CheckableTypeTest.php index da1d9a73..3a54156d 100644 --- a/tests/Fields/CheckableTypeTest.php +++ b/tests/Fields/CheckableTypeTest.php @@ -108,4 +108,4 @@ public function it_handles_checked(): void $this->assertTrue($checkable->getValue()); $this->assertTrue($checkable->getOption('checked')); } -} \ No newline at end of file +} diff --git a/tests/Fields/FormFieldTest.php b/tests/Fields/FormFieldTest.php index bbfc8da4..4a039cb0 100644 --- a/tests/Fields/FormFieldTest.php +++ b/tests/Fields/FormFieldTest.php @@ -21,9 +21,7 @@ public function it_use_set_rendered() /** @test */ public function it_uses_the_template_prefix() { - $viewStub = $this->getMockBuilder('Illuminate\View\Factory')->setMethods(['make', 'with', 'render'])->disableOriginalConstructor()->getMock(); - $viewStub->method('make')->willReturn($viewStub); - $viewStub->method('with')->willReturn($viewStub); + $viewStub = $this->getViewFactoryMock(); $helper = new FormHelper($viewStub, $this->translator, $this->config); @@ -212,7 +210,7 @@ public function it_translates_the_label_if_translation_exists() $this->plainForm->setLanguageName('validation')->add('accepted', 'text'); $this->assertEquals( - 'The :attribute must be accepted.', + 'The :attribute field must be accepted.', $this->plainForm->accepted->getOption('label') ); } @@ -225,7 +223,7 @@ public function it_translates_the_label_using_translation_templates() $this->plainForm->setTranslationTemplate('validation.{name}')->add('accepted', 'text'); $this->assertEquals( - 'The :attribute must be accepted.', + 'The :attribute field must be accepted.', $this->plainForm->accepted->getOption('label') ); } @@ -479,7 +477,7 @@ public function label_template() 'options' => [ 'label' => 'Text Field #1', 'label_show' => true, - 'label_template' => 'laravel-form-builder-test::test-label', + 'label_template' => 'laravel-form-builder-test::test-label', ] ], [ @@ -488,10 +486,10 @@ public function label_template() 'options' => [ 'label' => 'Textarea Field #1', 'label_show' => true, - 'label_template' => 'laravel-form-builder-test::test-label', + 'label_template' => 'laravel-form-builder-test::test-label', ] ], - + ]; foreach ($fieldsOptions as $config) { diff --git a/tests/Fields/RepeatedTypeTest.php b/tests/Fields/RepeatedTypeTest.php index d96b5f25..e1a2b805 100644 --- a/tests/Fields/RepeatedTypeTest.php +++ b/tests/Fields/RepeatedTypeTest.php @@ -1,9 +1,7 @@ assertFalse($valid); $errors = [ - 'password' => ['The Password and password confirmation must match.'], + 'password' => [ + 'The Password field must match password confirmation.', + ], ]; $this->assertEquals($errors, $plainForm->getErrors()); @@ -65,7 +65,7 @@ public function handles_validation_rules_properly() 'rules' => 'required|min:5', ]); $plainForm->renderForm(); - + $rules = ['password' => ['required', 'min:5', 'same:password_confirmation']]; $this->assertEquals($rules, $plainForm->getRules()); @@ -74,8 +74,8 @@ public function handles_validation_rules_properly() $errors = [ 'password' => [ - 'The Password must be at least 5 characters.', - 'The Password and password confirmation must match.', + 'The Password field must be at least 5 characters.', + 'The Password field must match password confirmation.', ] ]; $this->assertEquals($errors, $plainForm->getErrors()); @@ -95,9 +95,9 @@ public function handles_validation_rules_properly() $errors = [ 'password' => [ - 'The Password must be at least 5 characters.', - 'The Password and password confirmation must match.', - ] + 'The Password field must be at least 5 characters.', + 'The Password field must match password confirmation.', + ] ]; $this->assertEquals($errors, $plainForm->getErrors()); @@ -117,9 +117,9 @@ public function handles_validation_rules_properly() $errors = [ 'password' => [ - 'The Password must be at least 5 characters.', - 'The Password and password confirmation must match.', - ] + 'The Password field must be at least 5 characters.', + 'The Password field must match password confirmation.', + ] ]; $this->assertEquals($errors, $plainForm->getErrors()); } diff --git a/tests/FormBuilderTestCase.php b/tests/FormBuilderTestCase.php index f4a4f7ae..84f27d5c 100644 --- a/tests/FormBuilderTestCase.php +++ b/tests/FormBuilderTestCase.php @@ -186,6 +186,9 @@ public function setUp(): void parent::setUp(); $this->withoutDeprecationHandling(); + + $this->app['path.lang'] = __DIR__ . '/resources/lang'; + // add views for testing $this->app['view']->addNamespace('laravel-form-builder-test', __DIR__ . '/resources/views'); @@ -267,4 +270,16 @@ protected function assertIdentical($one, $two): void { self::assertThat($one, new IsIdentical($two)); } + + protected function getViewFactoryMock() + { + $mock = $this->getMockBuilder('Illuminate\View\Factory') + ->onlyMethods(['make']) + ->addMethods(['with', 'render']) + ->disableOriginalConstructor() + ->getMock(); + $mock->method('make')->willReturn($mock); + $mock->method('with')->willReturn($mock); + return $mock; + } } diff --git a/tests/FormTest.php b/tests/FormTest.php index f51fd5c6..7a488e08 100644 --- a/tests/FormTest.php +++ b/tests/FormTest.php @@ -91,7 +91,7 @@ public function it_fails_validation() $errors = [ 'name' => ['The Name field is required.'], - 'description' => ['The Description must not be greater than 10 characters.'] + 'description' => ['The Description field must not be greater than 10 characters.'] ]; $this->assertEquals($errors, $this->plainForm->getErrors()); @@ -153,7 +153,7 @@ public function it_can_automatically_redirect_back_when_failing_verification() $errorBag = $response->getSession()->get('errors'); $this->assertTrue($errorBag->has('description')); $this->assertTrue($errorBag->has('name')); - $this->assertEquals('The Description must not be greater than 10 characters.', $errorBag->first('description')); + $this->assertEquals('The Description field must not be greater than 10 characters.', $errorBag->first('description')); } } @@ -191,7 +191,7 @@ public function it_can_automatically_redirect_to_a_specified_destination_when_fa $errorBag = $response->getSession()->get('errors'); $this->assertTrue($errorBag->has('description')); $this->assertTrue($errorBag->has('name')); - $this->assertEquals('The Description must not be greater than 10 characters.', $errorBag->first('description')); + $this->assertEquals('The Description field must not be greater than 10 characters.', $errorBag->first('description')); } } @@ -233,7 +233,7 @@ public function it_overrides_default_rules_and_messages() $errors = [ 'name' => ['Name field must be numeric.'], - 'description' => ['The Description must not be greater than 10 characters.'], + 'description' => ['The Description field must not be greater than 10 characters.'], 'age' => ['The age field is a must.'], 'email' => ['The email is very required.'] ]; @@ -1109,9 +1109,7 @@ public function it_stores_a_template_prefix() /** @test */ public function it_uses_the_template_prefix() { - $viewStub = $this->getMockBuilder('Illuminate\View\Factory')->setMethods(['make', 'with', 'render'])->disableOriginalConstructor()->getMock(); - $viewStub->method('make')->willReturn($viewStub); - $viewStub->method('with')->willReturn($viewStub); + $viewStub = $this->getViewFactoryMock(); $helper = new FormHelper($viewStub, $this->translator, $this->config); diff --git a/tests/resources/lang/en/validation.php b/tests/resources/lang/en/validation.php new file mode 100644 index 00000000..99f7c611 --- /dev/null +++ b/tests/resources/lang/en/validation.php @@ -0,0 +1,184 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'uuid' => 'The :attribute field must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; From cdd39225b408fdb77670863eae9fd131ac71133a Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:27:57 +0100 Subject: [PATCH 183/194] Allow collection option label to be a callback for dynamic child label (#718) --- src/Kris/LaravelFormBuilder/Fields/CollectionType.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index 77e61974..f51b679f 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -237,6 +237,10 @@ protected function setupChild(FormField $field, $name, $value = null) ['attr' => array_merge(['id' => $newFieldName], $this->getOption('attr'))] ); + if (isset($firstFieldOptions['label'])) { + $firstFieldOptions['label'] = value($firstFieldOptions['label'], $value, $field); + } + $field->setName($newFieldName); $field->setOptions($firstFieldOptions); @@ -249,7 +253,6 @@ protected function setupChild(FormField $field, $name, $value = null) $field->setValue($value); - return $field; } From 916b83d321d0f3f784c548e8d1530f3ae7974e24 Mon Sep 17 00:00:00 2001 From: Janusz Date: Thu, 29 Feb 2024 13:01:52 +0100 Subject: [PATCH 184/194] issue 719: fixed numeric pattern (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Janusz PaszyƄski --- src/Kris/LaravelFormBuilder/RulesParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/RulesParser.php b/src/Kris/LaravelFormBuilder/RulesParser.php index e0bb9ce8..6f7452e6 100644 --- a/src/Kris/LaravelFormBuilder/RulesParser.php +++ b/src/Kris/LaravelFormBuilder/RulesParser.php @@ -175,7 +175,7 @@ protected function numeric() } return [ - 'pattern' => '[-+]?[0-9]*[.,]?[0-9]+', + 'pattern' => '[\\-+]?[0-9]*[.,]?[0-9]+', 'title' => $this->getTitle('numeric'), ]; } From 62b0a6345de8bd7e237436e60f73ce032b3e4f31 Mon Sep 17 00:00:00 2001 From: Sang Nguyen Date: Fri, 5 Apr 2024 02:33:13 +0700 Subject: [PATCH 185/194] Registering customLabel macro only once Form service resolves (#725) --- .../FormBuilderServiceProvider.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index 4ae5b07b..c2697987 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -7,8 +7,8 @@ use Collective\Html\HtmlBuilder; use Illuminate\Support\ServiceProvider; use InvalidArgumentException; -use Kris\LaravelFormBuilder\Traits\ValidatesWhenResolved; use Kris\LaravelFormBuilder\Form; +use Kris\LaravelFormBuilder\Traits\ValidatesWhenResolved; class FormBuilderServiceProvider extends ServiceProvider { @@ -105,15 +105,15 @@ public function boot() __DIR__ . '/../../config/config.php' => config_path('laravel-form-builder.php') ]); - $form = $this->app[static::FORM_ABSTRACT]; - - $form->macro('customLabel', function($name, $value, $options = [], $escape_html = true) use ($form) { - if (isset($options['for']) && $for = $options['for']) { - unset($options['for']); - return $form->label($for, $value, $options, $escape_html); - } + $this->app->afterResolving(static::FORM_ABSTRACT, function (LaravelForm $form) { + $form->macro('customLabel', function($name, $value, $options = [], $escapeHtml = true) use ($form) { + if (isset($options['for']) && $for = $options['for']) { + unset($options['for']); + return $form->label($for, $value, $options, $escapeHtml); + } - return $form->label($name, $value, $options, $escape_html); + return $form->label($name, $value, $options, $escapeHtml); + }); }); } From 8ef6b44b9a10392f9184e9d4b0bd366bbc8adbed Mon Sep 17 00:00:00 2001 From: Laravel Shift Date: Sun, 21 Apr 2024 09:50:34 -0400 Subject: [PATCH 186/194] Laravel 11.x compatibility & tests (#721) --- .github/workflows/test.yml | 35 ++++++++++++++++++++++------------- composer.json | 13 +++++++------ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1202972..9af7919d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,23 @@ env: jobs: test: - name: "Build" + name: "Lara ${{ matrix.laravel }} PHP ${{ matrix.php }} Unit ${{ matrix.phpunit }}" runs-on: ubuntu-latest strategy: - max-parallel: 12 + max-parallel: 6 # 12 fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2'] - package-release: [dist] + laravel: [9, 10, 11] + php: ['8.1', '8.2', '8.3'] + phpunit: [9, 10] + exclude: + - {laravel: 11, php: '8.1'} + - {laravel: 9, phpunit: 10} + - {phpunit: 9} + include: + - {laravel: 9, php: '8.1', phpunit: 9} + - {laravel: 9, php: '8.2', phpunit: 9} + - {laravel: 9, php: '8.3', phpunit: 9} steps: - name: Checkout repository uses: actions/checkout@v3 @@ -37,21 +46,21 @@ jobs: uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} - key: composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.package-release }}-${{ hashFiles('**/composer.json') }} + key: composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ hashFiles('**/composer.json') }} restore-keys: | - composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.package-release }}-${{ env.cache-name }}- - composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.package-release }}- + composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ env.cache-name }}- + composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}- composer-${{ runner.os }}-${{ matrix.php }}- composer-${{ runner.os }}- - name: Install composer dependencies - run: composer install --no-progress --no-interaction --prefer-${{ matrix.package-release }} + run: composer require --no-progress --no-interaction illuminate/database:^${{ matrix.laravel }}.0 illuminate/validation:^${{ matrix.laravel }}.0 phpunit/phpunit:^${{ matrix.phpunit }}.0 - name: Run unit tests run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover - - name: Upload to Scrutinizer - continue-on-error: true - run: | - composer global require scrutinizer/ocular - ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover + # - name: Upload to Scrutinizer + # continue-on-error: true + # run: | + # composer global require scrutinizer/ocular + # ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover diff --git a/composer.json b/composer.json index 80d14d8e..013cc451 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "kris/laravel-form-builder", "description": "Laravel form builder - symfony like", - "keywords": ["laravel", "form", "builder","symfony"], + "keywords": ["laravel", "form", "builder", "symfony"], "license": "MIT", "authors": [ { @@ -10,13 +10,14 @@ } ], "require": { - "php": ">=7.4", - "laravelcollective/html": "^6", - "illuminate/database": "^6 || ^7 || ^8 || ^9 || ^10", - "illuminate/validation": "^6 || ^7 || ^8 || ^9 || ^10" + "php": "^8.0", + "rdx/laravelcollective-html": "^6", + "illuminate/database": "^6 || ^7 || ^8 || ^9 || ^10 || ^11", + "illuminate/validation": "^6 || ^7 || ^8 || ^9 || ^10 || ^11" }, "require-dev": { - "orchestra/testbench": "^6.13 || ^7.0 || ^8" + "orchestra/testbench": "^6.13 || ^7 || ^8 || ^9", + "phpunit/phpunit": "^10.0" }, "extra": { "branch-alias": { From ee4f8f1c41593f8f747e98b96355ba57f13a3be8 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Mon, 29 Apr 2024 18:32:45 +0200 Subject: [PATCH 187/194] maybe fix undefined customLabel() in poor containers --- src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php index c2697987..3474d130 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php +++ b/src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php @@ -105,7 +105,7 @@ public function boot() __DIR__ . '/../../config/config.php' => config_path('laravel-form-builder.php') ]); - $this->app->afterResolving(static::FORM_ABSTRACT, function (LaravelForm $form) { + $this->callAfterResolving(static::FORM_ABSTRACT, function (LaravelForm $form) { $form->macro('customLabel', function($name, $value, $options = [], $escapeHtml = true) use ($form) { if (isset($options['for']) && $for = $options['for']) { unset($options['for']); From 0837e269b991f33181de1c339a3b42f57add450d Mon Sep 17 00:00:00 2001 From: ELtd <36067295+ELtd@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:12:24 +0200 Subject: [PATCH 188/194] use specific language_name in a ChildForm Collection #728 #729 --- src/Kris/LaravelFormBuilder/Fields/ChildFormType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php index 49513c9d..5c35689a 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php @@ -116,7 +116,7 @@ protected function getClassFromOptions() $options = [ 'model' => $this->getOption($this->valueProperty) ?: $this->parent->getModel(), 'name' => $this->name, - 'language_name' => $this->parent->getLanguageName(), + 'language_name' => $this->getOption('language_name') ?: $this->parent->getLanguageName(), 'translation_template' => $this->parent->getTranslationTemplate(), ]; From 6cefaa91797b4be9e93e1b531ec2fa53276bd33d Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Sun, 27 Oct 2024 21:53:11 +0100 Subject: [PATCH 189/194] FormBuilder::create() @template @return --- src/Kris/LaravelFormBuilder/FormBuilder.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/FormBuilder.php b/src/Kris/LaravelFormBuilder/FormBuilder.php index 119e665a..99672f9e 100644 --- a/src/Kris/LaravelFormBuilder/FormBuilder.php +++ b/src/Kris/LaravelFormBuilder/FormBuilder.php @@ -56,10 +56,11 @@ public function fireEvent($event) /** * Create a Form instance. * - * @param string $formClass The name of the class that inherits \Kris\LaravelFormBuilder\Form. - * @param array $options|null - * @param array $data|null - * @return Form + * @template T + * @param class-string $formClass + * @param array $options + * @param array $data + * @return T */ public function create($formClass, array $options = [], array $data = []) { From 0dbb8033e41067bf3cb921a111215b78f957a7eb Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Mon, 28 Oct 2024 02:24:34 +0100 Subject: [PATCH 190/194] +phpstan generics --- phpstan-extension.neon | 5 ++ .../Fields/ChildFormType.php | 8 ++ .../LaravelFormBuilder/Fields/ChoiceType.php | 5 +- .../Fields/CollectionType.php | 7 ++ .../LaravelFormBuilder/Fields/ParentType.php | 7 ++ .../Fields/RepeatedType.php | 3 + .../PhpStan/FormGetFieldExtension.php | 73 +++++++++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 phpstan-extension.neon create mode 100644 src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php diff --git a/phpstan-extension.neon b/phpstan-extension.neon new file mode 100644 index 00000000..9f989f83 --- /dev/null +++ b/phpstan-extension.neon @@ -0,0 +1,5 @@ +services: + - + class: Kris\LaravelFormBuilder\PhpStan\FormGetFieldExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php index 5c35689a..1be9df76 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php @@ -4,11 +4,17 @@ use Kris\LaravelFormBuilder\Form; +/** + * @template TFormType of Form + * + * @extends ParentType + */ class ChildFormType extends ParentType { /** * @var Form + * @phpstan-var TFormType */ protected $form; @@ -22,6 +28,7 @@ protected function getTemplate() /** * @return Form + * @phpstan-return TFormType */ public function getForm() { @@ -96,6 +103,7 @@ protected function createChildren() /** * @return Form + * @phpstan-return TFormType * @throws \Exception */ protected function getClassFromOptions() diff --git a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php index 026d4a46..85ee0fcf 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php @@ -4,6 +4,9 @@ use Illuminate\Support\Arr; +/** + * @extends ParentType + */ class ChoiceType extends ParentType { /** @@ -76,7 +79,7 @@ protected function createChildren() if (($data_override = $this->getOption('data_override')) && $data_override instanceof \Closure) { $this->options['choices'] = $data_override($this->options['choices'], $this); } - + $this->children = []; $this->determineChoiceField(); diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index f51b679f..a06d2989 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -5,12 +5,18 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +/** + * @template TType of FormField + * + * @extends ParentType + */ class CollectionType extends ParentType { /** * Contains template for a collection element. * * @var FormField + * @phpstan-var TType */ protected $proto; @@ -49,6 +55,7 @@ protected function getDefaults() * Get the prototype object. * * @return FormField + * @phpstan-return TType * @throws \Exception */ public function prototype() diff --git a/src/Kris/LaravelFormBuilder/Fields/ParentType.php b/src/Kris/LaravelFormBuilder/Fields/ParentType.php index 80352d0c..1b92d18d 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ParentType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ParentType.php @@ -5,11 +5,15 @@ use Illuminate\Support\Arr; use Kris\LaravelFormBuilder\Form; +/** + * @template TChildType of FormField + */ abstract class ParentType extends FormField { /** * @var FormField[] + * @phpstan-var TChildType[] */ protected $children; @@ -64,6 +68,7 @@ public function render(array $options = [], $showLabel = true, $showField = true * Get all children of the choice field. * * @return mixed + * @phpstan-return TChildType[] */ public function getChildren() { @@ -74,6 +79,7 @@ public function getChildren() * Get a child of the choice field. * * @return mixed + * @phpstan-return ?TChildType */ public function getChild($key) { @@ -145,6 +151,7 @@ public function isRendered() * * @param string $name * @return FormField + * @phpstan-return TChildType */ public function __get($name) { diff --git a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php index 0afaff78..2c46055f 100644 --- a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php +++ b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php @@ -4,6 +4,9 @@ use Illuminate\Support\Arr; +/** + * @extends ParentType + */ class RepeatedType extends ParentType { diff --git a/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php b/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php new file mode 100644 index 00000000..f03bb4ce --- /dev/null +++ b/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php @@ -0,0 +1,73 @@ +getName() == 'getField'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { + return $this->getTypeFromMethodCallGetField($methodReflection, $methodCall, $scope); + } + + protected function getTypeFromMethodCallGetField(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->getArgs()) < 1) { + return null; + } + + $fieldNameArg = $methodCall->getArgs()[0]->value; + if (!($fieldNameArg instanceof String_)) { + return null; + } + + $fieldName = $fieldNameArg->value; + + $calledOnType = $scope->getType($methodCall->var); + assert($calledOnType instanceof TypeWithClassName); + $formClass = $calledOnType->getClassName(); + + $formClassReflection = $this->reflectionProvider->getClass($formClass); + + if (!$formClassReflection->hasProperty($fieldName)) { + return null; + } + + $formClassFieldProperty = $formClassReflection->getProperty($fieldName, $scope); + if (!($formClassFieldProperty instanceof AnnotationPropertyReflection)) { + return null; + } + + return $formClassFieldProperty->getReadableType(); + } + +} From b01ef09fbb143a03b080c03ba180ce09b1a85db3 Mon Sep 17 00:00:00 2001 From: Rudie Dirkx <168024+rudiedirkx@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:03:36 +0100 Subject: [PATCH 191/194] field (css class) defaults+ (#731) --- .../LaravelFormBuilder/Fields/FormField.php | 8 +++-- tests/Fields/CheckableTypeTest.php | 2 +- tests/Fields/FormFieldTest.php | 29 ++++++++++++++++--- tests/FormBuilderTestCase.php | 5 +++- tests/FormTest.php | 4 +-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Kris/LaravelFormBuilder/Fields/FormField.php b/src/Kris/LaravelFormBuilder/Fields/FormField.php index b5032140..2b32f79b 100644 --- a/src/Kris/LaravelFormBuilder/Fields/FormField.php +++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php @@ -406,7 +406,7 @@ protected function normalizeRules($rules) * * @param array $first first set of rules * @param array $second second set of rules - * @return array merged set of rules without duplicates + * @return array merged set of rules without duplicates */ protected function mergeRules($first, $second) { @@ -672,12 +672,16 @@ protected function addErrorClass() */ protected function setDefaultOptions(array $options = []) { + // Get default defaults from config (eg. defaults.field_class) $this->options = $this->formHelper->mergeOptions($this->allDefaults(), $this->getDefaults()); - $this->options = $this->prepareOptions($options); + // Maybe overwrite with field type defaults from config (eg. defaults.checkbox.field_class) $defaults = $this->setDefaultClasses($options); $this->options = $this->formHelper->mergeOptions($this->options, $defaults); + // Add specific field classes (eg. attr.class_append) + $this->options = $this->prepareOptions($options); + $this->setupLabel(); } diff --git a/tests/Fields/CheckableTypeTest.php b/tests/Fields/CheckableTypeTest.php index 3a54156d..5d013062 100644 --- a/tests/Fields/CheckableTypeTest.php +++ b/tests/Fields/CheckableTypeTest.php @@ -21,7 +21,7 @@ public function it_creates_checkbox_field(): void $expectedOptions = $this->getDefaults( [ - 'class' => null, + 'class' => 'custom-checkbox-field-class', 'required' => 'required', 'id' => 'test', ], diff --git a/tests/Fields/FormFieldTest.php b/tests/Fields/FormFieldTest.php index 4a039cb0..a848a8eb 100644 --- a/tests/Fields/FormFieldTest.php +++ b/tests/Fields/FormFieldTest.php @@ -1,8 +1,9 @@ plainForm, $options); $renderResult = $text->render(); - $this->assertMatchesRegularExpression('/appended/', $text->getOption('attr.class')); + $this->assertMatchesRegularExpression('/\bappended\b/', $text->getOption('attr.class')); $defaultClasses = $this->config['defaults']['field_class']; $this->assertEquals('form-control appended', $text->getOption('attr.class')); @@ -126,6 +127,26 @@ public function it_appends_to_the_class_attribute_of_the_field() $this->assertStringNotContainsString('class_append', $renderResult); } + /** @test */ + public function it_appends_to_the_class_attribute_of_a_custom_classes_checkbox_field() + { + $options = [ + 'attr' => [ + 'class_append' => 'appended', + ], + ]; + + $text = new CheckableType('field_name', 'checkbox', $this->plainForm, $options); + $renderResult = $text->render(); + + $this->assertMatchesRegularExpression('/\bappended\b/', $text->getOption('attr.class')); + + $this->assertEquals('custom-checkbox-field-class appended', $text->getOption('attr.class')); + + $defaultClasses = $this->config['defaults']['field_class']; + $this->assertStringNotContainsString($defaultClasses, $text->getOption('attr.class')); + } + /** @test */ public function it_appends_to_the_class_attribute_of_the_label() { @@ -138,7 +159,7 @@ public function it_appends_to_the_class_attribute_of_the_label() $text = new InputType('field_name', 'text', $this->plainForm, $options); $renderResult = $text->render(); - $this->assertMatchesRegularExpression('/appended/', $text->getOption('label_attr.class')); + $this->assertMatchesRegularExpression('/\bappended\b/', $text->getOption('label_attr.class')); $defaultClasses = $this->config['defaults']['label_class']; $this->assertEquals('control-label appended', $text->getOption('label_attr.class')); @@ -159,7 +180,7 @@ public function it_appends_to_the_class_attribute_of_the_wrapper() $text = new InputType('field_name', 'text', $this->plainForm, $options); $renderResult = $text->render(); - $this->assertMatchesRegularExpression('/appended/', $text->getOption('wrapper.class')); + $this->assertMatchesRegularExpression('/\bappended\b/', $text->getOption('wrapper.class')); $defaultClasses = $this->config['defaults']['wrapper_class']; $this->assertEquals('form-group appended', $text->getOption('wrapper.class')); diff --git a/tests/FormBuilderTestCase.php b/tests/FormBuilderTestCase.php index 84f27d5c..48c4bc80 100644 --- a/tests/FormBuilderTestCase.php +++ b/tests/FormBuilderTestCase.php @@ -199,7 +199,10 @@ public function setUp(): void $this->validatorFactory = $this->app['validator']; $this->eventDispatcher = $this->app['events']; $this->model = new TestModel(); - $this->config = include __DIR__.'/../src/config/config.php'; + + $config = include __DIR__.'/../src/config/config.php'; + $config['defaults']['checkbox']['field_class'] = 'custom-checkbox-field-class'; + $this->config = $config; $this->formHelper = new FormHelper($this->view, $this->translator, $this->config); $this->formBuilder = new FormBuilder($this->app, $this->formHelper, $this->eventDispatcher); diff --git a/tests/FormTest.php b/tests/FormTest.php index 7a488e08..67487717 100644 --- a/tests/FormTest.php +++ b/tests/FormTest.php @@ -1230,8 +1230,8 @@ public function it_add_option_attributes_properly() $this->assertStringContainsString(' diff --git a/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php b/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php index 34fc5359..4317609a 100644 --- a/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php +++ b/src/Kris/LaravelFormBuilder/Filters/Exception/InvalidInstanceException.php @@ -13,9 +13,9 @@ */ class InvalidInstanceException extends \Exception { - public function __construct($message = "", $code = 0, Throwable $previous = null) + public function __construct($message = "", $code = 0, ?Throwable $previous = null) { $message = 'Filter object must implement ' . FilterInterface::class; parent::__construct($message, $code, $previous); } -} \ No newline at end of file +} diff --git a/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php b/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php index b8f4b5c7..91f8ba48 100644 --- a/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php +++ b/src/Kris/LaravelFormBuilder/Filters/Exception/UnableToResolveFilterException.php @@ -12,9 +12,9 @@ */ class UnableToResolveFilterException extends \Exception { - public function __construct($message = "", $code = 0, Throwable $previous = null) + public function __construct($message = "", $code = 0, ?Throwable $previous = null) { $message = "Passed filter can't be resolved."; parent::__construct($message, $code, $previous); } -} \ No newline at end of file +} diff --git a/src/Kris/LaravelFormBuilder/RulesParser.php b/src/Kris/LaravelFormBuilder/RulesParser.php index 6f7452e6..51ddf8a9 100644 --- a/src/Kris/LaravelFormBuilder/RulesParser.php +++ b/src/Kris/LaravelFormBuilder/RulesParser.php @@ -606,7 +606,7 @@ protected function parseParameters($rule, $parameter) if (strtolower($rule) == 'regex') { return [$parameter]; } - return str_getcsv($parameter); + return str_getcsv($parameter, escape: '\\'); } /** From 0ad7ffb158282f983fe59bb1ffbafbd4d5748997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20My=C5=A1ka?= Date: Wed, 9 Apr 2025 20:08:35 +0200 Subject: [PATCH 194/194] Add support for Laravel 12 (#737) --- .github/workflows/test.yml | 6 ++++-- composer.json | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ea8acd92..60f03b98 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,11 +15,13 @@ jobs: max-parallel: 6 # 12 fail-fast: false matrix: - laravel: [10, 11] + laravel: [10, 11, 12] php: ['8.2', '8.3', '8.4'] - phpunit: [10] + phpunit: [10, 11] exclude: - {laravel: 10, php: '8.4'} + - {laravel: 10, phpunit: 11} + - {laravel: 12, phpunit: 10} steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/composer.json b/composer.json index 3eb7a0ef..228ed907 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,12 @@ "require": { "php": "^8.0", "rdx/laravelcollective-html": "^6", - "illuminate/database": "^10 || ^11", - "illuminate/validation": "^10 || ^11" + "illuminate/database": "^10 || ^11 || ^12", + "illuminate/validation": "^10 || ^11 || ^12" }, "require-dev": { - "orchestra/testbench": "^8 || ^9", - "phpunit/phpunit": "^10.0" + "orchestra/testbench": "^8 || ^9 || ^10", + "phpunit/phpunit": "^10.0 || ^11.0" }, "extra": { "branch-alias": {