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/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..60f03b98
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,62 @@
+name: "Run unit tests"
+
+on:
+ - push
+ - pull_request
+
+env:
+ COMPOSER_MEMORY_LIMIT: -1
+
+jobs:
+ test:
+ name: "Lara ${{ matrix.laravel }} PHP ${{ matrix.php }} Unit ${{ matrix.phpunit }}"
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 6 # 12
+ fail-fast: false
+ matrix:
+ laravel: [10, 11, 12]
+ php: ['8.2', '8.3', '8.4']
+ 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
+ with:
+ fetch-depth: 2
+
+ - name: Setup PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: exif,json,mbstring,dom
+
+ - name: Get user-level Composer cache
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Setup Composer cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ hashFiles('**/composer.json') }}
+ restore-keys: |
+ 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 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
diff --git a/.gitignore b/.gitignore
index 984ff523..37dc081e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,11 @@ composer.phar
composer.lock
.DS_Store
.idea
+.vscode
coverage
+*.taskpaper
+NOTES.md
+/.phpunit.result.cache
+/.phpunit.cache/
+/phpunit.xml.bak
+/coverage.clover
diff --git a/.travis.yml b/.travis.yml
index 9eed5381..564d0099 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,24 @@
language: php
php:
- - 5.6
- - 7.0
- 7.1
+ - 7.2
+ - 7.3
+ - 7.4
+
+env:
+ - COMPOSER_MEMORY_LIMIT=-1
before_script:
- travis_retry composer self-update
- travis_retry composer install --prefer-source --no-interaction
script:
- - if [ "$TRAVIS_PHP_VERSION" == "hhvm" ]; then vendor/bin/phpunit; fi
- - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover; fi
+ - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi
+ - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
notifications:
email:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0799c83d..c562aea9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,51 @@
+## 1.20.0
+- Add field rules event [#491](https://github.com/kristijanhusak/laravel-form-builder/pull/491)(Thanks to [@rudiedirkx](https://github.com/rudiedirkx))
+- Escape html with `e()` to respect Htmlable [#473](https://github.com/kristijanhusak/laravel-form-builder/pull/473)(Thanks to [@rudiedirkx](https://github.com/rudiedirkx))
+- Fix `datetime_local` to `datetime-local` field constant [#483](https://github.com/kristijanhusak/laravel-form-builder/pull/483)(Thanks to [@nea](https://github.com/nea))
+- Add missing `entity` field to constants [#484](https://github.com/kristijanhusak/laravel-form-builder/pull/484)(Thanks to [@nea](https://github.com/nea))
+- Fix compatibility with Laravel 5.8 by using EventDispatcher `dispatch` method instead of `fire`
+## 1.16.0
+- Add option for form specific config. [#406](https://github.com/kristijanhusak/laravel-form-builder/pull/406) (Thanks to [@beghelli](https://github.com/beghelli))
+- Add class enum that contains all field types [#455](https://github.com/kristijanhusak/laravel-form-builder/pull/455) (Thanks to [@tresa02](https://github.com/tresa02))
+## 1.15.1
+- Fix issue [#441](https://github.com/kristijanhusak/laravel-form-builder/issues/441)
+- Fix issue [#442](https://github.com/kristijanhusak/laravel-form-builder/issues/442)
+## 1.15.0
+- Add translation template [#399](https://github.com/kristijanhusak/laravel-form-builder/pull/399) (Thanks to [@koenvu](https://github.com/koenvu))
+- Add field error class [#411](https://github.com/kristijanhusak/laravel-form-builder/pull/411) (Thanks to [@n7olkachev](https://github.com/n7olkachev))
+- Allow using different error bag per form [#414](https://github.com/kristijanhusak/laravel-form-builder/pull/414) (Thanks to [@Fellner96](https://github.com/Fellner96))
+- Get PSR-4 namespace from composer [#424](https://github.com/kristijanhusak/laravel-form-builder/pull/424) (Thanks to [@icfr](https://github.com/icfr))
+- Escape static field value [#407](https://github.com/kristijanhusak/laravel-form-builder/pull/407) (Thanks to [@beghelli](https://github.com/beghelli))
+- Fix missing field name for rule closure [#403](https://github.com/kristijanhusak/laravel-form-builder/pull/403) (Thanks to [@yemenifree](https://github.com/yemenifree))
+- Fix checking trueness of empty array in collection type [#412](https://github.com/kristijanhusak/laravel-form-builder/pull/412) (Thanks to [@kiperz](https://github.com/kiperz))
+- Fix parent type not pushing options to children [#356](https://github.com/kristijanhusak/laravel-form-builder/pull/356) (Thanks to [@pimlie](https://github.com/pimlie))
+- Use request as model when validating to properly validate collection types
+- Setup named model after attaching model to form
+- Fix custom closure interpreted as string when using html5 validation rules [#435](https://github.com/kristijanhusak/laravel-form-builder/pull/435) (Thanks to [@yarbsemaj](https://github.com/yarbsemaj))
+- Fix radio and checkbox help block position [#440](https://github.com/kristijanhusak/laravel-form-builder/pull/440) (Thanks to [@sagarnasit](https://github.com/sagarnasit))
+## 1.14.0
+- Fix php7.2 compatibility
+## 1.13.0
+- Add Laravel 5.5 support [#377](https://github.com/kristijanhusak/laravel-form-builder/pull/377) (Thanks to [@wuwx](https://github.com/wuwx))
+- Add field filters [#376](https://github.com/kristijanhusak/laravel-form-builder/pull/376) (Thanks to [@unckleg](https://github.com/unckleg))
+- Add `data_override` closure for choice type fields [#383](https://github.com/kristijanhusak/laravel-form-builder/pull/383) (Thanks to [@yemenifree](https://github.com/yemenifree))
+- Fix adding client validation attributes to non required fields [#379](https://github.com/kristijanhusak/laravel-form-builder/pull/379) (Thanks to [@koichirose](https://github.com/koichirose))
+
+## 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))
+- 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
diff --git a/NOTES.md b/NOTES.md
new file mode 100644
index 00000000..734224fc
--- /dev/null
+++ b/NOTES.md
@@ -0,0 +1,10 @@
+# laravel-form-builder
+
+#501 Refactor to use Arr class for deprecated array helpers
+- array_get
+- array_pull
+- array_set
+- array_forget
+
+- str_is
+- str_contains
diff --git a/README.md b/README.md
index 5334e523..a1e5aa5d 100644
--- a/README.md
+++ b/README.md
@@ -12,22 +12,27 @@ 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).
+
+## Bootstrap 4 support
+To use bootstrap 4 instead of bootstrap 3, install [laravel-form-builder-bs4](https://github.com/ycs77/laravel-form-builder-bs4).
## 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/).
+For detailed documentation refer to https://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 +46,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 +67,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:
@@ -81,19 +86,20 @@ Form is created in path `app/Forms/SongForm.php` with content:
namespace App\Forms;
use Kris\LaravelFormBuilder\Form;
+use Kris\LaravelFormBuilder\Field;
class SongForm extends Form
{
public function buildForm()
{
$this
- ->add('name', 'text', [
+ ->add('name', Field::TEXT, [
'rules' => 'required|min:5'
])
- ->add('lyrics', 'textarea', [
+ ->add('lyrics', Field::TEXTAREA, [
'rules' => 'max:5000'
])
- ->add('publish', 'checkbox');
+ ->add('publish', Field::CHECKBOX);
}
}
```
@@ -194,22 +200,93 @@ class SongsController extends BaseController {
```
+If you want to store a model after a form submit considerating all fields are model properties:
+
+```php
+create(\App\Forms\SongForm::class);
+ $form->redirectIfNotValid();
+
+ SongForm::create($form->getFieldValues());
+
+ // Do redirecting...
+ }
+```
+
+You can only save properties you need:
+
+```php
+create(\App\Forms\SongForm::class);
+ $form->redirectIfNotValid();
+
+ $songForm = new SongForm();
+ $songForm->fill($request->only(['name', 'artist'])->save();
+ // Do redirecting...
+ }
+```
+Or you can update any model after form submit:
+```php
+getForm($songForm);
+ $form->redirectIfNotValid();
+
+ $songForm->update($form->getFieldValues());
+
+ // Do redirecting...
+ }
+```
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 +322,48 @@ Go to `/songs/create`; above code will generate this html:
```
-### Contributing
+Or you can generate forms easier by using simple array
+```php
+createByArray([
+ [
+ 'name' => 'name',
+ 'type' => Field::TEXT,
+ ],
+ [
+ 'name' => 'lyrics',
+ 'type' => Field::TEXTAREA,
+ ],
+ [
+ 'name' => 'publish',
+ 'type' => Field::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.
diff --git a/composer.json b/composer.json
index cc3b2b18..228ed907 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,7 @@
{
"name": "kris/laravel-form-builder",
"description": "Laravel form builder - symfony like",
+ "keywords": ["laravel", "form", "builder", "symfony"],
"license": "MIT",
"authors": [
{
@@ -9,31 +10,41 @@
}
],
"require": {
- "php": ">=5.6.0",
- "laravelcollective/html": "5.*",
- "illuminate/database": "5.*@dev",
- "illuminate/validation": "5.*@dev"
+ "php": "^8.0",
+ "rdx/laravelcollective-html": "^6",
+ "illuminate/database": "^10 || ^11 || ^12",
+ "illuminate/validation": "^10 || ^11 || ^12"
},
"require-dev": {
- "phpunit/phpunit": "~5.0",
- "orchestra/testbench": "~3.4"
+ "orchestra/testbench": "^8 || ^9 || ^10",
+ "phpunit/phpunit": "^10.0 || ^11.0"
},
"extra": {
"branch-alias": {
- "dev-master": "1.6.x-dev"
+ "dev-master": "1.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Kris\\LaravelFormBuilder\\FormBuilderServiceProvider"
+ ],
+ "aliases": {
+ "FormBuilder": "Kris\\LaravelFormBuilder\\Facades\\FormBuilder"
+ }
}
},
"autoload": {
"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/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/phpunit-printer.yml b/phpunit-printer.yml
new file mode 100644
index 00000000..29fc6fd3
--- /dev/null
+++ b/phpunit-printer.yml
@@ -0,0 +1,14 @@
+options:
+ cd-printer-hide-class: false
+ cd-printer-simple-output: false
+ cd-printer-show-config: true
+ cd-printer-hide-namespace: true
+ cd-printer-anybar: false
+ cd-printer-anybar-port: 1738
+markers:
+ cd-pass: "✔︎ "
+ cd-fail: "✖ "
+ cd-error: "⚈ "
+ cd-skipped: "=> "
+ cd-incomplete: "∅ "
+ cd-risky: "⌽ "
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index e320df8f..6ac4b5e8 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,30 +1,33 @@
-
./tests/
+ ./tests/resources/views/
+ ./tests/resources/lang/
+ ./tests/Fixtures/
+ ./tests/FormBuilderTestCase.php
-
-
-
+
+
./src/Kris
-
- ./src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php
- ./src/Kris/LaravelFormBuilder/Facades/FormBuilder.php
- ./src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php
- ./src/Kris/LaravelFormBuilder/FormBuilderTrait.php
-
-
-
+
+
+ ./src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php
+ ./src/Kris/LaravelFormBuilder/Facades/FormBuilder.php
+ ./src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php
+ ./src/Kris/LaravelFormBuilder/FormBuilderTrait.php
+
+
diff --git a/src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php b/src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php
index 3956bb73..06bdce8b 100644
--- a/src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php
+++ b/src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php
@@ -4,6 +4,7 @@
use Illuminate\Console\GeneratorCommand;
use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Arr;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -109,6 +110,11 @@ protected function replaceNamespace(&$stub, $name)
if ($path) {
$namespace = str_replace('/', '\\', trim($path, '/'));
+ foreach ($this->getAutoload() as $autoloadNamespace => $autoloadPath) {
+ if (preg_match('|'.$autoloadPath.'|', $path)) {
+ $namespace = str_replace([$autoloadPath, '/'], [$autoloadNamespace, '\\'], trim($path, '/'));
+ }
+ }
}
}
@@ -127,6 +133,24 @@ protected function getStub()
return __DIR__ . '/stubs/form-class-template.stub';
}
+ /**
+ * Get psr-4 namespace.
+ *
+ * @return array
+ */
+ protected function getAutoload()
+ {
+ $composerPath = base_path('/composer.json');
+ if (! file_exists($composerPath)) {
+ return [];
+ }
+ $composer = json_decode(file_get_contents(
+ $composerPath
+ ), true);
+
+ return Arr::get($composer, 'autoload.psr-4', []);
+ }
+
/**
* Get the desired class name from the input.
*
diff --git a/src/Kris/LaravelFormBuilder/Events/AfterCollectingFieldRules.php b/src/Kris/LaravelFormBuilder/Events/AfterCollectingFieldRules.php
new file mode 100644
index 00000000..f4de5d6a
--- /dev/null
+++ b/src/Kris/LaravelFormBuilder/Events/AfterCollectingFieldRules.php
@@ -0,0 +1,54 @@
+field = $field;
+ $this->rules = $rules;
+ }
+
+ /**
+ * Return the event's field.
+ *
+ * @return FormField
+ */
+ public function getField() {
+ return $this->field;
+ }
+
+ /**
+ * Return the event's field's rules.
+ *
+ * @return Rules
+ */
+ public function getRules() {
+ return $this->rules;
+ }
+}
diff --git a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php
index 6c2d30c3..0312e2bc 100644
--- a/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php
+++ b/src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php
@@ -16,7 +16,7 @@ class AfterFormCreation
/**
* Create a new after form creation instance.
*
- * @param Form $form
+ * @param Form $form
* @return void
*/
public function __construct(Form $form) {
diff --git a/src/Kris/LaravelFormBuilder/Field.php b/src/Kris/LaravelFormBuilder/Field.php
new file mode 100644
index 00000000..bc93a644
--- /dev/null
+++ b/src/Kris/LaravelFormBuilder/Field.php
@@ -0,0 +1,40 @@
+ ['class' => null, 'id' => $this->getName()],
- 'value' => 1,
+ 'value' => self::DEFAULT_VALUE,
'checked' => null
];
}
diff --git a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php
index a172fb1f..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()
@@ -116,7 +124,8 @@ 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(),
];
if (!$this->parent->clientValidationEnabled()) {
@@ -146,6 +155,10 @@ protected function getClassFromOptions()
$class->setLanguageName($this->parent->getLanguageName());
}
+ if (!$class->getTranslationTemplate()) {
+ $class->setTranslationTemplate($this->parent->getTranslationTemplate());
+ }
+
if (!$this->parent->clientValidationEnabled()) {
$class->setClientValidationEnabled(false);
}
diff --git a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php
index 35a6bd5d..85ee0fcf 100644
--- a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php
@@ -2,6 +2,11 @@
namespace Kris\LaravelFormBuilder\Fields;
+use Illuminate\Support\Arr;
+
+/**
+ * @extends ParentType
+ */
class ChoiceType extends ParentType
{
/**
@@ -32,7 +37,7 @@ protected function determineChoiceField()
$expanded = $this->options['expanded'];
$multiple = $this->options['multiple'];
- if ($multiple) {
+ if (!$expanded && $multiple) {
$this->options['attr']['multiple'] = true;
}
@@ -43,6 +48,8 @@ protected function determineChoiceField()
if ($expanded && $multiple) {
return $this->choiceType = 'checkbox';
}
+
+ return $this->choiceType = 'select';
}
/**
@@ -69,6 +76,10 @@ protected function getDefaults()
*/
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();
@@ -96,12 +107,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' => ['id' => $id],
+ '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]),
@@ -142,15 +155,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 b22582ce..a06d2989 100644
--- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php
@@ -2,14 +2,21 @@
namespace Kris\LaravelFormBuilder\Fields;
+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;
@@ -38,7 +45,9 @@ protected function getDefaults()
'data' => null,
'property' => 'id',
'prototype_name' => '__NAME__',
- 'empty_row' => true
+ 'empty_row' => true,
+ 'prefer_input' => false,
+ 'empty_model' => null,
];
}
@@ -46,6 +55,7 @@ protected function getDefaults()
* Get the prototype object.
*
* @return FormField
+ * @phpstan-return TType
* @throws \Exception
*/
public function prototype()
@@ -69,6 +79,26 @@ public function getAllAttributes()
return $this->parent->getFormHelper()->mergeAttributes($this->children);
}
+ /**
+ * Allow form-specific value alters.
+ *
+ * @param array $values
+ * @return void
+ */
+ public function alterFieldValues(array &$values)
+ {
+ $stripLeft = strlen($this->getName()) + 1;
+ $stripRight = 1;
+ foreach ($this->children as $child) {
+ if (method_exists($child, 'alterFieldValues')) {
+ $itemKey = substr($child->getName(), $stripLeft, -$stripRight);
+ if (isset($values[$itemKey])) {
+ $child->alterFieldValues($values[$itemKey]);
+ }
+ }
+ }
+ }
+
/**
* @inheritdoc
*/
@@ -77,7 +107,10 @@ 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());
+
+ is_array($oldInput) or $oldInput = [];
+ is_array($currentInput) or $currentInput = [];
try {
$fieldType = $this->formHelper->getFieldType($type);
@@ -91,20 +124,32 @@ protected function createChildren()
$data = $this->getOption($this->valueProperty, []);
// If no value is provided, get values from current request.
- if (count($data) === 0) {
- $data = $currentInput;
+ if (!is_null($data) && count($data) === 0) {
+ if ($this->getOption('prefer_input')) {
+ $data = $this->formatInputIntoModels($currentInput);
+ }
+ elseif ($this->getOption('empty_row')) {
+ $data = $this->formatInputIntoModels(array_slice($currentInput, 0, 1, true));
+ }
+ else {
+ $data = [];
+ }
}
-
- // Needs to have more than 1 item because 1 is rendered by default.
- // This overrides current request in situations when validation fails.
- if (count($oldInput) > 1) {
- $data = $oldInput;
+ // Or if the current request input is preferred over original data.
+ elseif ($this->getOption('prefer_input') && count($currentInput)) {
+ $data = $this->formatInputIntoModels($currentInput, $data ?? []);
}
if ($data instanceof Collection) {
$data = $data->all();
}
+ // Needs to have more than 1 item because 1 is rendered by default.
+ // This overrides current request in situations when validation fails.
+ if ($oldInput && count($oldInput) > 1) {
+ $data = $this->formatInputIntoModels($oldInput, $data ?? []);
+ }
+
$field = new $fieldType($this->name, $type, $this->parent, $this->getOption('options'));
if ($this->getOption('prototype')) {
@@ -113,7 +158,7 @@ protected function createChildren()
if (!$data || empty($data)) {
if ($this->getOption('empty_row')) {
- return $this->children[] = $this->setupChild(clone $field, '[0]');
+ return $this->children[] = $this->setupChild(clone $field, '[0]', $this->makeEmptyRowValue());
}
return $this->children = [];
@@ -128,6 +173,58 @@ protected function createChildren()
foreach ($data as $key => $val) {
$this->children[] = $this->setupChild(clone $field, '['.$key.']', $val);
}
+
+ return $this->children;
+ }
+
+ protected function makeEmptyRowValue()
+ {
+ $empty = $this->getOption('empty_row');
+ return $empty === true ? $this->makeNewEmptyModel() : $empty;
+ }
+
+ protected function makeNewEmptyModel()
+ {
+ return value($this->getOption('empty_model'));
+ }
+
+ protected function formatInputIntoModels(array $input, array $originalData = [])
+ {
+ if (!$this->getOption('empty_model')) {
+ return $input;
+ }
+
+ $newData = [];
+ foreach ($input as $k => $inputItem) {
+ if (is_array($inputItem)) {
+ $newData[$k] = $this->formatInputIntoModel($originalData[$k] ?? $this->makeNewEmptyModel(), $inputItem);
+ }
+ else {
+ $newData[$k] = $inputItem;
+ }
+ }
+
+ 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;
}
/**
@@ -144,9 +241,13 @@ 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'))]
);
+ if (isset($firstFieldOptions['label'])) {
+ $firstFieldOptions['label'] = value($firstFieldOptions['label'], $value, $field);
+ }
+
$field->setName($newFieldName);
$field->setOptions($firstFieldOptions);
@@ -159,7 +260,6 @@ protected function setupChild(FormField $field, $name, $value = null)
$field->setValue($value);
-
return $field;
}
@@ -171,7 +271,7 @@ protected function setupChild(FormField $field, $name, $value = null)
*/
protected function generatePrototype(FormField $field)
{
- $value = $field instanceof ChildFormType ? false : null;
+ $value = $this->makeNewEmptyModel();
$field->setOption('is_prototype', true);
$field = $this->setupChild($field, $this->getPrototypeName(), $value);
diff --git a/src/Kris/LaravelFormBuilder/Fields/EntityType.php b/src/Kris/LaravelFormBuilder/Fields/EntityType.php
index 29636a18..56d87baa 100644
--- a/src/Kris/LaravelFormBuilder/Fields/EntityType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/EntityType.php
@@ -1,6 +1,6 @@
getKeyName();
}
-
+
if ($queryBuilder instanceof \Closure) {
- $data = $queryBuilder($entity);
+ $data = $queryBuilder($entity, $this->parent);
} else {
$data = $entity;
}
- $data = $this->pluck($value, $key, $data);
+ if ($value instanceof \Closure) {
+ $data = $this->get($data);
+ } else {
+ $data = $this->pluck($value, $key, $data);
+ }
if ($data instanceof Collection) {
$data = $data->all();
}
+ if ($value instanceof \Closure) {
+ $part = [];
+ foreach ($data as $item) {
+ $part[$item->__get($key)] = $value($item);
+ }
+
+ $data = $part;
+ }
+
$this->options['choices'] = $data;
return parent::createChildren();
@@ -90,5 +103,29 @@ 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)
+ {
+ if (!is_object($data)) {
+ return $data;
+ }
+
+ if (method_exists($data, 'get') || $data instanceof Model) {
+ //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 ae63a815..2b32f79b 100644
--- a/src/Kris/LaravelFormBuilder/Fields/FormField.php
+++ b/src/Kris/LaravelFormBuilder/Fields/FormField.php
@@ -2,11 +2,14 @@
namespace Kris\LaravelFormBuilder\Fields;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+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;
-use Kris\LaravelFormBuilder\RulesParser;
-use Illuminate\Database\Eloquent\Collection;
+use Kris\LaravelFormBuilder\Rules;
/**
* Class FormField
@@ -84,6 +87,27 @@ abstract class FormField
*/
protected $valueClosure = null;
+ /**
+ * Array of filters key(alias/name) => objects.
+ *
+ * @var array
+ */
+ protected $filters = [];
+
+ /**
+ * Raw/unfiltered field value.
+ *
+ * @var mixed $rawValues
+ */
+ protected $rawValue;
+
+ /**
+ * Override filters with same alias/name for field.
+ *
+ * @var bool
+ */
+ protected $filtersOverride = false;
+
/**
* @param string $name
* @param string $type
@@ -99,6 +123,7 @@ public function __construct($name, $type, Form $parent, array $options = [])
$this->setTemplate();
$this->setDefaultOptions($options);
$this->setupValue();
+ $this->initFilters();
}
@@ -117,7 +142,13 @@ protected function setupValue()
}
if (($value === null || $value instanceof \Closure) && !$isChild) {
- $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name));
+ if ($this instanceof EntityType) {
+ $attributeName = $this->name;
+ } else {
+ $attributeName = $this->getOption('value_property', $this->name);
+ }
+
+ $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $attributeName));
} elseif (!$isChild) {
$this->hasDefault = true;
}
@@ -181,7 +212,9 @@ public function render(array $options = [], $showLabel = true, $showField = true
'options' => $this->options,
'showLabel' => $showLabel,
'showField' => $showField,
- 'showError' => $showError
+ 'showError' => $showError,
+ 'errorBag' => $this->parent->getErrorBag(),
+ 'translationTemplate' => $this->parent->getTranslationTemplate(),
]
)->render();
}
@@ -191,7 +224,8 @@ public function render(array $options = [], $showLabel = true, $showField = true
*
* @return array
*/
- protected function getRenderData() {
+ protected function getRenderData()
+ {
return [];
}
@@ -205,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)) {
- return array_get($model, $transformedName);
}
+
+ if (is_array($model)) {
+ return Arr::get($model, $transformedName);
+ }
+
+ throw new \InvalidArgumentException('Invalid model given to field');
}
/**
@@ -229,23 +274,26 @@ protected function transformKey($key)
* Prepare options for rendering.
*
* @param array $options
- * @return array
+ * @return array The parsed options
*/
protected function prepareOptions(array $options = [])
{
$helper = $this->formHelper;
- $rulesParser = new RulesParser($this);
+
+ $this->options = $this->prepareRules($options);
+ $this->options = $helper->mergeOptions($this->options, $options);
+
+ $rulesParser = $helper->createRulesParser($this);
$rules = $this->getOption('rules');
$parsedRules = $rules ? $rulesParser->parse($rules) : [];
- $this->options = $helper->mergeOptions($this->options, $options);
foreach (['attr', 'label_attr', 'wrapper'] as $appendable) {
// Append values to the 'class' attribute
if ($this->getOption("{$appendable}.class_append")) {
// Combine the current class attribute with the appends
$append = $this->getOption("{$appendable}.class_append");
- $classAttribute = $this->getOption("{$appendable}.class", '').' '.$append;
+ $classAttribute = $this->getOption("{$appendable}.class", '') . ' ' . $append;
$this->setOption("{$appendable}.class", $classAttribute);
// Then remove the class_append option to prevent it from showing up as an attribute in the HTML
@@ -254,7 +302,7 @@ protected function prepareOptions(array $options = [])
}
if ($this->getOption('attr.multiple') && !$this->getOption('tmp.multipleBracesSet')) {
- $this->name = $this->name.'[]';
+ $this->name = $this->name . '[]';
$this->setOption('tmp.multipleBracesSet', true);
}
@@ -264,20 +312,25 @@ protected function prepareOptions(array $options = [])
if ($this->getOption('required') === true || isset($parsedRules['required'])) {
$lblClass = $this->getOption('label_attr.class', '');
- $requiredClass = $helper->getConfig('defaults.required_class', 'required');
+ $requiredClass = $this->getConfig('defaults.required_class', 'required');
- if (! str_contains($lblClass, $requiredClass)) {
- $lblClass .= ' '.$requiredClass;
+ if (!Str::contains($lblClass, $requiredClass)) {
+ $lblClass .= ' ' . $requiredClass;
$this->setOption('label_attr.class', $lblClass);
}
if ($this->parent->clientValidationEnabled()) {
$this->setOption('attr.required', 'required');
+ }
- if ($parsedRules) {
- $attrs = $this->getOption('attr') + $parsedRules;
- $this->setOption('attr', $attrs);
- }
+ if (isset($parsedRules['required'])) {
+ unset($parsedRules['required']);
+ }
+ }
+
+ if ($this->parent->clientValidationEnabled() && $parsedRules) {
+ foreach($parsedRules as $rule => $param){
+ $this->setOption('attr.' . $rule, $param);
}
}
@@ -294,6 +347,73 @@ protected function prepareOptions(array $options = [])
return $this->options;
}
+ /**
+ * Normalize and merge rules.
+ * @param array $sourceOptions
+ * @return array
+ */
+ protected function prepareRules(array &$sourceOptions = [])
+ {
+ $options = $this->options;
+
+ // Normalize rules
+ if (array_key_exists('rules_append', $sourceOptions)) {
+ $sourceOptions['rules_append'] = $this->normalizeRules($sourceOptions['rules_append']);
+ }
+
+ if (array_key_exists('rules', $sourceOptions)) {
+ $sourceOptions['rules'] = $this->normalizeRules($sourceOptions['rules']);
+ }
+
+ if (array_key_exists('rules', $options)) {
+ $options['rules'] = $this->normalizeRules($options['rules']);
+ }
+
+
+ // Append rules
+ if ($rulesToBeAppended = Arr::pull($sourceOptions, 'rules_append')) {
+ $mergedRules = $this->mergeRules($options['rules'], $rulesToBeAppended);
+ $options['rules'] = $mergedRules;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Normalize the the given rule expression to an array.
+ * @param mixed $rules
+ * @return array
+ */
+ protected function normalizeRules($rules)
+ {
+ if (empty($rules)) {
+ return [];
+ }
+
+ if (is_string($rules)) {
+ return explode('|', $rules);
+ }
+
+ if (is_array($rules)) {
+ return array_values(array_unique(Arr::flatten($rules), SORT_REGULAR));
+ }
+
+ 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.
*
@@ -346,7 +466,7 @@ public function getOptions()
*/
public function getOption($option, $default = null)
{
- return array_get($this->options, $option, $default);
+ return Arr::get($this->options, $option, $default);
}
/**
@@ -371,7 +491,7 @@ public function setOptions($options)
*/
public function setOption($name, $value)
{
- array_set($this->options, $name, $value);
+ Arr::set($this->options, $name, $value);
return $this;
}
@@ -419,6 +539,18 @@ public function isRendered()
return $this->rendered;
}
+ /**
+ * Marks the view as rendered.
+ *
+ * @return $this
+ */
+ public function setRendered()
+ {
+ $this->rendered = true;
+
+ return $this;
+ }
+
/**
* Default options for field.
*
@@ -437,18 +569,20 @@ protected function getDefaults()
private function allDefaults()
{
return [
- 'wrapper' => ['class' => $this->formHelper->getConfig('defaults.wrapper_class')],
- 'attr' => ['class' => $this->formHelper->getConfig('defaults.field_class')],
- 'help_block' => ['text' => null, 'tag' => 'p', 'attr' => [
- 'class' => $this->formHelper->getConfig('defaults.help_block_class')
- ]],
+ 'wrapper' => ['class' => $this->getConfig('defaults.wrapper_class')],
+ 'attr' => ['class' => $this->getConfig('defaults.field_class')],
+ 'help_block' => [
+ 'text' => null,
+ 'tag' => $this->getConfig('defaults.help_block_tag', 'p'),
+ 'attr' => ['class' => $this->getConfig('defaults.help_block_class')],
+ ],
'value' => null,
'default_value' => null,
'label' => null,
'label_show' => true,
'is_child' => false,
- 'label_attr' => ['class' => $this->formHelper->getConfig('defaults.label_class')],
- 'errors' => ['class' => $this->formHelper->getConfig('defaults.error_class')],
+ 'label_attr' => ['class' => $this->getConfig('defaults.label_class')],
+ 'errors' => ['class' => $this->getConfig('defaults.error_class')],
'rules' => [],
'error_messages' => []
];
@@ -496,7 +630,7 @@ public function setValue($value)
*/
private function setTemplate()
{
- $this->template = $this->formHelper->getConfig($this->getTemplate(), $this->getTemplate());
+ $this->template = $this->getConfig($this->getTemplate(), $this->getTemplate());
}
/**
@@ -506,20 +640,31 @@ private function setTemplate()
*/
protected function addErrorClass()
{
- $errors = $this->parent->getRequest()->session()->get('errors');
+ $errors = [];
+ if ($this->parent->getRequest()->hasSession()) {
+ $errors = $this->parent->getRequest()->session()->get('errors');
+ }
+ $errorBag = $this->parent->getErrorBag();
- if ($errors && $errors->has($this->getNameKey())) {
- $errorClass = $this->formHelper->getConfig('defaults.wrapper_error_class');
+ if ($errors && $errors->hasBag($errorBag) && $errors->getBag($errorBag)->has($this->getNameKey())) {
+ $fieldErrorClass = $this->getConfig('defaults.field_error_class');
+ $fieldClass = $this->getOption('attr.class');
+
+ if ($fieldErrorClass && !Str::contains($fieldClass, $fieldErrorClass)) {
+ $fieldClass .= ' ' . $fieldErrorClass;
+ $this->setOption('attr.class', $fieldClass);
+ }
+
+ $wrapperErrorClass = $this->getConfig('defaults.wrapper_error_class');
$wrapperClass = $this->getOption('wrapper.class');
- if ($this->getOption('wrapper') && !str_contains($wrapperClass, $errorClass)) {
- $wrapperClass .= ' ' . $errorClass;
+ if ($wrapperErrorClass && $this->getOption('wrapper') && !Str::contains($wrapperClass, $wrapperErrorClass)) {
+ $wrapperClass .= ' ' . $wrapperErrorClass;
$this->setOption('wrapper.class', $wrapperClass);
}
}
}
-
/**
* Merge all defaults with field specific defaults and set template if passed.
*
@@ -527,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();
}
@@ -544,18 +693,18 @@ protected function setDefaultOptions(array $options = [])
*/
protected function setDefaultClasses(array $options = [])
{
- $wrapper_class = $this->formHelper->getConfig('defaults.' . $this->type . '.wrapper_class', '');
- $label_class = $this->formHelper->getConfig('defaults.' . $this->type . '.label_class', '');
- $field_class = $this->formHelper->getConfig('defaults.' . $this->type . '.field_class', '');
+ $wrapper_class = $this->getConfig('defaults.' . $this->type . '.wrapper_class', '');
+ $label_class = $this->getConfig('defaults.' . $this->type . '.label_class', '');
+ $field_class = $this->getConfig('defaults.' . $this->type . '.field_class', '');
$defaults = [];
- if ($wrapper_class && !array_get($options, 'wrapper.class')) {
+ if ($wrapper_class && !Arr::get($options, 'wrapper.class')) {
$defaults['wrapper']['class'] = $wrapper_class;
}
- if ($label_class && !array_get($options, 'label_attr.class')) {
+ if ($label_class && !Arr::get($options, 'label_attr.class')) {
$defaults['label_attr']['class'] = $label_class;
}
- if ($field_class && !array_get($options, 'attr.class')) {
+ if ($field_class && !Arr::get($options, 'attr.class')) {
$defaults['attr']['class'] = $field_class;
}
return $defaults;
@@ -572,7 +721,13 @@ protected function setupLabel()
return;
}
- if ($langName = $this->parent->getLanguageName()) {
+ if ($template = $this->parent->getTranslationTemplate()) {
+ $label = str_replace(
+ ['{name}', '{type}'],
+ [$this->getRealName(), 'label'],
+ $template
+ );
+ } elseif ($langName = $this->parent->getLanguageName()) {
$label = sprintf('%s.%s', $langName, $this->getRealName());
} else {
$label = $this->getRealName();
@@ -617,22 +772,34 @@ public function disable()
*/
public function enable()
{
- array_forget($this->options, 'attr.disabled');
+ Arr::forget($this->options, 'attr.disabled');
return $this;
}
+ /**
+ * Whether this field is disabled.
+ *
+ * @return bool
+ */
+ public function isDisabled()
+ {
+ $disabled = $this->getOption('attr.disabled');
+
+ return $disabled !== null && $disabled !== false;
+ }
+
/**
* Get validation rules for a field if any with label for attributes.
*
- * @return array|null
+ * @return Rules
*/
public function getValidationRules()
{
$rules = $this->getOption('rules', []);
$name = $this->getNameKey();
$messages = $this->getOption('error_messages', []);
- $formName = $this->formHelper->transformToDotSyntax($this->parent->getName());
+ $formName = $this->parent->getNameKey();
if ($messages && $formName) {
$newMessages = [];
@@ -644,14 +811,14 @@ public function getValidationRules()
}
if (!$rules) {
- return [];
+ return (new Rules([]))->setFieldName($this->getNameKey());
}
- return [
- 'rules' => [$name => $rules],
- 'attributes' => [$name => $this->getOption('label')],
- 'error_messages' => $messages
- ];
+ return (new Rules(
+ [$name => $rules],
+ [$name => $this->getOption('label')],
+ $messages
+ ))->setFieldName($this->getNameKey());
}
/**
@@ -661,7 +828,7 @@ public function getValidationRules()
*/
public function getAllAttributes()
{
- return [$this->getNameKey()];
+ return $this->isDisabled() ? [] : [$this->getNameKey()];
}
/**
@@ -695,4 +862,198 @@ 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->setFiltersOverride(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->getFiltersOverride()) {
+ 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()] = $filterObj;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Method used to set Unfiltered/Unmutated field value.
+ * Method is called before field value mutating starts - request value filtering.
+ *
+ * @param mixed $value
+ *
+ * @return \Kris\LaravelFormBuilder\Fields\FormField
+ */
+ public function setRawValue($value)
+ {
+ $this->rawValue = $value;
+ return $this;
+ }
+
+ /**
+ * Returns unfiltered raw value of field.
+ *
+ * @return mixed
+ */
+ public function getRawValue()
+ {
+ return $this->rawValue;
+ }
+
+ /**
+ * Get config from the form.
+ *
+ * @return mixed
+ */
+ private function getConfig($key = null, $default = null)
+ {
+ return $this->parent->getConfig($key, $default);
+ }
}
diff --git a/src/Kris/LaravelFormBuilder/Fields/ParentType.php b/src/Kris/LaravelFormBuilder/Fields/ParentType.php
index 4a283bd0..1b92d18d 100644
--- a/src/Kris/LaravelFormBuilder/Fields/ParentType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/ParentType.php
@@ -2,13 +2,18 @@
namespace Kris\LaravelFormBuilder\Fields;
+use Illuminate\Support\Arr;
use Kris\LaravelFormBuilder\Form;
+/**
+ * @template TChildType of FormField
+ */
abstract class ParentType extends FormField
{
/**
* @var FormField[]
+ * @phpstan-var TChildType[]
*/
protected $children;
@@ -28,7 +33,7 @@ abstract protected function createChildren();
*/
public function __construct($name, $type, Form $parent, array $options = [])
{
- parent::__construct($name, $type, $parent, $options);
+ parent::__construct($name, $type, $parent, $options + ['copy_options_to_children' => true]);
// If there is default value provided and setValue was not triggered
// in the parent call, make sure we generate child elements.
if ($this->hasDefault) {
@@ -40,7 +45,7 @@ public function __construct($name, $type, Form $parent, array $options = [])
/**
* @param mixed $val
*
- * @return ChildFormType
+ * @return $this
*/
public function setValue($val)
{
@@ -63,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()
{
@@ -73,10 +79,11 @@ public function getChildren()
* Get a child of the choice field.
*
* @return mixed
+ * @phpstan-return ?TChildType
*/
public function getChild($key)
{
- return array_get($this->children, $key);
+ return Arr::get($this->children, $key);
}
/**
@@ -93,6 +100,38 @@ public function removeChild($key)
return $this;
}
+ /**
+ * @inheritdoc
+ */
+ public function setOption($name, $value)
+ {
+ parent::setOption($name, $value);
+
+ if ($this->options['copy_options_to_children']) {
+ foreach ((array) $this->children as $key => $child) {
+ $this->children[$key]->setOption($name, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setOptions($options)
+ {
+ parent::setOptions($options);
+
+ if ($this->options['copy_options_to_children']) {
+ foreach ((array) $this->children as $key => $child) {
+ $this->children[$key]->setOptions($options);
+ }
+ }
+
+ return $this;
+ }
+
/**
* @inheritdoc
*/
@@ -112,6 +151,7 @@ public function isRendered()
*
* @param string $name
* @return FormField
+ * @phpstan-return TChildType
*/
public function __get($name)
{
@@ -142,9 +182,11 @@ public function __clone()
*/
public function disable()
{
+ parent::disable();
foreach ($this->children as $field) {
$field->disable();
}
+ return $this;
}
/**
@@ -152,9 +194,11 @@ public function disable()
*/
public function enable()
{
+ parent::enable();
foreach ($this->children as $field) {
$field->enable();
}
+ return $this;
}
/**
@@ -164,7 +208,7 @@ public function getValidationRules()
{
$rules = parent::getValidationRules();
$childrenRules = $this->formHelper->mergeFieldsRules($this->children);
- return array_replace_recursive($rules, $childrenRules);
+ return $rules->append($childrenRules);
}
}
diff --git a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php
index 6e9c34fc..2c46055f 100644
--- a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php
@@ -2,6 +2,11 @@
namespace Kris\LaravelFormBuilder\Fields;
+use Illuminate\Support\Arr;
+
+/**
+ * @extends ParentType
+ */
class RepeatedType extends ParentType
{
@@ -42,6 +47,8 @@ public function getAllAttributes()
*/
protected function createChildren()
{
+ $this->prepareOptions();
+
$firstName = $this->getRealName();
$secondName = $this->getOption('second_name');
@@ -49,14 +56,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/src/Kris/LaravelFormBuilder/Fields/SelectType.php b/src/Kris/LaravelFormBuilder/Fields/SelectType.php
index a0d4f93b..40f82719 100644
--- a/src/Kris/LaravelFormBuilder/Fields/SelectType.php
+++ b/src/Kris/LaravelFormBuilder/Fields/SelectType.php
@@ -27,6 +27,7 @@ public function getDefaults()
{
return [
'choices' => [],
+ 'option_attributes' => [],
'empty_value' => null,
'selected' => null
];
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..5594861b
--- /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($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', $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/Lowercase.php b/src/Kris/LaravelFormBuilder/Filters/Collection/Lowercase.php
new file mode 100644
index 00000000..0aa79d73
--- /dev/null
+++ b/src/Kris/LaravelFormBuilder/Filters/Collection/Lowercase.php
@@ -0,0 +1,99 @@
+
+ */
+class Lowercase 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\Lowercase
+ *
+ * @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 'Lowercase';
+ }
+}
\ 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..c1d87633
--- /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 array $options
+ */
+ public function __construct($options = [])
+ {
+ 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/StripNewlines.php b/src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php
new file mode 100644
index 00000000..1eeed785
--- /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(["\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..81050ac1
--- /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 array $options
+ */
+ public function __construct($options = [])
+ {
+ 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 = [$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, '
+= Form::customLabel($name, $options['label'], $options['label_attr']) ?>