├── .editorconfig ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── NOTES.md ├── README.md ├── README_OLD.md ├── composer.json ├── phpstan-extension.neon ├── phpunit-printer.yml ├── phpunit.xml ├── src ├── Kris │ └── LaravelFormBuilder │ │ ├── Console │ │ ├── FormGenerator.php │ │ ├── FormMakeCommand.php │ │ └── stubs │ │ │ └── form-class-template.stub │ │ ├── Events │ │ ├── AfterCollectingFieldRules.php │ │ ├── AfterFieldCreation.php │ │ ├── AfterFormCreation.php │ │ ├── AfterFormValidation.php │ │ └── BeforeFormValidation.php │ │ ├── Facades │ │ └── FormBuilder.php │ │ ├── Field.php │ │ ├── Fields │ │ ├── ButtonGroupType.php │ │ ├── ButtonType.php │ │ ├── CheckableType.php │ │ ├── ChildFormType.php │ │ ├── ChoiceType.php │ │ ├── CollectionType.php │ │ ├── EntityType.php │ │ ├── FormField.php │ │ ├── InputType.php │ │ ├── ParentType.php │ │ ├── RepeatedType.php │ │ ├── SelectType.php │ │ ├── StaticType.php │ │ └── TextareaType.php │ │ ├── Filters │ │ ├── Collection │ │ │ ├── BaseName.php │ │ │ ├── HtmlEntities.php │ │ │ ├── Integer.php │ │ │ ├── Lowercase.php │ │ │ ├── PregReplace.php │ │ │ ├── StripNewlines.php │ │ │ ├── StripTags.php │ │ │ ├── Trim.php │ │ │ ├── Uppercase.php │ │ │ └── XSS.php │ │ ├── Exception │ │ │ ├── FilterAlreadyBindedException.php │ │ │ ├── InvalidInstanceException.php │ │ │ └── UnableToResolveFilterException.php │ │ ├── FilterInterface.php │ │ └── FilterResolver.php │ │ ├── Form.php │ │ ├── FormBuilder.php │ │ ├── FormBuilderServiceProvider.php │ │ ├── FormBuilderTrait.php │ │ ├── FormHelper.php │ │ ├── PhpStan │ │ └── FormGetFieldExtension.php │ │ ├── Rules.php │ │ ├── RulesParser.php │ │ └── Traits │ │ └── ValidatesWhenResolved.php ├── config │ └── config.php ├── helpers.php └── views │ ├── button.php │ ├── buttongroup.php │ ├── checkbox.php │ ├── child_form.php │ ├── choice.php │ ├── collection.php │ ├── errors.php │ ├── form.php │ ├── help_block.php │ ├── label.php │ ├── radio.php │ ├── repeated.php │ ├── select.php │ ├── static.php │ ├── text.php │ └── textarea.php └── tests ├── .gitkeep ├── Console └── FormGeneratorTest.php ├── Fields ├── ButtonGroupTypeTest.php ├── ButtonTypeTest.php ├── CheckableTypeTest.php ├── ChildFormTypeTest.php ├── ChoiceTypeTest.php ├── CollectionTypeTest.php ├── EntityTypeTest.php ├── FormFieldTest.php ├── InputTypeTest.php ├── RepeatedTypeTest.php ├── SelectTypeTest.php ├── StaticTypeTest.php └── TextareaTypeTest.php ├── Filters └── FilterResolverTest.php ├── Fixtures ├── TestController.php └── TestForm.php ├── FormBuilderServiceProviderTest.php ├── FormBuilderTest.php ├── FormBuilderTestCase.php ├── FormBuilderValidationTest.php ├── FormHelperTest.php ├── FormTest.php ├── RulesParserTest.php └── resources ├── lang └── en │ └── validation.php └── views └── test-label.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.php] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = space 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Run unit tests" 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | env: 8 | COMPOSER_MEMORY_LIMIT: -1 9 | 10 | jobs: 11 | test: 12 | name: "Lara ${{ matrix.laravel }} PHP ${{ matrix.php }} Unit ${{ matrix.phpunit }}" 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 6 # 12 16 | fail-fast: false 17 | matrix: 18 | laravel: [10, 11, 12] 19 | php: ['8.2', '8.3', '8.4'] 20 | phpunit: [10, 11] 21 | exclude: 22 | - {laravel: 10, php: '8.4'} 23 | - {laravel: 10, phpunit: 11} 24 | - {laravel: 12, phpunit: 10} 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v3 28 | with: 29 | fetch-depth: 2 30 | 31 | - name: Setup PHP ${{ matrix.php }} 32 | uses: shivammathur/setup-php@v2 33 | with: 34 | php-version: ${{ matrix.php }} 35 | extensions: exif,json,mbstring,dom 36 | 37 | - name: Get user-level Composer cache 38 | id: composer-cache 39 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 40 | 41 | - name: Setup Composer cache 42 | uses: actions/cache@v3 43 | with: 44 | path: ${{ steps.composer-cache.outputs.dir }} 45 | key: composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ hashFiles('**/composer.json') }} 46 | restore-keys: | 47 | composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}-${{ env.cache-name }}- 48 | composer-${{ runner.os }}-${{ matrix.php }}-${{ matrix.laravel }}- 49 | composer-${{ runner.os }}-${{ matrix.php }}- 50 | composer-${{ runner.os }}- 51 | 52 | - name: Install composer dependencies 53 | run: composer require --no-progress --no-interaction illuminate/database:^${{ matrix.laravel }}.0 illuminate/validation:^${{ matrix.laravel }}.0 phpunit/phpunit:^${{ matrix.phpunit }}.0 54 | 55 | - name: Run unit tests 56 | run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 57 | 58 | # - name: Upload to Scrutinizer 59 | # continue-on-error: true 60 | # run: | 61 | # composer global require scrutinizer/ocular 62 | # ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .idea 6 | .vscode 7 | coverage 8 | *.taskpaper 9 | NOTES.md 10 | /.phpunit.result.cache 11 | /.phpunit.cache/ 12 | /phpunit.xml.bak 13 | /coverage.clover 14 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | remove_extra_empty_lines: true 5 | remove_php_closing_tag: true 6 | remove_trailing_whitespace: true 7 | fix_use_statements: 8 | remove_unused: true 9 | preserve_multiple: false 10 | preserve_blanklines: true 11 | order_alphabetically: true 12 | fix_php_opening_tag: true 13 | fix_linefeed: true 14 | fix_line_ending: true 15 | fix_identation_4spaces: true 16 | fix_doc_comments: true 17 | filter: 18 | excluded_paths: [tests/*, vendor/*, src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php] 19 | tools: 20 | external_code_coverage: 21 | timeout: 600 22 | php_code_coverage: false 23 | php_code_sniffer: 24 | config: 25 | standard: PSR2 26 | filter: 27 | paths: ['src'] 28 | php_loc: 29 | enabled: true 30 | excluded_dirs: [vendor, tests] 31 | php_cpd: 32 | enabled: true 33 | excluded_dirs: [vendor, tests] 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | - 7.3 7 | - 7.4 8 | 9 | env: 10 | - COMPOSER_MEMORY_LIMIT=-1 11 | 12 | before_script: 13 | - travis_retry composer self-update 14 | - travis_retry composer install --prefer-source --no-interaction 15 | 16 | script: 17 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 18 | 19 | after_script: 20 | - wget https://scrutinizer-ci.com/ocular.phar 21 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 22 | 23 | notifications: 24 | email: 25 | recipients: 26 | - husakkristijan@gmail.com 27 | on_success: never 28 | on_failure: always 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kristijan Husak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # laravel-form-builder 2 | 3 | #501 Refactor to use Arr class for deprecated array helpers 4 | - array_get 5 | - array_pull 6 | - array_set 7 | - array_forget 8 | 9 | - str_is 10 | - str_contains 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kris/laravel-form-builder", 3 | "description": "Laravel form builder - symfony like", 4 | "keywords": ["laravel", "form", "builder", "symfony"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Kristijan Husak", 9 | "email": "husakkristijan@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.0", 14 | "rdx/laravelcollective-html": "^6", 15 | "illuminate/database": "^10 || ^11 || ^12", 16 | "illuminate/validation": "^10 || ^11 || ^12" 17 | }, 18 | "require-dev": { 19 | "orchestra/testbench": "^8 || ^9 || ^10", 20 | "phpunit/phpunit": "^10.0 || ^11.0" 21 | }, 22 | "extra": { 23 | "branch-alias": { 24 | "dev-master": "1.x-dev" 25 | }, 26 | "laravel": { 27 | "providers": [ 28 | "Kris\\LaravelFormBuilder\\FormBuilderServiceProvider" 29 | ], 30 | "aliases": { 31 | "FormBuilder": "Kris\\LaravelFormBuilder\\Facades\\FormBuilder" 32 | } 33 | } 34 | }, 35 | "autoload": { 36 | "psr-0": { 37 | "Kris\\LaravelFormBuilder": "src/" 38 | }, 39 | "files": [ 40 | "src/helpers.php" 41 | ] 42 | }, 43 | "autoload-dev": { 44 | "classmap": [ 45 | "tests/" 46 | ] 47 | }, 48 | "minimum-stability": "dev", 49 | "prefer-stable": true 50 | } 51 | -------------------------------------------------------------------------------- /phpstan-extension.neon: -------------------------------------------------------------------------------- 1 | services: 2 | - 3 | class: Kris\LaravelFormBuilder\PhpStan\FormGetFieldExtension 4 | tags: 5 | - phpstan.broker.dynamicMethodReturnTypeExtension 6 | -------------------------------------------------------------------------------- /phpunit-printer.yml: -------------------------------------------------------------------------------- 1 | options: 2 | cd-printer-hide-class: false 3 | cd-printer-simple-output: false 4 | cd-printer-show-config: true 5 | cd-printer-hide-namespace: true 6 | cd-printer-anybar: false 7 | cd-printer-anybar-port: 1738 8 | markers: 9 | cd-pass: "✔︎ " 10 | cd-fail: "✖ " 11 | cd-error: "⚈ " 12 | cd-skipped: "=> " 13 | cd-incomplete: "∅ " 14 | cd-risky: "⌽ " -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | ./tests/resources/views/ 17 | ./tests/resources/lang/ 18 | ./tests/Fixtures/ 19 | ./tests/FormBuilderTestCase.php 20 | 21 | 22 | 23 | 24 | ./src/Kris 25 | 26 | 27 | ./src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php 28 | ./src/Kris/LaravelFormBuilder/Facades/FormBuilder.php 29 | ./src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php 30 | ./src/Kris/LaravelFormBuilder/FormBuilderTrait.php 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/FormGenerator.php: -------------------------------------------------------------------------------- 1 | parseFields($fields); 18 | } 19 | 20 | return '// Add fields here...'; 21 | } 22 | 23 | /** 24 | * @param string $name 25 | * @return object 26 | */ 27 | public function getClassInfo($name) 28 | { 29 | $explodedClassNamespace = explode('\\', $name); 30 | $className = array_pop($explodedClassNamespace); 31 | $fullNamespace = join('\\', $explodedClassNamespace); 32 | 33 | return (object)[ 34 | 'namespace' => $fullNamespace, 35 | 'className' => $className 36 | ]; 37 | } 38 | 39 | /** 40 | * Parse fields from string. 41 | * 42 | * @param string $fields 43 | * @return string 44 | */ 45 | protected function parseFields($fields) 46 | { 47 | $fieldsArray = explode(',', $fields); 48 | $text = '$this'."\n"; 49 | 50 | foreach ($fieldsArray as $field) { 51 | $text .= $this->prepareAdd($field, end($fieldsArray) == $field); 52 | } 53 | 54 | return $text.';'; 55 | } 56 | 57 | /** 58 | * Prepare template for single add field. 59 | * 60 | * @param string $field 61 | * @param bool $isLast 62 | * @return string 63 | */ 64 | protected function prepareAdd($field, $isLast = false) 65 | { 66 | $field = trim($field); 67 | list($name, $type) = explode(':', $field); 68 | $textArr = [ 69 | " ->add('", 70 | $name, 71 | "', '", 72 | $type, 73 | "')", 74 | ($isLast) ? "" : "\n" 75 | ]; 76 | 77 | return join('', $textArr); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php: -------------------------------------------------------------------------------- 1 | formGenerator = $formGenerator; 44 | } 45 | 46 | /** 47 | * Get the console command arguments. 48 | * 49 | * @return array 50 | */ 51 | protected function getArguments() 52 | { 53 | return array( 54 | array('name', InputArgument::REQUIRED, 'Full class name of the desired form class.'), 55 | ); 56 | } 57 | 58 | /** 59 | * Get the console command options. 60 | * 61 | * @return array 62 | */ 63 | protected function getOptions() 64 | { 65 | return array( 66 | array('fields', null, InputOption::VALUE_OPTIONAL, 'Fields for the form'), 67 | array('namespace', null, InputOption::VALUE_OPTIONAL, 'Class namespace'), 68 | array('path', null, InputOption::VALUE_OPTIONAL, 'File path') 69 | ); 70 | } 71 | 72 | /** 73 | * Replace the class name for the given stub. 74 | * 75 | * @param string $stub 76 | * @param string $name 77 | * @return string 78 | */ 79 | protected function replaceClass($stub, $name) 80 | { 81 | $formGenerator = $this->formGenerator; 82 | 83 | $stub = str_replace( 84 | '{{class}}', 85 | $formGenerator->getClassInfo($name)->className, 86 | $stub 87 | ); 88 | 89 | return str_replace( 90 | '{{fields}}', 91 | $formGenerator->getFieldsVariable($this->option('fields')), 92 | $stub 93 | ); 94 | } 95 | 96 | /** 97 | * Replace the namespace for the given stub. 98 | * 99 | * @param string $stub 100 | * @param string $name 101 | * @return $this 102 | */ 103 | protected function replaceNamespace(&$stub, $name) 104 | { 105 | $path = $this->option('path'); 106 | $namespace = $this->option('namespace'); 107 | 108 | if (!$namespace) { 109 | $namespace = $this->formGenerator->getClassInfo($name)->namespace; 110 | 111 | if ($path) { 112 | $namespace = str_replace('/', '\\', trim($path, '/')); 113 | foreach ($this->getAutoload() as $autoloadNamespace => $autoloadPath) { 114 | if (preg_match('|'.$autoloadPath.'|', $path)) { 115 | $namespace = str_replace([$autoloadPath, '/'], [$autoloadNamespace, '\\'], trim($path, '/')); 116 | } 117 | } 118 | } 119 | } 120 | 121 | $stub = str_replace('{{namespace}}', $namespace, $stub); 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * Get the stub file for the generator. 128 | * 129 | * @return string 130 | */ 131 | protected function getStub() 132 | { 133 | return __DIR__ . '/stubs/form-class-template.stub'; 134 | } 135 | 136 | /** 137 | * Get psr-4 namespace. 138 | * 139 | * @return array 140 | */ 141 | protected function getAutoload() 142 | { 143 | $composerPath = base_path('/composer.json'); 144 | if (! file_exists($composerPath)) { 145 | return []; 146 | } 147 | $composer = json_decode(file_get_contents( 148 | $composerPath 149 | ), true); 150 | 151 | return Arr::get($composer, 'autoload.psr-4', []); 152 | } 153 | 154 | /** 155 | * Get the desired class name from the input. 156 | * 157 | * @return string 158 | */ 159 | protected function getNameInput() 160 | { 161 | return str_replace('/', '\\', $this->argument('name')); 162 | } 163 | 164 | /** 165 | * @inheritdoc 166 | */ 167 | protected function getPath($name) 168 | { 169 | $optionsPath = $this->option('path'); 170 | 171 | if ($optionsPath !== null) { 172 | return join('/', [ 173 | $this->laravel->basePath(), 174 | trim($optionsPath, '/'), 175 | $this->getNameInput().'.php' 176 | ]); 177 | } 178 | 179 | return parent::getPath($name); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/stubs/form-class-template.stub: -------------------------------------------------------------------------------- 1 | field = $field; 34 | $this->rules = $rules; 35 | } 36 | 37 | /** 38 | * Return the event's field. 39 | * 40 | * @return FormField 41 | */ 42 | public function getField() { 43 | return $this->field; 44 | } 45 | 46 | /** 47 | * Return the event's field's rules. 48 | * 49 | * @return Rules 50 | */ 51 | public function getRules() { 52 | return $this->rules; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Events/AfterFieldCreation.php: -------------------------------------------------------------------------------- 1 | form = $form; 33 | $this->field = $field; 34 | } 35 | 36 | /** 37 | * Return the event's form. 38 | * 39 | * @return Form 40 | */ 41 | public function getForm() { 42 | return $this->form; 43 | } 44 | 45 | /** 46 | * Return the event's field. 47 | * 48 | * @return FormField 49 | */ 50 | public function getField() { 51 | return $this->field; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Events/AfterFormCreation.php: -------------------------------------------------------------------------------- 1 | form = $form; 24 | } 25 | 26 | /** 27 | * Return the event's form. 28 | * 29 | * @return Form 30 | */ 31 | public function getForm() { 32 | return $this->form; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Events/AfterFormValidation.php: -------------------------------------------------------------------------------- 1 | form = $form; 42 | $this->validator = $validator; 43 | $this->valid = $valid; 44 | } 45 | 46 | /** 47 | * Return the event's form. 48 | * 49 | * @return Form 50 | */ 51 | public function getForm() 52 | { 53 | return $this->form; 54 | } 55 | 56 | /** 57 | * Return the event's validator. 58 | * 59 | * @return Validator 60 | */ 61 | public function getValidator() 62 | { 63 | return $this->validator; 64 | } 65 | 66 | /** 67 | * Return wether the form is valid. 68 | * 69 | * @return bool 70 | */ 71 | public function isValid() 72 | { 73 | return $this->valid; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Events/BeforeFormValidation.php: -------------------------------------------------------------------------------- 1 | form = $form; 34 | $this->validator = $validator; 35 | } 36 | 37 | /** 38 | * Get the Form instance of this event. 39 | * 40 | * @return Form 41 | */ 42 | public function getForm() 43 | { 44 | return $this->form; 45 | } 46 | 47 | /** 48 | * Get the Validator instance of this event. 49 | * 50 | * @return Validator 51 | */ 52 | public function getValidator() 53 | { 54 | return $this->validator; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Facades/FormBuilder.php: -------------------------------------------------------------------------------- 1 | getOption('splitted', false); 24 | $options['size'] = $this->getOption('size', 'md'); 25 | $options['buttons'] = $this->getOption('buttons', []); 26 | 27 | return parent::render($options, $showLabel, $showField, $showError); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ButtonType.php: -------------------------------------------------------------------------------- 1 | false, 22 | 'attr' => ['type' => $this->type] 23 | ]; 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function getAllAttributes() 30 | { 31 | // Don't collect input for buttons. 32 | return []; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/CheckableType.php: -------------------------------------------------------------------------------- 1 | type; 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | public function getDefaults() 27 | { 28 | return [ 29 | 'attr' => ['class' => null, 'id' => $this->getName()], 30 | 'value' => self::DEFAULT_VALUE, 31 | 'checked' => null 32 | ]; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | protected function isValidValue($value) 39 | { 40 | return $value !== null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ChildFormType.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ChildFormType extends ParentType 13 | { 14 | 15 | /** 16 | * @var Form 17 | * @phpstan-var TFormType 18 | */ 19 | protected $form; 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | protected function getTemplate() 25 | { 26 | return 'child_form'; 27 | } 28 | 29 | /** 30 | * @return Form 31 | * @phpstan-return TFormType 32 | */ 33 | public function getForm() 34 | { 35 | return $this->form; 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | protected function getDefaults() 42 | { 43 | return [ 44 | 'class' => null, 45 | 'value' => null, 46 | 'formOptions' => [], 47 | 'data' => [], 48 | 'exclude' => [] 49 | ]; 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public function getAllAttributes() 56 | { 57 | // Collect all children's attributes. 58 | return $this->parent->getFormHelper()->mergeAttributes($this->children); 59 | } 60 | 61 | /** 62 | * Allow form-specific value alters. 63 | * 64 | * @param array $values 65 | * @return void 66 | */ 67 | public function alterFieldValues(array &$values) 68 | { 69 | $this->parent->getFormHelper()->alterFieldValues($this->form, $values); 70 | } 71 | 72 | /** 73 | * Allow form-specific valid alters. 74 | * 75 | * @param Form $mainForm 76 | * @param bool $isValid 77 | * @return void 78 | */ 79 | public function alterValid(Form $mainForm, &$isValid) 80 | { 81 | $this->parent->getFormHelper()->alterValid($this->form, $mainForm, $isValid); 82 | } 83 | 84 | /** 85 | * @return mixed|void 86 | */ 87 | protected function createChildren() 88 | { 89 | $this->form = $this->getClassFromOptions(); 90 | 91 | if ($this->form->getFormOption('files')) { 92 | $this->parent->setFormOption('files', true); 93 | } 94 | $model = $this->getOption($this->valueProperty); 95 | if ($this->isValidValue($model)) { 96 | foreach ($this->form->getFields() as $name => $field) { 97 | $field->setValue($this->getModelValueAttribute($model, $name)); 98 | } 99 | } 100 | 101 | $this->children = $this->form->getFields(); 102 | } 103 | 104 | /** 105 | * @return Form 106 | * @phpstan-return TFormType 107 | * @throws \Exception 108 | */ 109 | protected function getClassFromOptions() 110 | { 111 | if ($this->form instanceof Form) { 112 | return $this->form->setName($this->name); 113 | } 114 | 115 | $class = $this->getOption('class'); 116 | 117 | if (!$class) { 118 | throw new \InvalidArgumentException( 119 | 'Please provide full name or instance of Form class.' 120 | ); 121 | } 122 | 123 | if (is_string($class)) { 124 | $options = [ 125 | 'model' => $this->getOption($this->valueProperty) ?: $this->parent->getModel(), 126 | 'name' => $this->name, 127 | 'language_name' => $this->getOption('language_name') ?: $this->parent->getLanguageName(), 128 | 'translation_template' => $this->parent->getTranslationTemplate(), 129 | ]; 130 | 131 | if (!$this->parent->clientValidationEnabled()) { 132 | $options['client_validation'] = false; 133 | } 134 | 135 | if (!$this->parent->haveErrorsEnabled()) { 136 | $options['errors_enabled'] = false; 137 | } 138 | 139 | $formOptions = array_merge($options, $this->getOption('formOptions')); 140 | 141 | $data = array_merge($this->parent->getData(), $this->getOption('data')); 142 | 143 | return $this->parent->getFormBuilder()->create($class, $formOptions, $data); 144 | } 145 | 146 | if ($class instanceof Form) { 147 | $class->setName($this->name, false); 148 | $class->setModel($class->getModel() ?: $this->parent->getModel()); 149 | 150 | if (!$class->getData()) { 151 | $class->addData($this->parent->getData()); 152 | } 153 | 154 | if (!$class->getLanguageName()) { 155 | $class->setLanguageName($this->parent->getLanguageName()); 156 | } 157 | 158 | if (!$class->getTranslationTemplate()) { 159 | $class->setTranslationTemplate($this->parent->getTranslationTemplate()); 160 | } 161 | 162 | if (!$this->parent->clientValidationEnabled()) { 163 | $class->setClientValidationEnabled(false); 164 | } 165 | 166 | if (!$this->parent->haveErrorsEnabled()) { 167 | $class->setErrorsEnabled(false); 168 | } 169 | 170 | return $class->setName($this->name); 171 | } 172 | 173 | throw new \InvalidArgumentException( 174 | 'Class provided does not exist or it passed in wrong format.' 175 | ); 176 | } 177 | 178 | /** 179 | * @inheritdoc 180 | */ 181 | public function removeChild($key) 182 | { 183 | if ($this->getChild($key)) { 184 | $this->form->remove($key); 185 | return parent::removeChild($key); 186 | } 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * @inheritdoc 193 | */ 194 | protected function getRenderData() { 195 | $data = parent::getRenderData(); 196 | $data['child_form'] = $this->form; 197 | return $data; 198 | } 199 | 200 | /** 201 | * @param $method 202 | * @param $arguments 203 | * 204 | * @return Form|null 205 | */ 206 | public function __call($method, $arguments) 207 | { 208 | if (method_exists($this->form, $method)) { 209 | return call_user_func_array([$this->form, $method], $arguments); 210 | } 211 | 212 | throw new \BadMethodCallException( 213 | 'Method ['.$method.'] does not exist on form ['.get_class($this->form).']' 214 | ); 215 | } 216 | 217 | /** 218 | * Check if provided value is valid for this type. 219 | * 220 | * @return bool 221 | */ 222 | protected function isValidValue($value) 223 | { 224 | return $value !== null; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ChoiceType.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ChoiceType extends ParentType 11 | { 12 | /** 13 | * @var string 14 | */ 15 | protected $choiceType = 'select'; 16 | 17 | /** 18 | * @inheritdoc 19 | */ 20 | protected $valueProperty = 'selected'; 21 | 22 | /** 23 | * @inheritdoc 24 | */ 25 | protected function getTemplate() 26 | { 27 | return 'choice'; 28 | } 29 | 30 | /** 31 | * Determine which choice type to use. 32 | * 33 | * @return string 34 | */ 35 | protected function determineChoiceField() 36 | { 37 | $expanded = $this->options['expanded']; 38 | $multiple = $this->options['multiple']; 39 | 40 | if (!$expanded && $multiple) { 41 | $this->options['attr']['multiple'] = true; 42 | } 43 | 44 | if ($expanded && !$multiple) { 45 | return $this->choiceType = 'radio'; 46 | } 47 | 48 | if ($expanded && $multiple) { 49 | return $this->choiceType = 'checkbox'; 50 | } 51 | 52 | return $this->choiceType = 'select'; 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | protected function getDefaults() 59 | { 60 | return [ 61 | 'choices' => null, 62 | 'selected' => null, 63 | 'expanded' => false, 64 | 'multiple' => false, 65 | 'choice_options' => [ 66 | 'wrapper' => false, 67 | 'is_child' => true 68 | ] 69 | ]; 70 | } 71 | 72 | /** 73 | * Create children depending on choice type. 74 | * 75 | * @return void 76 | */ 77 | protected function createChildren() 78 | { 79 | if (($data_override = $this->getOption('data_override')) && $data_override instanceof \Closure) { 80 | $this->options['choices'] = $data_override($this->options['choices'], $this); 81 | } 82 | 83 | $this->children = []; 84 | $this->determineChoiceField(); 85 | 86 | $fieldType = $this->formHelper->getFieldType($this->choiceType); 87 | 88 | switch ($this->choiceType) { 89 | case 'radio': 90 | case 'checkbox': 91 | $this->buildCheckableChildren($fieldType); 92 | break; 93 | default: 94 | $this->buildSelect($fieldType); 95 | break; 96 | } 97 | } 98 | 99 | /** 100 | * Build checkable children fields from choice type. 101 | * 102 | * @param string $fieldType 103 | * 104 | * @return void 105 | */ 106 | protected function buildCheckableChildren($fieldType) 107 | { 108 | $multiple = $this->getOption('multiple') ? '[]' : ''; 109 | 110 | $attr = $this->options['attr']?? []; 111 | $attr = Arr::except($attr, ['class', 'multiple', 'id', 'name']); 112 | foreach ((array)$this->options['choices'] as $key => $choice) { 113 | $id = str_replace('.', '_', $this->getNameKey()) . '_' . $key; 114 | $options = $this->formHelper->mergeOptions( 115 | $this->getOption('choice_options'), 116 | [ 117 | 'attr' => array_merge(['id' => $id], $this->options['option_attributes'][$key] ?? $attr), 118 | 'label_attr' => ['for' => $id], 119 | 'label' => $choice, 120 | 'checked' => in_array($key, (array)$this->options[$this->valueProperty]), 121 | 'value' => $key 122 | ] 123 | ); 124 | $this->children[] = new $fieldType( 125 | $this->name . $multiple, 126 | $this->choiceType, 127 | $this->parent, 128 | $options 129 | ); 130 | } 131 | } 132 | 133 | /** 134 | * Build select field from choice. 135 | * 136 | * @param string $fieldType 137 | */ 138 | protected function buildSelect($fieldType) 139 | { 140 | $this->children[] = new $fieldType( 141 | $this->name, 142 | $this->choiceType, 143 | $this->parent, 144 | $this->formHelper->mergeOptions($this->options, ['is_child' => true]) 145 | ); 146 | } 147 | 148 | /** 149 | * Creates default wrapper classes for the form element. 150 | * 151 | * @param array $options 152 | * @return array 153 | */ 154 | protected function setDefaultClasses(array $options = []) 155 | { 156 | $defaults = parent::setDefaultClasses($options); 157 | $choice_type = $this->determineChoiceField(); 158 | Arr::forget($defaults, 'attr.class'); 159 | 160 | $wrapper_class = $this->formHelper->getConfig('defaults.' . $this->type . '.' . $choice_type . '_wrapper_class', ''); 161 | if ($wrapper_class) { 162 | $defaults['wrapper']['class'] = (isset($defaults['wrapper']['class']) ? $defaults['wrapper']['class'] . ' ' : '') . $wrapper_class; 163 | } 164 | 165 | $choice_wrapper_class = $this->formHelper->getConfig( 166 | 'defaults.' . $this->type . '.choice_options.wrapper_class', 167 | $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.wrapper_class', '') 168 | ); 169 | $choice_label_class = $this->formHelper->getConfig( 170 | 'defaults.' . $this->type . '.choice_options.label_class', 171 | $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.label_class', '') 172 | ); 173 | $choice_field_class = $this->formHelper->getConfig( 174 | 'defaults.' . $this->type . '.choice_options.field_class', 175 | $this->formHelper->getConfig('defaults.' . $this->type . '.choice_options.' . $choice_type . '.field_class', '') 176 | ); 177 | 178 | if ($choice_wrapper_class) { 179 | $defaults['choice_options']['wrapper']['class'] = $choice_wrapper_class; 180 | } 181 | if ($choice_label_class) { 182 | $defaults['choice_options']['label_attr']['class'] = $choice_label_class; 183 | } 184 | if ($choice_field_class) { 185 | $defaults['choice_options']['attr']['class'] = $choice_field_class; 186 | } 187 | 188 | return $defaults; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/CollectionType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CollectionType extends ParentType 14 | { 15 | /** 16 | * Contains template for a collection element. 17 | * 18 | * @var FormField 19 | * @phpstan-var TType 20 | */ 21 | protected $proto; 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | protected $valueProperty = 'data'; 27 | 28 | /** 29 | * @return string 30 | */ 31 | protected function getTemplate() 32 | { 33 | return 'collection'; 34 | } 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | protected function getDefaults() 40 | { 41 | return [ 42 | 'type' => null, 43 | 'options' => ['is_child' => true], 44 | 'prototype' => true, 45 | 'data' => null, 46 | 'property' => 'id', 47 | 'prototype_name' => '__NAME__', 48 | 'empty_row' => true, 49 | 'prefer_input' => false, 50 | 'empty_model' => null, 51 | ]; 52 | } 53 | 54 | /** 55 | * Get the prototype object. 56 | * 57 | * @return FormField 58 | * @phpstan-return TType 59 | * @throws \Exception 60 | */ 61 | public function prototype() 62 | { 63 | 64 | if ($this->getOption('prototype') === false) { 65 | throw new \Exception( 66 | 'Prototype for collection field [' . $this->name .'] is disabled.' 67 | ); 68 | } 69 | 70 | return $this->proto; 71 | } 72 | 73 | /** 74 | * @inheritdoc 75 | */ 76 | public function getAllAttributes() 77 | { 78 | // Collect all children's attributes. 79 | return $this->parent->getFormHelper()->mergeAttributes($this->children); 80 | } 81 | 82 | /** 83 | * Allow form-specific value alters. 84 | * 85 | * @param array $values 86 | * @return void 87 | */ 88 | public function alterFieldValues(array &$values) 89 | { 90 | $stripLeft = strlen($this->getName()) + 1; 91 | $stripRight = 1; 92 | foreach ($this->children as $child) { 93 | if (method_exists($child, 'alterFieldValues')) { 94 | $itemKey = substr($child->getName(), $stripLeft, -$stripRight); 95 | if (isset($values[$itemKey])) { 96 | $child->alterFieldValues($values[$itemKey]); 97 | } 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * @inheritdoc 104 | */ 105 | protected function createChildren() 106 | { 107 | $this->children = []; 108 | $type = $this->getOption('type'); 109 | $oldInput = $this->parent->getRequest()->old($this->getNameKey()); 110 | $currentInput = $this->parent->getRequest()->input($this->getNameKey()); 111 | 112 | is_array($oldInput) or $oldInput = []; 113 | is_array($currentInput) or $currentInput = []; 114 | 115 | try { 116 | $fieldType = $this->formHelper->getFieldType($type); 117 | } catch (\Exception $e) { 118 | throw new \Exception( 119 | 'Collection field ['.$this->name.'] requires [type] option'. "\n\n". 120 | $e->getMessage() 121 | ); 122 | } 123 | 124 | $data = $this->getOption($this->valueProperty, []); 125 | 126 | // If no value is provided, get values from current request. 127 | if (!is_null($data) && count($data) === 0) { 128 | if ($this->getOption('prefer_input')) { 129 | $data = $this->formatInputIntoModels($currentInput); 130 | } 131 | elseif ($this->getOption('empty_row')) { 132 | $data = $this->formatInputIntoModels(array_slice($currentInput, 0, 1, true)); 133 | } 134 | else { 135 | $data = []; 136 | } 137 | } 138 | // Or if the current request input is preferred over original data. 139 | elseif ($this->getOption('prefer_input') && count($currentInput)) { 140 | $data = $this->formatInputIntoModels($currentInput, $data ?? []); 141 | } 142 | 143 | if ($data instanceof Collection) { 144 | $data = $data->all(); 145 | } 146 | 147 | // Needs to have more than 1 item because 1 is rendered by default. 148 | // This overrides current request in situations when validation fails. 149 | if ($oldInput && count($oldInput) > 1) { 150 | $data = $this->formatInputIntoModels($oldInput, $data ?? []); 151 | } 152 | 153 | $field = new $fieldType($this->name, $type, $this->parent, $this->getOption('options')); 154 | 155 | if ($this->getOption('prototype')) { 156 | $this->generatePrototype(clone $field); 157 | } 158 | 159 | if (!$data || empty($data)) { 160 | if ($this->getOption('empty_row')) { 161 | return $this->children[] = $this->setupChild(clone $field, '[0]', $this->makeEmptyRowValue()); 162 | } 163 | 164 | return $this->children = []; 165 | } 166 | 167 | if (!is_array($data) && !$data instanceof \Traversable) { 168 | throw new \Exception( 169 | 'Data for collection field ['.$this->name.'] must be iterable.' 170 | ); 171 | } 172 | 173 | foreach ($data as $key => $val) { 174 | $this->children[] = $this->setupChild(clone $field, '['.$key.']', $val); 175 | } 176 | 177 | return $this->children; 178 | } 179 | 180 | protected function makeEmptyRowValue() 181 | { 182 | $empty = $this->getOption('empty_row'); 183 | return $empty === true ? $this->makeNewEmptyModel() : $empty; 184 | } 185 | 186 | protected function makeNewEmptyModel() 187 | { 188 | return value($this->getOption('empty_model')); 189 | } 190 | 191 | protected function formatInputIntoModels(array $input, array $originalData = []) 192 | { 193 | if (!$this->getOption('empty_model')) { 194 | return $input; 195 | } 196 | 197 | $newData = []; 198 | foreach ($input as $k => $inputItem) { 199 | if (is_array($inputItem)) { 200 | $newData[$k] = $this->formatInputIntoModel($originalData[$k] ?? $this->makeNewEmptyModel(), $inputItem); 201 | } 202 | else { 203 | $newData[$k] = $inputItem; 204 | } 205 | } 206 | 207 | return $newData; 208 | } 209 | 210 | protected function formatInputIntoModel($model, $input) 211 | { 212 | if ($model instanceof Model) { 213 | $model->forceFill($input); 214 | } 215 | elseif (is_object($model)) { 216 | foreach ($input as $key => $value) { 217 | $model->$key = $value; 218 | } 219 | } 220 | elseif (is_array($model)) { 221 | $model = $input + $model; 222 | } 223 | else { 224 | $model = $input; 225 | } 226 | 227 | return $model; 228 | } 229 | 230 | /** 231 | * Set up a single child element for a collection. 232 | * 233 | * @param FormField $field 234 | * @param $name 235 | * @param null $value 236 | * @return FormField 237 | */ 238 | protected function setupChild(FormField $field, $name, $value = null) 239 | { 240 | $newFieldName = $field->getName().$name; 241 | 242 | $firstFieldOptions = $this->formHelper->mergeOptions( 243 | $this->getOption('options'), 244 | ['attr' => array_merge(['id' => $newFieldName], $this->getOption('attr'))] 245 | ); 246 | 247 | if (isset($firstFieldOptions['label'])) { 248 | $firstFieldOptions['label'] = value($firstFieldOptions['label'], $value, $field); 249 | } 250 | 251 | $field->setName($newFieldName); 252 | $field->setOptions($firstFieldOptions); 253 | 254 | if ($value && !$field instanceof ChildFormType) { 255 | $value = $this->getModelValueAttribute( 256 | $value, 257 | $this->getOption('property') 258 | ); 259 | } 260 | 261 | $field->setValue($value); 262 | 263 | return $field; 264 | } 265 | 266 | /** 267 | * Generate prototype for regular form field. 268 | * 269 | * @param FormField $field 270 | * @return void 271 | */ 272 | protected function generatePrototype(FormField $field) 273 | { 274 | $value = $this->makeNewEmptyModel(); 275 | $field->setOption('is_prototype', true); 276 | $field = $this->setupChild($field, $this->getPrototypeName(), $value); 277 | 278 | if ($field instanceof ChildFormType) { 279 | foreach ($field->getChildren() as $child) { 280 | if ($child instanceof CollectionType) { 281 | $child->preparePrototype($child->prototype()); 282 | } 283 | } 284 | } 285 | 286 | $this->proto = $field; 287 | } 288 | 289 | /** 290 | * Generate array like prototype name. 291 | * 292 | * @return string 293 | */ 294 | protected function getPrototypeName() 295 | { 296 | return '[' . $this->getOption('prototype_name') . ']'; 297 | } 298 | 299 | /** 300 | * Prepare collection for prototype by adding prototype as child. 301 | * 302 | * @param FormField $field 303 | * @return void 304 | */ 305 | public function preparePrototype(FormField $field) 306 | { 307 | if (!$field->getOption('is_prototype')) { 308 | throw new \InvalidArgumentException( 309 | 'Field ['.$field->getRealName().'] is not a valid prototype object.' 310 | ); 311 | } 312 | 313 | $this->children = []; 314 | $this->children[] = $field; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/EntityType.php: -------------------------------------------------------------------------------- 1 | null, 18 | 'query_builder' => null, 19 | 'property' => 'name', 20 | 'property_key' => null, 21 | ]; 22 | 23 | return array_merge(parent::getDefaults(), $defaults); 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | protected function createChildren() 30 | { 31 | if ($this->getOption('choices')) { 32 | return parent::createChildren(); 33 | } 34 | 35 | $entity = $this->getOption('class'); 36 | $queryBuilder = $this->getOption('query_builder'); 37 | $key = $this->getOption('property_key'); 38 | $value = $this->getOption('property'); 39 | 40 | if (!$entity || !class_exists($entity)) { 41 | throw new \InvalidArgumentException(sprintf( 42 | 'Please provide valid "class" option for entity field [%s] in form class [%s]', 43 | $this->getRealName(), 44 | get_class($this->parent) 45 | )); 46 | } 47 | 48 | $entity = new $entity(); 49 | 50 | if ($key === null) { 51 | $key = $entity->getKeyName(); 52 | } 53 | 54 | if ($queryBuilder instanceof \Closure) { 55 | $data = $queryBuilder($entity, $this->parent); 56 | } else { 57 | $data = $entity; 58 | } 59 | 60 | if ($value instanceof \Closure) { 61 | $data = $this->get($data); 62 | } else { 63 | $data = $this->pluck($value, $key, $data); 64 | } 65 | 66 | if ($data instanceof Collection) { 67 | $data = $data->all(); 68 | } 69 | 70 | if ($value instanceof \Closure) { 71 | $part = []; 72 | foreach ($data as $item) { 73 | $part[$item->__get($key)] = $value($item); 74 | } 75 | 76 | $data = $part; 77 | } 78 | 79 | $this->options['choices'] = $data; 80 | 81 | return parent::createChildren(); 82 | } 83 | 84 | /** 85 | * Pluck data. 86 | * 87 | * @param string $value 88 | * @param string $key 89 | * @param mixed $data 90 | * 91 | * @return mixed 92 | * */ 93 | protected function pluck($value, $key, $data) 94 | { 95 | if (!is_object($data)) { 96 | return $data; 97 | } 98 | 99 | if (method_exists($data, 'pluck') || $data instanceof Model) { 100 | //laravel 5.3.* 101 | return $data->pluck($value, $key); 102 | } elseif (method_exists($data, 'lists')) { 103 | //laravel 5.2.* 104 | return $data->lists($value, $key); 105 | } 106 | 107 | throw new \InvalidArgumentException(sprintf( 108 | 'Please provide valid "property" option for entity field [%s] in form class [%s]', 109 | $this->getRealName(), 110 | get_class($this->parent) 111 | )); 112 | } 113 | 114 | protected function get($data) 115 | { 116 | if (!is_object($data)) { 117 | return $data; 118 | } 119 | 120 | if (method_exists($data, 'get') || $data instanceof Model) { 121 | //laravel 5.3.* 122 | return $data->get(); 123 | } 124 | 125 | throw new \InvalidArgumentException(sprintf( 126 | 'Please provide valid "query_builder" option for entity field [%s] in form class [%s]', 127 | $this->getRealName(), 128 | get_class($this->parent) 129 | )); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/InputType.php: -------------------------------------------------------------------------------- 1 | true]); 37 | // If there is default value provided and setValue was not triggered 38 | // in the parent call, make sure we generate child elements. 39 | if ($this->hasDefault) { 40 | $this->createChildren(); 41 | } 42 | $this->checkIfFileType(); 43 | } 44 | 45 | /** 46 | * @param mixed $val 47 | * 48 | * @return $this 49 | */ 50 | public function setValue($val) 51 | { 52 | parent::setValue($val); 53 | $this->createChildren(); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * {inheritdoc} 60 | */ 61 | public function render(array $options = [], $showLabel = true, $showField = true, $showError = true) 62 | { 63 | $options['children'] = $this->children; 64 | return parent::render($options, $showLabel, $showField, $showError); 65 | } 66 | 67 | /** 68 | * Get all children of the choice field. 69 | * 70 | * @return mixed 71 | * @phpstan-return TChildType[] 72 | */ 73 | public function getChildren() 74 | { 75 | return $this->children; 76 | } 77 | 78 | /** 79 | * Get a child of the choice field. 80 | * 81 | * @return mixed 82 | * @phpstan-return ?TChildType 83 | */ 84 | public function getChild($key) 85 | { 86 | return Arr::get($this->children, $key); 87 | } 88 | 89 | /** 90 | * Remove child. 91 | * 92 | * @return $this 93 | */ 94 | public function removeChild($key) 95 | { 96 | if ($this->getChild($key)) { 97 | unset($this->children[$key]); 98 | } 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @inheritdoc 105 | */ 106 | public function setOption($name, $value) 107 | { 108 | parent::setOption($name, $value); 109 | 110 | if ($this->options['copy_options_to_children']) { 111 | foreach ((array) $this->children as $key => $child) { 112 | $this->children[$key]->setOption($name, $value); 113 | } 114 | } 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * @inheritdoc 121 | */ 122 | public function setOptions($options) 123 | { 124 | parent::setOptions($options); 125 | 126 | if ($this->options['copy_options_to_children']) { 127 | foreach ((array) $this->children as $key => $child) { 128 | $this->children[$key]->setOptions($options); 129 | } 130 | } 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * @inheritdoc 137 | */ 138 | public function isRendered() 139 | { 140 | foreach ((array) $this->children as $key => $child) { 141 | if ($child->isRendered()) { 142 | return true; 143 | } 144 | } 145 | 146 | return parent::isRendered(); 147 | } 148 | 149 | /** 150 | * Get child dynamically. 151 | * 152 | * @param string $name 153 | * @return FormField 154 | * @phpstan-return TChildType 155 | */ 156 | public function __get($name) 157 | { 158 | return $this->getChild($name); 159 | } 160 | 161 | /** 162 | * Check if field has type property and if it's file add enctype/multipart to form. 163 | * 164 | * @return void 165 | */ 166 | protected function checkIfFileType() 167 | { 168 | if ($this->getOption('type') === 'file') { 169 | $this->parent->setFormOption('files', true); 170 | } 171 | } 172 | 173 | public function __clone() 174 | { 175 | foreach ((array) $this->children as $key => $child) { 176 | $this->children[$key] = clone $child; 177 | } 178 | } 179 | 180 | /** 181 | * @inheritdoc 182 | */ 183 | public function disable() 184 | { 185 | parent::disable(); 186 | foreach ($this->children as $field) { 187 | $field->disable(); 188 | } 189 | return $this; 190 | } 191 | 192 | /** 193 | * @inheritdoc 194 | */ 195 | public function enable() 196 | { 197 | parent::enable(); 198 | foreach ($this->children as $field) { 199 | $field->enable(); 200 | } 201 | return $this; 202 | } 203 | 204 | /** 205 | * @inheritdoc 206 | */ 207 | public function getValidationRules() 208 | { 209 | $rules = parent::getValidationRules(); 210 | $childrenRules = $this->formHelper->mergeFieldsRules($this->children); 211 | 212 | return $rules->append($childrenRules); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/RepeatedType.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RepeatedType extends ParentType 11 | { 12 | 13 | /** 14 | * Get the template, can be config variable or view path. 15 | * 16 | * @return string 17 | */ 18 | protected function getTemplate() 19 | { 20 | return 'repeated'; 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | protected function getDefaults() 27 | { 28 | return [ 29 | 'type' => 'password', 30 | 'second_name' => null, 31 | 'first_options' => ['label' => 'Password'], 32 | 'second_options' => ['label' => 'Password confirmation'] 33 | ]; 34 | } 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public function getAllAttributes() 40 | { 41 | // Collect all children's attributes. 42 | return $this->parent->getFormHelper()->mergeAttributes($this->children); 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | protected function createChildren() 49 | { 50 | $this->prepareOptions(); 51 | 52 | $firstName = $this->getRealName(); 53 | $secondName = $this->getOption('second_name'); 54 | 55 | if (is_null($secondName)) { 56 | $secondName = $firstName.'_confirmation'; 57 | } 58 | 59 | // merge field rules and first field rules 60 | $firstOptions = $this->getOption('first_options'); 61 | $firstOptions['rules'] = $this->normalizeRules(Arr::pull($firstOptions, 'rules', [])); 62 | if ($mainRules = $this->getOption('rules')) { 63 | $firstOptions['rules'] = $this->mergeRules($mainRules, $firstOptions['rules']); 64 | } 65 | 66 | $sameRule = 'same:' . $secondName; 67 | if (!in_array($sameRule, $firstOptions['rules'])) { 68 | $firstOptions['rules'][] = $sameRule; 69 | } 70 | 71 | $form = $this->parent->getFormBuilder()->plain([ 72 | 'name' => $this->parent->getName(), 73 | 'model' => $this->parent->getModel() 74 | ]) 75 | ->add($firstName, $this->getOption('type'), $firstOptions) 76 | ->add($secondName, $this->getOption('type'), $this->getOption('second_options')); 77 | 78 | $this->children['first'] = $form->getField($firstName); 79 | $this->children['second'] = $form->getField($secondName); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/SelectType.php: -------------------------------------------------------------------------------- 1 | [], 30 | 'option_attributes' => [], 31 | 'empty_value' => null, 32 | 'selected' => null 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/StaticType.php: -------------------------------------------------------------------------------- 1 | setupStaticOptions($options); 13 | return parent::render($options, $showLabel, $showField, $showError); 14 | } 15 | 16 | /** 17 | * Setup static field options. 18 | * 19 | * @param array $options 20 | * @return void 21 | */ 22 | private function setupStaticOptions(&$options) 23 | { 24 | $options['elemAttrs'] = $this->formHelper->prepareAttributes($this->getOption('attr')); 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | protected function getTemplate() 31 | { 32 | return 'static'; 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | protected function getDefaults() 39 | { 40 | return [ 41 | 'tag' => 'div', 42 | 'attr' => ['class' => 'form-control-static', 'id' => $this->getName()] 43 | ]; 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function getAllAttributes() 50 | { 51 | // No input allowed for Static fields. 52 | return []; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/TextareaType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BaseName implements FilterInterface 14 | { 15 | /** 16 | * @param string $value 17 | * @param array $options 18 | * 19 | * @return string 20 | */ 21 | public function filter($value, $options = []) 22 | { 23 | $value = (string) $value; 24 | return basename($value); 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getName() 31 | { 32 | return 'BaseName'; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/HtmlEntities.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class HtmlEntities implements FilterInterface 14 | { 15 | 16 | /** 17 | * Second arg of htmlentities function. 18 | * 19 | * @var integer 20 | */ 21 | protected $quoteStyle; 22 | 23 | /** 24 | * Third arg of htmlentities function. 25 | * 26 | * @var string 27 | */ 28 | protected $encoding; 29 | 30 | /** 31 | * Fourth arg of htmlentities function. 32 | * 33 | * @var string 34 | */ 35 | protected $doubleQuote; 36 | 37 | /** 38 | * HtmlEntities constructor. 39 | * 40 | * @param array $options 41 | */ 42 | public function __construct(array $options = []) 43 | { 44 | if (!isset($options['quotestyle'])) { 45 | $options['quotestyle'] = ENT_COMPAT; 46 | } 47 | 48 | if (!isset($options['encoding'])) { 49 | $options['encoding'] = 'UTF-8'; 50 | } 51 | 52 | if (isset($options['charset'])) { 53 | $options['encoding'] = $options['charset']; 54 | } 55 | 56 | if (!isset($options['doublequote'])) { 57 | $options['doublequote'] = true; 58 | } 59 | 60 | $this->setQuoteStyle($options['quotestyle']); 61 | $this->setEncoding($options['encoding']); 62 | $this->setDoubleQuote($options['doublequote']); 63 | } 64 | 65 | /** 66 | * @return integer 67 | */ 68 | public function getQuoteStyle() 69 | { 70 | return $this->quoteStyle; 71 | } 72 | 73 | /** 74 | * @param integer $style 75 | * 76 | * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities 77 | */ 78 | public function setQuoteStyle($style) 79 | { 80 | $this->quoteStyle = $style; 81 | return $this; 82 | } 83 | 84 | /** 85 | * @return string 86 | */ 87 | public function getEncoding() 88 | { 89 | return $this->encoding; 90 | } 91 | 92 | /** 93 | * @param string $encoding 94 | * 95 | * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities 96 | */ 97 | public function setEncoding($encoding) 98 | { 99 | $this->encoding = (string) $encoding; 100 | return $this; 101 | } 102 | 103 | /** 104 | * Returns the charSet property 105 | * 106 | * Proxies to {@link getEncoding()} 107 | * 108 | * @return string 109 | */ 110 | public function getCharSet() 111 | { 112 | return $this->getEncoding(); 113 | } 114 | 115 | /** 116 | * Sets the charSet property. 117 | * 118 | * Proxies to {@link setEncoding()}. 119 | * 120 | * @param string $charSet 121 | * 122 | * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities 123 | */ 124 | public function setCharSet($charSet) 125 | { 126 | return $this->setEncoding($charSet); 127 | } 128 | 129 | /** 130 | * Returns the doubleQuote property. 131 | * 132 | * @return boolean 133 | */ 134 | public function getDoubleQuote() 135 | { 136 | return $this->doubleQuote; 137 | } 138 | 139 | /** 140 | * Sets the doubleQuote property. 141 | * 142 | * @param boolean $doubleQuote 143 | * 144 | * @return \Kris\LaravelFormBuilder\Filters\Collection\HtmlEntities 145 | */ 146 | public function setDoubleQuote($doubleQuote) 147 | { 148 | $this->doubleQuote = (boolean) $doubleQuote; 149 | return $this; 150 | } 151 | 152 | /** 153 | * @param string $value 154 | * @param array $options 155 | * 156 | * @return mixed 157 | * 158 | * @throws \Exception 159 | */ 160 | public function filter($value, $options = []) 161 | { 162 | $value = (string) $value; 163 | $filtered = htmlentities( 164 | $value, 165 | $this->getQuoteStyle(), 166 | $this->getEncoding(), 167 | $this->getDoubleQuote() 168 | ); 169 | 170 | if (strlen($value) && !strlen($filtered)) { 171 | if (!function_exists('iconv')) { 172 | $ex = new \Exception('Encoding mismatch has resulted in htmlentities errors.'); 173 | throw $ex; 174 | } 175 | 176 | $enc = $this->getEncoding(); 177 | $value = iconv('', $enc . '//IGNORE', $value); 178 | $filtered = htmlentities($value, $this->getQuoteStyle(), $enc, $this->getDoubleQuote()); 179 | 180 | if (!strlen($filtered)) { 181 | $ex = new \Exception('Encoding mismatch has resulted in htmlentities errors.'); 182 | throw $ex; 183 | } 184 | } 185 | 186 | return $filtered; 187 | } 188 | 189 | /** 190 | * @return string 191 | */ 192 | public function getName() 193 | { 194 | return 'HtmlEntities'; 195 | } 196 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/Integer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Integer implements FilterInterface 14 | { 15 | /** 16 | * @param mixed $value 17 | * @param array $options 18 | * 19 | * @return mixed 20 | */ 21 | public function filter($value, $options = []) 22 | { 23 | $value = (int) ((string) $value); 24 | return $value; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getName() 31 | { 32 | return 'Integer'; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/Lowercase.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Lowercase implements FilterInterface 14 | { 15 | /** 16 | * Encoding for string input. 17 | * 18 | * @var string $encoding 19 | */ 20 | protected $encoding = null; 21 | 22 | /** 23 | * StringToLower constructor. 24 | * 25 | * @param array $options 26 | */ 27 | public function __construct(array $options = []) 28 | { 29 | if (!array_key_exists('encoding', $options) && function_exists('mb_internal_encoding')) { 30 | $options['encoding'] = mb_internal_encoding(); 31 | } 32 | 33 | if (array_key_exists('encoding', $options)) { 34 | $this->setEncoding($options['encoding']); 35 | } 36 | } 37 | 38 | /** 39 | * Returns current encoding. 40 | * 41 | * @return string 42 | */ 43 | public function getEncoding() 44 | { 45 | return $this->encoding; 46 | } 47 | 48 | /** 49 | * @param null $encoding 50 | * 51 | * @return \Kris\LaravelFormBuilder\Filters\Collection\Lowercase 52 | * 53 | * @throws \Exception 54 | */ 55 | public function setEncoding($encoding = null) 56 | { 57 | if ($encoding !== null) { 58 | if (!function_exists('mb_strtolower')) { 59 | $ex = new \Exception('mbstring extension is required for value mutating.'); 60 | throw $ex; 61 | } 62 | 63 | $encoding = (string) $encoding; 64 | if (!in_array(strtolower($encoding), array_map('strtolower', mb_list_encodings()))) { 65 | $ex = new \Exception('The given encoding '.$encoding.' is not supported by mbstring ext.'); 66 | throw $ex; 67 | } 68 | } 69 | 70 | $this->encoding = $encoding; 71 | return $this; 72 | } 73 | 74 | /** 75 | * Returns the string lowercased $value. 76 | * 77 | * @param mixed $value 78 | * @param array $options 79 | * 80 | * @return mixed 81 | */ 82 | public function filter($value, $options = []) 83 | { 84 | $value = (string) $value; 85 | if ($this->getEncoding() !== null) { 86 | return mb_strtolower($value, $this->getEncoding()); 87 | } 88 | 89 | return strtolower($value); 90 | } 91 | 92 | /** 93 | * @return string 94 | */ 95 | public function getName() 96 | { 97 | return 'Lowercase'; 98 | } 99 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/PregReplace.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class PregReplace implements FilterInterface 14 | { 15 | /** 16 | * Pattern to match 17 | * 18 | * @var mixed $pattern 19 | */ 20 | protected $pattern = null; 21 | 22 | /** 23 | * Replacement against matches. 24 | * 25 | * @var mixed $replacement 26 | */ 27 | protected $replacement = ''; 28 | 29 | /** 30 | * PregReplace constructor. 31 | * 32 | * @param array $options 33 | */ 34 | public function __construct($options = []) 35 | { 36 | if (array_key_exists('pattern', $options)) { 37 | $this->setPattern($options['pattern']); 38 | } 39 | 40 | if (array_key_exists('replace', $options)) { 41 | $this->setReplacement($options['replace']); 42 | } 43 | } 44 | 45 | /** 46 | * Set the match pattern for the regex being called within filter(). 47 | * 48 | * @param mixed $pattern - first arg of preg_replace 49 | * 50 | * @return \Kris\LaravelFormBuilder\Filters\Collection\PregReplace 51 | */ 52 | public function setPattern($pattern) 53 | { 54 | $this->pattern = $pattern; 55 | return $this; 56 | } 57 | 58 | /** 59 | * Get currently set match pattern. 60 | * 61 | * @return string 62 | */ 63 | public function getPattern() 64 | { 65 | return $this->pattern; 66 | } 67 | 68 | /** 69 | * Set the Replacement pattern/string for the preg_replace called in filter. 70 | * 71 | * @param mixed $replacement - same as the second argument of preg_replace 72 | * 73 | * @return \Kris\LaravelFormBuilder\Filters\Collection\PregReplace 74 | */ 75 | public function setReplacement($replacement) 76 | { 77 | $this->replacement = $replacement; 78 | return $this; 79 | } 80 | 81 | /** 82 | * Get currently set replacement value. 83 | * 84 | * @return string 85 | */ 86 | public function getReplacement() 87 | { 88 | return $this->replacement; 89 | } 90 | 91 | /** 92 | * @param mixed $value 93 | * @param array $options 94 | * 95 | * @return mixed 96 | * 97 | * @throws \Exception 98 | */ 99 | public function filter($value, $options = []) 100 | { 101 | if ($this->getPattern() == null) { 102 | $ex = new \Exception(get_class($this) . ' does not have a valid MatchPattern set.'); 103 | throw $ex; 104 | } 105 | 106 | return preg_replace($this->getPattern(), $this->getReplacement(), $value); 107 | } 108 | 109 | /** 110 | * @return string 111 | */ 112 | public function getName() 113 | { 114 | return 'PregReplace'; 115 | } 116 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/StripNewlines.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StripNewlines implements FilterInterface 14 | { 15 | /** 16 | * @param mixed $value 17 | * @param array $options 18 | * 19 | * @return mixed 20 | */ 21 | public function filter($value, $options = []) 22 | { 23 | return str_replace(["\n", "\r"], '', $value); 24 | } 25 | 26 | /** 27 | * @return string 28 | */ 29 | public function getName() 30 | { 31 | return 'StripNewlines'; 32 | } 33 | } -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Filters/Collection/StripTags.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StripTags implements FilterInterface 14 | { 15 | /** 16 | * Array of allowed tags and allowed attributes for each allowed tag. 17 | * 18 | * Tags are stored in the array keys, and the array values are themselves 19 | * arrays of the attributes allowed for the corresponding tag. 20 | * 21 | * @var array $allowedTags 22 | */ 23 | protected $allowedTags = []; 24 | 25 | /** 26 | * 27 | * Array of allowed attributes for all allowed tags. 28 | * 29 | * Attributes stored here are allowed for all of the allowed tags. 30 | * 31 | * @var array $allowedAttributes 32 | */ 33 | protected $allowedAttributes = []; 34 | 35 | /** 36 | * StripTags constructor. 37 | * 38 | * @param array $options 39 | */ 40 | public function __construct($options = []) 41 | { 42 | if (array_key_exists('allowedTags', $options)) { 43 | $this->setAllowedTags($options['allowedTags']); 44 | } 45 | 46 | if (array_key_exists('allowedAttribs', $options)) { 47 | $this->setAllowedAttributes($options['allowedAttribs']); 48 | } 49 | } 50 | 51 | /** 52 | * Sets the allowedTags property. 53 | * 54 | * @param array|string $allowedTags 55 | * 56 | * @return \Kris\LaravelFormBuilder\Filters\Collection\StripTags 57 | */ 58 | public function setAllowedTags($allowedTags) 59 | { 60 | if (!is_array($allowedTags)) { 61 | $allowedTags = array($allowedTags); 62 | } 63 | 64 | foreach ($allowedTags as $index => $element) { 65 | 66 | // If the tag was provided without attributes 67 | if (is_int($index) && is_string($element)) { 68 | // Canonicalize the tag name 69 | $tagName = strtolower($element); 70 | // Store the tag as allowed with no attributes 71 | $this->allowedTags[$tagName] = []; 72 | } 73 | 74 | // Otherwise, if a tag was provided with attributes 75 | else if (is_string($index) && (is_array($element) || is_string($element))) { 76 | 77 | // Canonicalize the tag name 78 | $tagName = strtolower($index); 79 | // Canonicalize the attributes 80 | if (is_string($element)) { 81 | $element = [$element]; 82 | } 83 | 84 | // Store the tag as allowed with the provided attributes 85 | $this->allowedTags[$tagName] = []; 86 | foreach ($element as $attribute) { 87 | if (is_string($attribute)) { 88 | // Canonicalize the attribute name 89 | $attributeName = strtolower($attribute); 90 | $this->allowedTags[$tagName][$attributeName] = null; 91 | } 92 | } 93 | 94 | } 95 | } 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getAllowedTags() 104 | { 105 | return $this->allowedTags; 106 | } 107 | 108 | /** 109 | * Sets the allowedAttributes property. 110 | * 111 | * @param array|string $allowedAttribs 112 | * 113 | * @return \Kris\LaravelFormBuilder\Filters\Collection\StripTags 114 | */ 115 | public function setAllowedAttributes($allowedAttribs) 116 | { 117 | if (!is_array($allowedAttribs)) { 118 | $allowedAttribs = [$allowedAttribs]; 119 | } 120 | 121 | // Store each attribute as allowed. 122 | foreach ($allowedAttribs as $attribute) { 123 | if (is_string($attribute)) { 124 | // Canonicalize the attribute name. 125 | $attributeName = strtolower($attribute); 126 | $this->allowedAttributes[$attributeName] = null; 127 | } 128 | } 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * @param mixed $value 135 | * @param array $options 136 | * 137 | * @return string 138 | */ 139 | public function filter($value, $options = []) 140 | { 141 | $value = (string) $value; 142 | 143 | // Strip HTML comments first 144 | while (strpos($value, ' 2 | 3 | --------------------------------------------------------------------------------