├── LICENSE ├── assets ├── js │ ├── app.js │ ├── controllers │ │ ├── datepicker_controller.js │ │ └── datepicker_controller.test.js │ └── setup.test.js └── scss │ └── app.scss ├── composer.json ├── src ├── Bridge │ └── Symfony │ │ ├── DependencyInjection │ │ ├── Configuration.php │ │ └── SonataFormExtension.php │ │ ├── Resources │ │ ├── config │ │ │ ├── date.php │ │ │ ├── form_types.php │ │ │ └── validator.php │ │ ├── meta │ │ │ └── LICENSE │ │ ├── public │ │ │ ├── 0.js │ │ │ ├── 102.js │ │ │ ├── 14.js │ │ │ ├── 200.js │ │ │ ├── 329.js │ │ │ ├── 358.js │ │ │ ├── 38.js │ │ │ ├── 459.js │ │ │ ├── 466.js │ │ │ ├── 51.js │ │ │ ├── 639.js │ │ │ ├── 77.js │ │ │ ├── 785.js │ │ │ ├── 854.js │ │ │ ├── 876.js │ │ │ ├── 929.js │ │ │ ├── 964.js │ │ │ ├── 969.js │ │ │ ├── 987.js │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ ├── entrypoints.json │ │ │ └── manifest.json │ │ ├── translations │ │ │ ├── SonataFormBundle.ar.xliff │ │ │ ├── SonataFormBundle.bg.xliff │ │ │ ├── SonataFormBundle.ca.xliff │ │ │ ├── SonataFormBundle.cs.xliff │ │ │ ├── SonataFormBundle.de.xliff │ │ │ ├── SonataFormBundle.en.xliff │ │ │ ├── SonataFormBundle.es.xliff │ │ │ ├── SonataFormBundle.eu.xliff │ │ │ ├── SonataFormBundle.fa.xliff │ │ │ ├── SonataFormBundle.fi.xliff │ │ │ ├── SonataFormBundle.fr.xliff │ │ │ ├── SonataFormBundle.hr.xliff │ │ │ ├── SonataFormBundle.hu.xliff │ │ │ ├── SonataFormBundle.it.xliff │ │ │ ├── SonataFormBundle.ja.xliff │ │ │ ├── SonataFormBundle.lb.xliff │ │ │ ├── SonataFormBundle.lt.xliff │ │ │ ├── SonataFormBundle.nl.xliff │ │ │ ├── SonataFormBundle.pl.xliff │ │ │ ├── SonataFormBundle.pt.xliff │ │ │ ├── SonataFormBundle.pt_BR.xliff │ │ │ ├── SonataFormBundle.ro.xliff │ │ │ ├── SonataFormBundle.ru.xliff │ │ │ ├── SonataFormBundle.sk.xliff │ │ │ ├── SonataFormBundle.sl.xliff │ │ │ ├── SonataFormBundle.uk.xliff │ │ │ └── SonataFormBundle.zh_CN.xliff │ │ └── views │ │ │ └── Form │ │ │ └── datepicker.html.twig │ │ └── SonataFormBundle.php ├── DataTransformer │ └── BooleanTypeToBooleanTransformer.php ├── Date │ └── JavaScriptFormatConverter.php ├── EventListener │ ├── FixCheckboxDataListener.php │ └── ResizeFormListener.php ├── Fixtures │ └── StubTranslator.php ├── Test │ └── AbstractWidgetTestCase.php ├── Type │ ├── BasePickerType.php │ ├── BaseStatusType.php │ ├── BooleanType.php │ ├── CollectionType.php │ ├── DatePickerType.php │ ├── DateRangePickerType.php │ ├── DateRangeType.php │ ├── DateTimePickerType.php │ ├── DateTimeRangePickerType.php │ ├── DateTimeRangeType.php │ └── ImmutableArrayType.php └── Validator │ ├── Constraints │ └── InlineConstraint.php │ ├── ErrorElement.php │ └── InlineValidator.php └── vite.config.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 Thomas Rabaix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | // Any SCSS/CSS you require will output into a single css file (app.css in this case) 11 | import '../scss/app.scss'; 12 | 13 | // eslint-disable-next-line import/no-unresolved 14 | import DatePicker from '@symfony/stimulus-bridge/lazy-controller-loader?lazy=true!./controllers/datepicker_controller.js'; 15 | 16 | const { sonataApplication } = global; 17 | 18 | sonataApplication.register('datepicker', DatePicker); 19 | -------------------------------------------------------------------------------- /assets/js/controllers/datepicker_controller.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { Controller } from '@hotwired/stimulus'; 11 | import { TempusDominus, Namespace, loadLocale } from '@eonasdan/tempus-dominus'; 12 | import { faFiveIcons } from '@eonasdan/tempus-dominus/dist/plugins/fa-five'; 13 | 14 | export default class extends Controller { 15 | static outlets = ['datepicker']; 16 | 17 | static values = { 18 | options: Object, 19 | }; 20 | 21 | #allowedLocales = [ 22 | 'ar', 23 | 'ar-SA', 24 | 'de', 25 | 'es', 26 | 'fi', 27 | 'fr', 28 | 'it', 29 | 'nl', 30 | 'pl', 31 | 'ro', 32 | 'ru', 33 | 'sl', 34 | 'tr', 35 | ]; 36 | 37 | datePicker = null; 38 | 39 | connect() { 40 | const options = this.processOptions(); 41 | const locale = this.processLocale(options); 42 | 43 | this.dispatchEvent('pre-connect', { options, locale }); 44 | 45 | this.datePicker = new TempusDominus(this.element, options); 46 | 47 | if (locale !== null) { 48 | import(`@eonasdan/tempus-dominus/dist/locales/${locale}`).then((data) => { 49 | loadLocale(data); 50 | 51 | this.datePicker.locale(data.name); 52 | this.datePicker.updateOptions(options); 53 | 54 | this.dispatchEvent('post-connect-changed-locale'); 55 | }); 56 | } 57 | 58 | this.dispatchEvent('connect', { datePicker: this.datePicker }); 59 | } 60 | 61 | datepickerOutletConnected(outlet, element) { 62 | this.element.addEventListener(Namespace.events.change, (event) => { 63 | outlet.datePicker.updateOptions({ 64 | restrictions: { 65 | minDate: event.detail.date, 66 | }, 67 | }); 68 | }); 69 | 70 | element.addEventListener(Namespace.events.change, (event) => { 71 | this.datePicker.updateOptions({ 72 | restrictions: { 73 | maxDate: event.detail.date, 74 | }, 75 | }); 76 | }); 77 | } 78 | 79 | processOptions() { 80 | const options = this.optionsValue; 81 | const { restrictions = {}, display = {} } = options; 82 | 83 | if (options?.defaultDate) { 84 | options.defaultDate = new Date(options.defaultDate); 85 | } 86 | 87 | if (options?.viewDate) { 88 | options.viewDate = new Date(options.viewDate); 89 | } 90 | 91 | if (restrictions?.minDate) { 92 | restrictions.minDate = new Date(restrictions.minDate); 93 | } 94 | 95 | if (restrictions?.maxDate) { 96 | restrictions.maxDate = new Date(restrictions.maxDate); 97 | } 98 | 99 | if (restrictions?.disabledDates) { 100 | restrictions.disabledDates = restrictions.disabledDates.map((date) => new Date(date)); 101 | } 102 | 103 | if (restrictions?.enabledDates) { 104 | restrictions.enabledDates = restrictions.enabledDates.map((date) => new Date(date)); 105 | } 106 | 107 | if (!display?.icons) { 108 | display.icons = faFiveIcons; 109 | } 110 | 111 | return options; 112 | } 113 | 114 | processLocale(options) { 115 | const { localization: { locale } = {} } = options; 116 | 117 | if (!locale) { 118 | return null; 119 | } 120 | 121 | if (this.#allowedLocales.includes(locale)) { 122 | return locale; 123 | } 124 | 125 | if (!locale.includes('-')) { 126 | return null; 127 | } 128 | 129 | const localeCode = locale.split('-')[0]; 130 | 131 | if (this.#allowedLocales.includes(localeCode)) { 132 | return localeCode; 133 | } 134 | 135 | return null; 136 | } 137 | 138 | dispatchEvent(name, payload) { 139 | this.dispatch(name, { detail: payload, prefix: 'datepicker' }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /assets/js/controllers/datepicker_controller.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import { Application } from '@hotwired/stimulus'; 11 | import { getByTestId, waitFor } from '@testing-library/dom'; 12 | import userEvent from '@testing-library/user-event'; 13 | import { afterEach, beforeAll, describe, expect, it } from 'vitest'; 14 | import DatepickerController from './datepicker_controller'; 15 | 16 | const startDatepickerTest = async (html) => { 17 | let datePicker = null; 18 | let datePicker2 = null; 19 | 20 | document.body.addEventListener('datepicker:pre-connect', () => { 21 | document.body.classList.add('pre-connected'); 22 | }); 23 | 24 | document.body.addEventListener('datepicker:connect', (event) => { 25 | if (!datePicker) datePicker = event.detail.datePicker; 26 | else datePicker2 = event.detail.datePicker; 27 | 28 | document.body.classList.add('connected'); 29 | }); 30 | 31 | document.body.addEventListener('datepicker:post-connect-changed-locale', () => { 32 | document.body.classList.add('post-connect-changed-locale'); 33 | }); 34 | 35 | document.body.innerHTML = html; 36 | 37 | await waitFor(() => { 38 | expect(document.body).toHaveClass('pre-connected'); 39 | expect(document.body).toHaveClass('connected'); 40 | }); 41 | 42 | if (!datePicker) { 43 | throw new Error('Missing DatePicker instance'); 44 | } 45 | 46 | return { datePicker, datePicker2 }; 47 | }; 48 | 49 | const innerDatepickerTest = ` 50 | 55 | 60 | `; 61 | 62 | const tempusDominusCalendar = () => document.querySelector('.tempus-dominus-widget.show'); 63 | 64 | describe('DatepickerController', () => { 65 | beforeAll(() => { 66 | const application = Application.start(); 67 | 68 | application.register('datepicker', DatepickerController); 69 | }); 70 | 71 | afterEach(() => { 72 | document.body.innerHTML = ''; 73 | }); 74 | 75 | it('connects without options', async () => { 76 | const { datePicker } = await startDatepickerTest(` 77 |
${innerDatepickerTest}
83 | `); 84 | 85 | expect(datePicker).toBeDefined(); 86 | }); 87 | 88 | it('can be opened', async () => { 89 | await startDatepickerTest(` 90 |
${innerDatepickerTest}
96 | `); 97 | 98 | const icon = getByTestId(document, 'icon'); 99 | 100 | expect(tempusDominusCalendar()).toBeNull(); 101 | 102 | await userEvent.click(icon); 103 | 104 | expect(tempusDominusCalendar()).toHaveClass('show'); 105 | }); 106 | 107 | it('can receive options', async () => { 108 | const { datePicker } = await startDatepickerTest(` 109 |
${innerDatepickerTest}
118 | `); 119 | 120 | const icon = getByTestId(document, 'icon'); 121 | 122 | await userEvent.click(icon); 123 | 124 | expect(tempusDominusCalendar().querySelector('.date-container')).toBeNull(); 125 | expect(datePicker.optionsStore.options.display.components.calendar).toBe(false); 126 | }); 127 | 128 | it('can be localized', async () => { 129 | const { datePicker } = await startDatepickerTest(` 130 |
${innerDatepickerTest}
139 | `); 140 | 141 | expect(datePicker.optionsStore.options.localization.locale).toBe('fr'); 142 | 143 | const icon = getByTestId(document, 'icon'); 144 | 145 | await userEvent.click(icon); 146 | 147 | await waitFor(() => { 148 | expect(document.body).toHaveClass('post-connect-changed-locale'); 149 | }); 150 | 151 | expect(tempusDominusCalendar().querySelector('.picker-switch')).toHaveProperty( 152 | 'title', 153 | 'Sélectionner le mois' 154 | ); 155 | }); 156 | 157 | it('can be localized for locales with dash', async () => { 158 | const { datePicker } = await startDatepickerTest(` 159 |
${innerDatepickerTest}
168 | `); 169 | 170 | expect(datePicker.optionsStore.options.localization.locale).toBe('fr-FR'); 171 | 172 | const icon = getByTestId(document, 'icon'); 173 | 174 | await userEvent.click(icon); 175 | 176 | await waitFor(() => { 177 | expect(document.body).toHaveClass('post-connect-changed-locale'); 178 | }); 179 | 180 | expect(tempusDominusCalendar().querySelector('.picker-switch')).toHaveProperty( 181 | 'title', 182 | 'Sélectionner le mois' 183 | ); 184 | }); 185 | 186 | it('it does not localize for non supported locales', async () => { 187 | const { datePicker } = await startDatepickerTest(` 188 |
${innerDatepickerTest}
197 | `); 198 | 199 | expect(datePicker.optionsStore.options.localization.locale).toBe('ca'); 200 | 201 | const icon = getByTestId(document, 'icon'); 202 | 203 | await userEvent.click(icon); 204 | 205 | await waitFor(() => { 206 | expect(document.body).toHaveClass('post-connect-changed-locale'); 207 | }); 208 | 209 | expect(tempusDominusCalendar().querySelector('.picker-switch')).toHaveProperty( 210 | 'title', 211 | 'Select Month' 212 | ); 213 | }); 214 | 215 | it('it does not localize for non supported locales with dash', async () => { 216 | const { datePicker } = await startDatepickerTest(` 217 |
${innerDatepickerTest}
226 | `); 227 | 228 | expect(datePicker.optionsStore.options.localization.locale).toBe('ca-ES'); 229 | 230 | const icon = getByTestId(document, 'icon'); 231 | 232 | await userEvent.click(icon); 233 | 234 | await waitFor(() => { 235 | expect(document.body).toHaveClass('post-connect-changed-locale'); 236 | }); 237 | 238 | expect(tempusDominusCalendar().querySelector('.picker-switch')).toHaveProperty( 239 | 'title', 240 | 'Select Month' 241 | ); 242 | }); 243 | 244 | it('can select a date', async () => { 245 | await startDatepickerTest(` 246 |
${innerDatepickerTest}
255 | `); 256 | 257 | const icon = getByTestId(document, 'icon'); 258 | const input = getByTestId(document, 'input'); 259 | 260 | await userEvent.click(icon); 261 | 262 | const calendar = tempusDominusCalendar(); 263 | 264 | expect(calendar).toHaveClass('show'); 265 | expect(input.value).toBe(''); 266 | 267 | await userEvent.click(calendar.querySelector('.day')); 268 | 269 | expect(input.value).not.toBe(''); 270 | expect(calendar).toHaveClass('show'); 271 | 272 | await userEvent.click(document.body); 273 | 274 | expect(calendar).not.toHaveClass('show'); 275 | }); 276 | 277 | it('can be used without icon', async () => { 278 | await startDatepickerTest(` 279 | 285 | `); 286 | 287 | const input = getByTestId(document, 'input'); 288 | 289 | expect(tempusDominusCalendar()).toBeNull(); 290 | 291 | await userEvent.click(input); 292 | 293 | expect(tempusDominusCalendar()).toHaveClass('show'); 294 | }); 295 | 296 | it('can receive multiple options', async () => { 297 | await startDatepickerTest(` 298 |
${innerDatepickerTest}
368 | `); 369 | 370 | await waitFor(() => { 371 | expect(document.body).toHaveClass('post-connect-changed-locale'); 372 | }); 373 | }); 374 | 375 | it('can be used with related pickers', async () => { 376 | const { datePicker, datePicker2 } = await startDatepickerTest(` 377 |
${innerDatepickerTest}
384 | 390 | `); 391 | 392 | expect(datePicker.optionsStore.options.restrictions.maxDate).toBeUndefined(); 393 | expect(datePicker2.optionsStore.options.restrictions.minDate).toBeUndefined(); 394 | 395 | const firstIcon = getByTestId(document, 'icon'); 396 | const secondInput = getByTestId(document, 'input2'); 397 | 398 | await userEvent.click(firstIcon); 399 | 400 | const calendar = tempusDominusCalendar(); 401 | 402 | await userEvent.click(calendar.querySelector('.day')); 403 | await userEvent.click(secondInput); 404 | 405 | const secondCalendar = tempusDominusCalendar(); 406 | 407 | await userEvent.click(secondCalendar.querySelector('.day')); 408 | 409 | expect(datePicker.optionsStore.options.restrictions.maxDate).not.toBeUndefined(); 410 | expect(datePicker2.optionsStore.options.restrictions.minDate).not.toBeUndefined(); 411 | expect( 412 | datePicker.optionsStore.options.restrictions.maxDate >= 413 | datePicker2.optionsStore.options.restrictions.minDate 414 | ).toBe(true); 415 | }); 416 | }); 417 | -------------------------------------------------------------------------------- /assets/js/setup.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import matchers from '@testing-library/jest-dom/matchers'; 11 | import { vi, expect } from 'vitest'; 12 | 13 | expect.extend(matchers); 14 | 15 | Object.defineProperty(window, 'matchMedia', { 16 | writable: true, 17 | value: vi.fn().mockImplementation((query) => ({ 18 | matches: false, 19 | media: query, 20 | onchange: null, 21 | addListener: vi.fn(), // deprecated 22 | removeListener: vi.fn(), // deprecated 23 | addEventListener: vi.fn(), 24 | removeEventListener: vi.fn(), 25 | dispatchEvent: vi.fn(), 26 | })), 27 | }); 28 | -------------------------------------------------------------------------------- /assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | @import '@eonasdan/tempus-dominus'; 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sonata-project/form-extensions", 3 | "description": "Symfony form extensions", 4 | "license": "MIT", 5 | "type": "symfony-bundle", 6 | "keywords": [ 7 | "symfony", 8 | "form" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Thomas Rabaix", 13 | "email": "thomas.rabaix@sonata-project.org", 14 | "homepage": "https://sonata-project.org" 15 | }, 16 | { 17 | "name": "Sonata Community", 18 | "homepage": "https://github.com/sonata-project/form-extensions/contributors" 19 | } 20 | ], 21 | "homepage": "https://docs.sonata-project.org/projects/form-extensions", 22 | "require": { 23 | "php": "^8.1", 24 | "symfony/event-dispatcher": "^6.4 || ^7.1", 25 | "symfony/form": "^6.4 || ^7.1", 26 | "symfony/options-resolver": "^6.4 || ^7.1", 27 | "symfony/property-access": "^6.4 || ^7.1", 28 | "symfony/security-csrf": "^6.4 || ^7.1", 29 | "symfony/translation": "^6.4 || ^7.1", 30 | "symfony/translation-contracts": "^2.5 || ^3.0", 31 | "symfony/validator": "^6.4 || ^7.1", 32 | "twig/twig": "^3.0" 33 | }, 34 | "require-dev": { 35 | "friendsofphp/php-cs-fixer": "^3.4", 36 | "matthiasnoback/symfony-config-test": "^4.2 || ^5.1", 37 | "matthiasnoback/symfony-dependency-injection-test": "^4.0 || ^5.0", 38 | "phpstan/extension-installer": "^1.0", 39 | "phpstan/phpdoc-parser": "^1.0", 40 | "phpstan/phpstan": "^1.0 || ^2.0", 41 | "phpstan/phpstan-phpunit": "^1.0 || ^2.0", 42 | "phpstan/phpstan-strict-rules": "^1.0 || ^2.0", 43 | "phpstan/phpstan-symfony": "^1.0 || ^2.0", 44 | "phpunit/phpunit": "^9.5", 45 | "psalm/plugin-phpunit": "^0.18 || ^0.19", 46 | "psalm/plugin-symfony": "^5.0", 47 | "rector/rector": "^1.1 || ^2.0", 48 | "symfony/config": "^6.4 || ^7.1", 49 | "symfony/dependency-injection": "^6.4 || ^7.1", 50 | "symfony/framework-bundle": "^6.4 || ^7.1", 51 | "symfony/http-foundation": "^6.4 || ^7.1", 52 | "symfony/http-kernel": "^6.4 || ^7.1", 53 | "symfony/phpunit-bridge": "^6.4 || ^7.1", 54 | "symfony/twig-bridge": "^6.4.3 || ^7.1", 55 | "symfony/twig-bundle": "^6.4 || ^7.1", 56 | "vimeo/psalm": "^5.8 || ^6.10" 57 | }, 58 | "minimum-stability": "dev", 59 | "prefer-stable": true, 60 | "autoload": { 61 | "psr-4": { 62 | "Sonata\\Form\\": "src/" 63 | } 64 | }, 65 | "autoload-dev": { 66 | "psr-4": { 67 | "Sonata\\Form\\Tests\\": "tests/" 68 | } 69 | }, 70 | "config": { 71 | "allow-plugins": { 72 | "composer/package-versions-deprecated": true, 73 | "phpstan/extension-installer": true 74 | }, 75 | "sort-packages": true 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Bridge\Symfony\DependencyInjection; 15 | 16 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 17 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 18 | use Symfony\Component\Config\Definition\ConfigurationInterface; 19 | 20 | /** 21 | * This is the class that validates and merges configuration from your app/config files. 22 | * 23 | * @author Thomas Rabaix 24 | * @author Alexander 25 | */ 26 | final class Configuration implements ConfigurationInterface 27 | { 28 | public function getConfigTreeBuilder(): TreeBuilder 29 | { 30 | $treeBuilder = new TreeBuilder('sonata_form'); 31 | $rootNode = $treeBuilder->getRootNode(); 32 | 33 | $this->addFlashMessageSection($rootNode); 34 | 35 | return $treeBuilder; 36 | } 37 | 38 | /** 39 | * Returns configuration for flash messages. 40 | * 41 | * @psalm-suppress UndefinedInterfaceMethod 42 | * 43 | * @see https://github.com/psalm/psalm-plugin-symfony/issues/174 44 | */ 45 | private function addFlashMessageSection(ArrayNodeDefinition $node): void 46 | { 47 | $node 48 | ->children() 49 | ->scalarNode('form_type') 50 | ->defaultValue('standard') 51 | ->validate() 52 | ->ifNotInArray($validFormTypes = ['standard', 'horizontal']) 53 | ->thenInvalid(\sprintf( 54 | 'The form_type option value must be one of %s', 55 | $validFormTypesString = implode(', ', $validFormTypes) 56 | )) 57 | ->end() 58 | ->info(\sprintf('Must be one of %s', $validFormTypesString)) 59 | ->end() 60 | ->end(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/DependencyInjection/SonataFormExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Bridge\Symfony\DependencyInjection; 15 | 16 | use Symfony\Component\Config\Definition\Processor; 17 | use Symfony\Component\Config\FileLocator; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Extension\Extension; 20 | use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; 21 | use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; 22 | 23 | /** 24 | * @author Thomas Rabaix 25 | */ 26 | final class SonataFormExtension extends Extension implements PrependExtensionInterface 27 | { 28 | public function prepend(ContainerBuilder $container): void 29 | { 30 | $configs = $container->getExtensionConfig('sonata_admin'); 31 | 32 | foreach ($configs as $config) { 33 | if (isset($config['options']['form_type'])) { 34 | $container->prependExtensionConfig( 35 | $this->getAlias(), 36 | ['form_type' => $config['options']['form_type']] 37 | ); 38 | } 39 | } 40 | 41 | // add custom form widgets 42 | if ($container->hasExtension('twig')) { 43 | $container->prependExtensionConfig('twig', [ 44 | 'form_themes' => ['@SonataForm/Form/datepicker.html.twig'], 45 | ]); 46 | } 47 | } 48 | 49 | public function load(array $configs, ContainerBuilder $container): void 50 | { 51 | $processor = new Processor(); 52 | $configuration = new Configuration(); 53 | $config = $processor->processConfiguration($configuration, $configs); 54 | 55 | $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 56 | $loader->load('date.php'); 57 | $loader->load('form_types.php'); 58 | $loader->load('validator.php'); 59 | 60 | $container->setParameter('sonata.form.form_type', $config['form_type']); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/config/date.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\Form\Date\JavaScriptFormatConverter; 17 | 18 | return static function (ContainerConfigurator $containerConfigurator): void { 19 | $containerConfigurator->services() 20 | 21 | ->set('sonata.form.date.javascript_format_converter', JavaScriptFormatConverter::class); 22 | }; 23 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/config/form_types.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\Form\Type\BooleanType; 17 | use Sonata\Form\Type\CollectionType; 18 | use Sonata\Form\Type\DatePickerType; 19 | use Sonata\Form\Type\DateRangePickerType; 20 | use Sonata\Form\Type\DateRangeType; 21 | use Sonata\Form\Type\DateTimePickerType; 22 | use Sonata\Form\Type\DateTimeRangePickerType; 23 | use Sonata\Form\Type\DateTimeRangeType; 24 | use Sonata\Form\Type\ImmutableArrayType; 25 | 26 | return static function (ContainerConfigurator $containerConfigurator): void { 27 | $containerConfigurator->services() 28 | 29 | ->set('sonata.form.type.array', ImmutableArrayType::class) 30 | ->tag('form.type', ['alias' => 'sonata_type_immutable_array']) 31 | 32 | ->set('sonata.form.type.boolean', BooleanType::class) 33 | ->tag('form.type', ['alias' => 'sonata_type_boolean']) 34 | 35 | ->set('sonata.form.type.collection', CollectionType::class) 36 | ->tag('form.type', ['alias' => 'sonata_type_collection']) 37 | 38 | ->set('sonata.form.type.date_range', DateRangeType::class) 39 | ->tag('form.type', ['alias' => 'sonata_type_date_range']) 40 | 41 | ->set('sonata.form.type.datetime_range', DateTimeRangeType::class) 42 | ->tag('form.type', ['alias' => 'sonata_type_datetime_range']) 43 | 44 | ->set('sonata.form.type.date_picker', DatePickerType::class) 45 | ->tag('kernel.locale_aware') 46 | ->tag('form.type', ['alias' => 'sonata_type_date_picker']) 47 | ->args([ 48 | service('sonata.form.date.javascript_format_converter'), 49 | param('kernel.default_locale'), 50 | ]) 51 | 52 | ->set('sonata.form.type.datetime_picker', DateTimePickerType::class) 53 | ->tag('kernel.locale_aware') 54 | ->tag('form.type', ['alias' => 'sonata_type_datetime_picker']) 55 | ->args([ 56 | service('sonata.form.date.javascript_format_converter'), 57 | param('kernel.default_locale'), 58 | ]) 59 | 60 | ->set('sonata.form.type.date_range_picker', DateRangePickerType::class) 61 | ->tag('form.type', ['alias' => 'sonata_type_date_range_picker']) 62 | 63 | ->set('sonata.form.type.datetime_range_picker', DateTimeRangePickerType::class) 64 | ->tag('form.type', ['alias' => 'sonata_type_datetime_range_picker']); 65 | }; 66 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/config/validator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Symfony\Component\DependencyInjection\Loader\Configurator; 15 | 16 | use Sonata\Form\Validator\InlineValidator; 17 | 18 | return static function (ContainerConfigurator $containerConfigurator): void { 19 | $containerConfigurator->services() 20 | ->set('sonata.form.validator.inline', InlineValidator::class) 21 | ->tag('validator.constraint_validator', [ 22 | 'alias' => 'sonata.form.validator.inline', 23 | ]) 24 | ->args([ 25 | service('service_container'), 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2015 thomas.rabaix@sonata-project.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/0.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[0],[function(e,n){!function(e){"use strict";const n="nl",t={today:"Vandaag",clear:"Verwijder selectie",close:"Sluit de picker",selectMonth:"Selecteer een maand",previousMonth:"Vorige maand",nextMonth:"Volgende maand",selectYear:"Selecteer een jaar",previousYear:"Vorige jaar",nextYear:"Volgende jaar",selectDecade:"Selecteer decennium",previousDecade:"Vorige decennium",nextDecade:"Volgende decennium",previousCentury:"Vorige eeuw",nextCentury:"Volgende eeuw",pickHour:"Kies een uur",incrementHour:"Verhoog uur",decrementHour:"Verlaag uur",pickMinute:"Kies een minute",incrementMinute:"Verhoog minuut",decrementMinute:"Verlaag minuut",pickSecond:"Kies een seconde",incrementSecond:"Verhoog seconde",decrementSecond:"Verlaag seconde",toggleMeridiem:"Schakel tussen AM/PM",selectTime:"Selecteer een tijd",selectDate:"Selecteer een datum",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"nl",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd-MM-yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd d MMMM yyyy HH:mm"},ordinal:e=>`[${e}${1===e||8===e||e>=20?"ste":"de"}]`,format:"L LT"};e.localization=t,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}]]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/102.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[102],{102:function(e,n){!function(e){"use strict";const n="fr",t={today:"Aujourd'hui",clear:"Effacer la sélection",close:"Fermer",selectMonth:"Sélectionner le mois",previousMonth:"Mois précédent",nextMonth:"Mois suivant",selectYear:"Sélectionner l'année",previousYear:"Année précédente",nextYear:"Année suivante",selectDecade:"Sélectionner la décennie",previousDecade:"Décennie précédente",nextDecade:"Décennie suivante",previousCentury:"Siècle précédente",nextCentury:"Siècle suivante",pickHour:"Sélectionner l'heure",incrementHour:"Incrementer l'heure",decrementHour:"Diminuer l'heure",pickMinute:"Sélectionner les minutes",incrementMinute:"Incrementer les minutes",decrementMinute:"Diminuer les minutes",pickSecond:"Sélectionner les secondes",incrementSecond:"Incrementer les secondes",decrementSecond:"Diminuer les secondes",toggleMeridiem:"Basculer AM-PM",selectTime:"Sélectionner l'heure",selectDate:"Sélectionner une date",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"fr",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd/MM/yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd d MMMM yyyy HH:mm"},ordinal:e=>`${e}${1===e?"er":""}`,format:"L LT"};e.localization=t,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/14.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[14],{14:function(e,r){!function(e){"use strict";const r="es",n={today:"Hoy",clear:"Borrar selección",close:"Cerrar selector",selectMonth:"Seleccionar mes",previousMonth:"Mes anterior",nextMonth:"Próximo mes",selectYear:"Seleccionar año",previousYear:"Año anterior",nextYear:"Próximo año",selectDecade:"Seleccionar década",previousDecade:"Década anterior",nextDecade:"Próxima década",previousCentury:"Siglo anterior",nextCentury:"Próximo siglo",pickHour:"Elegir hora",incrementHour:"Incrementar hora",decrementHour:"Decrementar hora",pickMinute:"Elegir minuto",incrementMinute:"Incrementar minuto",decrementMinute:"Decrementar minuto",pickSecond:"Elegir segundo",incrementSecond:"Incrementar segundo",decrementSecond:"Decrementar segundo",toggleMeridiem:"Cambiar AM/PM",selectTime:"Seleccionar tiempo",selectDate:"Seleccionar fecha",dayViewHeaderFormat:{month:"long",year:"2-digit"},startOfTheWeek:1,locale:"es",dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd/MM/yyyy",LL:"d [de] MMMM [de] yyyy",LLL:"d [de] MMMM [de] yyyy H:mm",LLLL:"dddd, d [de] MMMM [de] yyyy H:mm"},ordinal:e=>`${e}º`,format:"L LT"};e.localization=n,e.name=r,Object.defineProperty(e,"__esModule",{value:!0})}(r)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/200.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[200],{200:function(e,t,n){function r(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function o(e){return e instanceof r(e).Element||e instanceof Element}function i(e){return e instanceof r(e).HTMLElement||e instanceof HTMLElement}function a(e){return"undefined"!=typeof ShadowRoot&&(e instanceof r(e).ShadowRoot||e instanceof ShadowRoot)}n.d(t,{createPopper:function(){return he}});var s=Math.max,f=Math.min,c=Math.round;function p(){var e=navigator.userAgentData;return null!=e&&e.brands&&Array.isArray(e.brands)?e.brands.map((function(e){return e.brand+"/"+e.version})).join(" "):navigator.userAgent}function u(){return!/^((?!chrome|android).)*safari/i.test(p())}function l(e,t,n){void 0===t&&(t=!1),void 0===n&&(n=!1);var a=e.getBoundingClientRect(),s=1,f=1;t&&i(e)&&(s=e.offsetWidth>0&&c(a.width)/e.offsetWidth||1,f=e.offsetHeight>0&&c(a.height)/e.offsetHeight||1);var p=(o(e)?r(e):window).visualViewport,l=!u()&&n,d=(a.left+(l&&p?p.offsetLeft:0))/s,h=(a.top+(l&&p?p.offsetTop:0))/f,m=a.width/s,v=a.height/f;return{width:m,height:v,top:h,right:d+m,bottom:h+v,left:d,x:d,y:h}}function d(e){var t=r(e);return{scrollLeft:t.pageXOffset,scrollTop:t.pageYOffset}}function h(e){return e?(e.nodeName||"").toLowerCase():null}function m(e){return((o(e)?e.ownerDocument:e.document)||window.document).documentElement}function v(e){return l(m(e)).left+d(e).scrollLeft}function g(e){return r(e).getComputedStyle(e)}function y(e){var t=g(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function b(e,t,n){void 0===n&&(n=!1);var o,a,s=i(t),f=i(t)&&function(e){var t=e.getBoundingClientRect(),n=c(t.width)/e.offsetWidth||1,r=c(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(t),p=m(t),u=l(e,f,n),g={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(s||!s&&!n)&&(("body"!==h(t)||y(p))&&(g=(o=t)!==r(o)&&i(o)?{scrollLeft:(a=o).scrollLeft,scrollTop:a.scrollTop}:d(o)),i(t)?((b=l(t,!0)).x+=t.clientLeft,b.y+=t.clientTop):p&&(b.x=v(p))),{x:u.left+g.scrollLeft-b.x,y:u.top+g.scrollTop-b.y,width:u.width,height:u.height}}function x(e){var t=l(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function w(e){return"html"===h(e)?e:e.assignedSlot||e.parentNode||(a(e)?e.host:null)||m(e)}function O(e){return["html","body","#document"].indexOf(h(e))>=0?e.ownerDocument.body:i(e)&&y(e)?e:O(w(e))}function j(e,t){var n;void 0===t&&(t=[]);var o=O(e),i=o===(null==(n=e.ownerDocument)?void 0:n.body),a=r(o),s=i?[a].concat(a.visualViewport||[],y(o)?o:[]):o,f=t.concat(s);return i?f:f.concat(j(w(s)))}function E(e){return["table","td","th"].indexOf(h(e))>=0}function D(e){return i(e)&&"fixed"!==g(e).position?e.offsetParent:null}function A(e){for(var t=r(e),n=D(e);n&&E(n)&&"static"===g(n).position;)n=D(n);return n&&("html"===h(n)||"body"===h(n)&&"static"===g(n).position)?t:n||function(e){var t=/firefox/i.test(p());if(/Trident/i.test(p())&&i(e)&&"fixed"===g(e).position)return null;var n=w(e);for(a(n)&&(n=n.host);i(n)&&["html","body"].indexOf(h(n))<0;){var r=g(n);if("none"!==r.transform||"none"!==r.perspective||"paint"===r.contain||-1!==["transform","perspective"].indexOf(r.willChange)||t&&"filter"===r.willChange||t&&r.filter&&"none"!==r.filter)return n;n=n.parentNode}return null}(e)||t}var k="top",L="bottom",P="right",W="left",M="auto",B=[k,L,P,W],H="start",R="end",T="viewport",C="popper",S=B.reduce((function(e,t){return e.concat([t+"-"+H,t+"-"+R])}),[]),V=[].concat(B,[M]).reduce((function(e,t){return e.concat([t,t+"-"+H,t+"-"+R])}),[]),q=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function _(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}var N={placement:"bottom",modifiers:[],strategy:"absolute"};function I(){for(var e=arguments.length,t=new Array(e),n=0;n=0?"x":"y"}function G(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?z(o):null,a=o?X(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case k:t={x:s,y:n.y-r.height};break;case L:t={x:s,y:n.y+n.height};break;case P:t={x:n.x+n.width,y:f};break;case W:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?Y(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case H:t[c]=t[c]-(n[p]/2-r[p]/2);break;case R:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}var J={top:"auto",right:"auto",bottom:"auto",left:"auto"};function K(e){var t,n=e.popper,o=e.popperRect,i=e.placement,a=e.variation,s=e.offsets,f=e.position,p=e.gpuAcceleration,u=e.adaptive,l=e.roundOffsets,d=e.isFixed,h=s.x,v=void 0===h?0:h,y=s.y,b=void 0===y?0:y,x="function"==typeof l?l({x:v,y:b}):{x:v,y:b};v=x.x,b=x.y;var w=s.hasOwnProperty("x"),O=s.hasOwnProperty("y"),j=W,E=k,D=window;if(u){var M=A(n),B="clientHeight",H="clientWidth";if(M===r(n)&&"static"!==g(M=m(n)).position&&"absolute"===f&&(B="scrollHeight",H="scrollWidth"),i===k||(i===W||i===P)&&a===R)E=L,b-=(d&&M===D&&D.visualViewport?D.visualViewport.height:M[B])-o.height,b*=p?1:-1;if(i===W||(i===k||i===L)&&a===R)j=P,v-=(d&&M===D&&D.visualViewport?D.visualViewport.width:M[H])-o.width,v*=p?1:-1}var T,C=Object.assign({position:f},u&&J),S=!0===l?function(e,t){var n=e.x,r=e.y,o=t.devicePixelRatio||1;return{x:c(n*o)/o||0,y:c(r*o)/o||0}}({x:v,y:b},r(n)):{x:v,y:b};return v=S.x,b=S.y,p?Object.assign({},C,((T={})[E]=O?"0":"",T[j]=w?"0":"",T.transform=(D.devicePixelRatio||1)<=1?"translate("+v+"px, "+b+"px)":"translate3d("+v+"px, "+b+"px, 0)",T)):Object.assign({},C,((t={})[E]=O?b+"px":"",t[j]=w?v+"px":"",t.transform="",t))}var Q={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.offset,i=void 0===o?[0,0]:o,a=V.reduce((function(e,n){return e[n]=function(e,t,n){var r=z(e),o=[W,k].indexOf(r)>=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[W,P].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},Z={left:"right",right:"left",bottom:"top",top:"bottom"};function $(e){return e.replace(/left|right|bottom|top/g,(function(e){return Z[e]}))}var ee={start:"end",end:"start"};function te(e){return e.replace(/start|end/g,(function(e){return ee[e]}))}function ne(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&a(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function re(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function oe(e,t,n){return t===T?re(function(e,t){var n=r(e),o=m(e),i=n.visualViewport,a=o.clientWidth,s=o.clientHeight,f=0,c=0;if(i){a=i.width,s=i.height;var p=u();(p||!p&&"fixed"===t)&&(f=i.offsetLeft,c=i.offsetTop)}return{width:a,height:s,x:f+v(e),y:c}}(e,n)):o(t)?function(e,t){var n=l(e,!1,"fixed"===t);return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}(t,n):re(function(e){var t,n=m(e),r=d(e),o=null==(t=e.ownerDocument)?void 0:t.body,i=s(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),a=s(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+v(e),c=-r.scrollTop;return"rtl"===g(o||n).direction&&(f+=s(n.clientWidth,o?o.clientWidth:0)-i),{width:i,height:a,x:f,y:c}}(m(e)))}function ie(e,t,n,r){var a="clippingParents"===t?function(e){var t=j(w(e)),n=["absolute","fixed"].indexOf(g(e).position)>=0&&i(e)?A(e):e;return o(n)?t.filter((function(e){return o(e)&&ne(e,n)&&"body"!==h(e)})):[]}(e):[].concat(t),c=[].concat(a,[n]),p=c[0],u=c.reduce((function(t,n){var o=oe(e,n,r);return t.top=s(o.top,t.top),t.right=f(o.right,t.right),t.bottom=f(o.bottom,t.bottom),t.left=s(o.left,t.left),t}),oe(e,p,r));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function ae(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function se(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function fe(e,t){void 0===t&&(t={});var n=t,r=n.placement,i=void 0===r?e.placement:r,a=n.strategy,s=void 0===a?e.strategy:a,f=n.boundary,c=void 0===f?"clippingParents":f,p=n.rootBoundary,u=void 0===p?T:p,d=n.elementContext,h=void 0===d?C:d,v=n.altBoundary,g=void 0!==v&&v,y=n.padding,b=void 0===y?0:y,x=ae("number"!=typeof b?b:se(b,B)),w=h===C?"reference":C,O=e.rects.popper,j=e.elements[g?w:h],E=ie(o(j)?j:j.contextElement||m(e.elements.popper),c,u,s),D=l(e.elements.reference),A=G({reference:D,element:O,strategy:"absolute",placement:i}),W=re(Object.assign({},O,A)),M=h===C?W:D,H={top:E.top-M.top+x.top,bottom:M.bottom-E.bottom+x.bottom,left:E.left-M.left+x.left,right:M.right-E.right+x.right},R=e.modifiersData.offset;if(h===C&&R){var S=R[i];Object.keys(H).forEach((function(e){var t=[P,L].indexOf(e)>=0?1:-1,n=[k,L].indexOf(e)>=0?"y":"x";H[e]+=S[n]*t}))}return H}function ce(e,t,n){return s(e,f(t,n))}var pe={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,i=void 0===o||o,a=n.altAxis,c=void 0!==a&&a,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,v=n.tetherOffset,g=void 0===v?0:v,y=fe(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),b=z(t.placement),w=X(t.placement),O=!w,j=Y(b),E="x"===j?"y":"x",D=t.modifiersData.popperOffsets,M=t.rects.reference,B=t.rects.popper,R="function"==typeof g?g(Object.assign({},t.rects,{placement:t.placement})):g,T="number"==typeof R?{mainAxis:R,altAxis:R}:Object.assign({mainAxis:0,altAxis:0},R),C=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,S={x:0,y:0};if(D){if(i){var V,q="y"===j?k:W,_="y"===j?L:P,N="y"===j?"height":"width",I=D[j],F=I+y[q],U=I-y[_],G=m?-B[N]/2:0,J=w===H?M[N]:B[N],K=w===H?-B[N]:-M[N],Q=t.elements.arrow,Z=m&&Q?x(Q):{width:0,height:0},$=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},ee=$[q],te=$[_],ne=ce(0,M[N],Z[N]),re=O?M[N]/2-G-ne-ee-T.mainAxis:J-ne-ee-T.mainAxis,oe=O?-M[N]/2+G+ne+te+T.mainAxis:K+ne+te+T.mainAxis,ie=t.elements.arrow&&A(t.elements.arrow),ae=ie?"y"===j?ie.clientTop||0:ie.clientLeft||0:0,se=null!=(V=null==C?void 0:C[j])?V:0,pe=I+oe-se,ue=ce(m?f(F,I+re-se-ae):F,I,m?s(U,pe):U);D[j]=ue,S[j]=ue-I}if(c){var le,de="x"===j?k:W,he="x"===j?L:P,me=D[E],ve="y"===E?"height":"width",ge=me+y[de],ye=me-y[he],be=-1!==[k,W].indexOf(b),xe=null!=(le=null==C?void 0:C[E])?le:0,we=be?ge:me-M[ve]-B[ve]-xe+T.altAxis,Oe=be?me+M[ve]+B[ve]-xe-T.altAxis:ye,je=m&&be?function(e,t,n){var r=ce(e,t,n);return r>n?n:r}(we,me,Oe):ce(m?we:ge,me,m?Oe:ye);D[E]=je,S[E]=je-me}t.modifiersData[r]=S}},requiresIfExists:["offset"]};var ue={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=z(n.placement),f=Y(s),c=[W,P].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return ae("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:se(e,B))}(o.padding,n),u=x(i),l="y"===f?k:W,d="y"===f?L:P,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],v=A(i),g=v?"y"===f?v.clientHeight||0:v.clientWidth||0:0,y=h/2-m/2,b=p[l],w=g-u[c]-p[d],O=g/2-u[c]/2+y,j=ce(b,O,w),E=f;n.modifiersData[r]=((t={})[E]=j,t.centerOffset=j-O,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&ne(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function le(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function de(e){return[k,P,L,W].some((function(t){return e[t]>=0}))}var he=F({defaultModifiers:[{name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,n=e.instance,o=e.options,i=o.scroll,a=void 0===i||i,s=o.resize,f=void 0===s||s,c=r(t.elements.popper),p=[].concat(t.scrollParents.reference,t.scrollParents.popper);return a&&p.forEach((function(e){e.addEventListener("scroll",n.update,U)})),f&&c.addEventListener("resize",n.update,U),function(){a&&p.forEach((function(e){e.removeEventListener("scroll",n.update,U)})),f&&c.removeEventListener("resize",n.update,U)}},data:{}},{name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state,n=e.name;t.modifiersData[n]=G({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}},{name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,n=e.options,r=n.gpuAcceleration,o=void 0===r||r,i=n.adaptive,a=void 0===i||i,s=n.roundOffsets,f=void 0===s||s,c={placement:z(t.placement),variation:X(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:o,isFixed:"fixed"===t.options.strategy};null!=t.modifiersData.popperOffsets&&(t.styles.popper=Object.assign({},t.styles.popper,K(Object.assign({},c,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:a,roundOffsets:f})))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign({},t.styles.arrow,K(Object.assign({},c,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:f})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})},data:{}},{name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var n=t.styles[e]||{},r=t.attributes[e]||{},o=t.elements[e];i(o)&&h(o)&&(Object.assign(o.style,n),Object.keys(r).forEach((function(e){var t=r[e];!1===t?o.removeAttribute(e):o.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach((function(e){var r=t.elements[e],o=t.attributes[e]||{},a=Object.keys(t.styles.hasOwnProperty(e)?t.styles[e]:n[e]).reduce((function(e,t){return e[t]="",e}),{});i(r)&&h(r)&&(Object.assign(r.style,a),Object.keys(o).forEach((function(e){r.removeAttribute(e)})))}))}},requires:["computeStyles"]},Q,{name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=z(v),y=f||(g===v||!h?[$(v)]:function(e){if(z(e)===M)return[];var t=$(e);return[te(e),t,te(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(z(n)===M?function(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?V:f,p=X(r),u=p?s?S:S.filter((function(e){return X(e)===p})):B,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=fe(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[z(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,j=!0,E=b[0],D=0;D=0,q=C?"width":"height",_=fe(t,{placement:A,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),N=C?T?P:W:T?L:k;x[q]>w[q]&&(N=$(N));var I=$(N),F=[];if(i&&F.push(_[R]<=0),s&&F.push(_[N]<=0,_[I]<=0),F.every((function(e){return e}))){E=A,j=!1;break}O.set(A,F)}if(j)for(var U=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return E=t,"break"},Y=h?3:1;Y>0;Y--){if("break"===U(Y))break}t.placement!==E&&(t.modifiersData[r]._skip=!0,t.placement=E,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}},pe,ue,{name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=fe(t,{elementContext:"reference"}),s=fe(t,{altBoundary:!0}),f=le(a,r),c=le(s,o,i),p=de(f),u=de(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}}]})}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/329.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[329],{329:function(e,t){!function(e){"use strict";const t="hy",n={today:"Այսօր",clear:"Ջնջել ընտրվածը",close:"Փակել",selectMonth:"Ընտրել ամիս",previousMonth:"Նախորդ ամիս",nextMonth:"Հաջորդ ամիս",selectYear:"Ընտրել տարի",previousYear:"Նախորդ տարի",nextYear:"Հաջորդ տարի",selectDecade:"Ընտրել տասնամյակ",previousDecade:"Նախորդ տասնամյակ",nextDecade:"Հաջորդ տասնամյակ",previousCentury:"Նախորդ դար",nextCentury:"Հաջորդ դար",pickHour:"Ընտրել ժամ",incrementHour:"Ավելացնել ժամ",decrementHour:"Նվազեցնել ժամ",pickMinute:"Ընտրել րոպե",incrementMinute:"Ավելացնել րոպե",decrementMinute:"Նվազեցնել րոպե",pickSecond:"Ընտրել երկրորդը",incrementSecond:"Ավելացնել վայրկյան",decrementSecond:"Նվազեցնել վայրկյան",toggleMeridiem:"Փոփոխել Ժամանակաշրջանը",selectTime:"Ընտրել Ժամ",selectDate:"Ընտրել ամսաթիվ",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"hy",startOfTheWeek:1,dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd.MM.yyyy",LL:"d MMMM yyyy թ.",LLL:"d MMMM yyyy թ., H:mm",LLLL:"dddd, d MMMM yyyy թ., H:mm"},ordinal:e=>e,format:"L LTS"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/358.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[358],{358:function(e,t){!function(e){"use strict";const t="ar-SA",n={today:"اليوم",clear:"مسح",close:"إغلاق",selectMonth:"اختر الشهر",previousMonth:"الشهر السابق",nextMonth:"الشهر التالي",selectYear:"اختر السنة",previousYear:"العام السابق",nextYear:"العام التالي",selectDecade:"اختر العقد",previousDecade:"العقد السابق",nextDecade:"العقد التالي",previousCentury:"القرن السابق",nextCentury:"القرن التالي",pickHour:"اختر الساعة",incrementHour:"أضف ساعة",decrementHour:"أنقص ساعة",pickMinute:"اختر الدقيقة",incrementMinute:"أضف دقيقة",decrementMinute:"أنقص دقيقة",pickSecond:"اختر الثانية",incrementSecond:"أضف ثانية",decrementSecond:"أنقص ثانية",toggleMeridiem:"تبديل الفترة",selectTime:"اخر الوقت",selectDate:"اختر التاريخ",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"ar-SA",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd/MM/yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd d MMMM yyyy HH:mm"},ordinal:e=>e,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/38.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[38],{38:function(e,n){!function(e){"use strict";const n="pl",i={today:"Dzisiaj",clear:"Wyczyść",close:"Zamknij",selectMonth:"Wybierz miesiąc",previousMonth:"Poprzedni miesiąc",nextMonth:"Następny miesiąc",selectYear:"Wybierz rok",previousYear:"Poprzedni rok",nextYear:"Następny rok",selectDecade:"Wybierz dekadę",previousDecade:"Poprzednia dekada",nextDecade:"Następna dekada",previousCentury:"Poprzednie stulecie",nextCentury:"Następne stulecie",pickHour:"Wybierz godzinę",incrementHour:"Kolejna godzina",decrementHour:"Poprzednia godzina",pickMinute:"Wybierz minutę",incrementMinute:"Kolejna minuta",decrementMinute:"Poprzednia minuta",pickSecond:"Wybierz sekundę",incrementSecond:"Kolejna sekunda",decrementSecond:"Poprzednia sekunda",toggleMeridiem:"Przełącz porę dnia",selectTime:"Ustaw godzinę",selectDate:"Ustaw datę",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"pl",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd.MM.yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd, d MMMM yyyy HH:mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=i,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/459.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[459],{459:function(e,t){!function(e){"use strict";const t="ru",n={today:"Перейти сегодня",clear:"Очистить выделение",close:"Закрыть сборщик",selectMonth:"Выбрать месяц",previousMonth:"Предыдущий месяц",nextMonth:"В следующем месяце",selectYear:"Выбрать год",previousYear:"Предыдущий год",nextYear:"В следующем году",selectDecade:"Выбрать десятилетие",previousDecade:"Предыдущее десятилетие",nextDecade:"Следующее десятилетие",previousCentury:"Предыдущий век",nextCentury:"Следующий век",pickHour:"Выберите час",incrementHour:"Время увеличения",decrementHour:"Уменьшить час",pickMinute:"Выбрать минуту",incrementMinute:"Минута приращения",decrementMinute:"Уменьшить минуту",pickSecond:"Выбрать второй",incrementSecond:"Увеличение секунды",decrementSecond:"Уменьшение секунды",toggleMeridiem:"Переключить период",selectTime:"Выбрать время",selectDate:"Выбрать дату",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"ru",startOfTheWeek:1,dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd.MM.yyyy",LL:"d MMMM yyyy г.",LLL:"d MMMM yyyy г., H:mm",LLLL:"dddd, d MMMM yyyy г., H:mm"},ordinal:e=>e,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/466.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[466],{100:function(e,t,s){var a={"./ar":[166,785],"./ar-SA":[358,358],"./ar-SA.js":[358,358],"./ar.js":[166,785],"./ca":[854,854],"./ca.js":[854,854],"./cs":[876,876],"./cs.js":[876,876],"./de":[929,929],"./de.js":[929,929],"./es":[14,14],"./es.js":[14,14],"./fi":[987,987],"./fi.js":[987,987],"./fr":[102,102],"./fr.js":[102,102],"./hy":[329,329],"./hy.js":[329,329],"./it":[51,51],"./it.js":[51,51],"./nl":[0,0],"./nl.js":[0,0],"./pl":[38,38],"./pl.js":[38,38],"./ro":[77,77],"./ro.js":[77,77],"./ru":[459,459],"./ru.js":[459,459],"./sl":[350,969],"./sl.js":[350,969],"./tr":[964,964],"./tr.js":[964,964]};function n(e){if(!s.o(a,e))return Promise.resolve().then((function(){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}));var t=a[e],n=t[0];return s.e(t[1]).then((function(){return s.t(n,23)}))}n.keys=function(){return Object.keys(a)},n.id=100,e.exports=n},466:function(e,t,s){"use strict";s.r(t);var a=s(167),n=s(969),i=s(255);t.default=class extends a.Controller{static outlets=["datepicker"];static values={options:Object};#e=["ar","ar-SA","de","es","fi","fr","it","nl","pl","ro","ru","sl","tr"];datePicker=null;connect(){const e=this.processOptions(),t=this.processLocale(e);this.dispatchEvent("pre-connect",{options:e,locale:t}),this.datePicker=new n.U_(this.element,e),null!==t&&s(100)(`./${t}`).then((t=>{(0,n.Xq)(t),this.datePicker.locale(t.name),this.datePicker.updateOptions(e),this.dispatchEvent("post-connect-changed-locale")})),this.dispatchEvent("connect",{datePicker:this.datePicker})}datepickerOutletConnected(e,t){this.element.addEventListener(n.g$.events.change,(t=>{e.datePicker.updateOptions({restrictions:{minDate:t.detail.date}})})),t.addEventListener(n.g$.events.change,(e=>{this.datePicker.updateOptions({restrictions:{maxDate:e.detail.date}})}))}processOptions(){const e=this.optionsValue,{restrictions:t={},display:s={}}=e;return e?.defaultDate&&(e.defaultDate=new Date(e.defaultDate)),e?.viewDate&&(e.viewDate=new Date(e.viewDate)),t?.minDate&&(t.minDate=new Date(t.minDate)),t?.maxDate&&(t.maxDate=new Date(t.maxDate)),t?.disabledDates&&(t.disabledDates=t.disabledDates.map((e=>new Date(e)))),t?.enabledDates&&(t.enabledDates=t.enabledDates.map((e=>new Date(e)))),s?.icons||(s.icons=i.faFiveIcons),e}processLocale(e){const{localization:{locale:t}={}}=e;if(!t)return null;if(this.#e.includes(t))return t;if(!t.includes("-"))return null;const s=t.split("-")[0];return this.#e.includes(s)?s:null}dispatchEvent(e,t){this.dispatch(e,{detail:t,prefix:"datepicker"})}}}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/51.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[51],{51:function(e,n){!function(e){"use strict";const n="it",o={today:"Oggi",clear:"Cancella selezione",close:"Chiudi",selectMonth:"Seleziona mese",previousMonth:"Mese precedente",nextMonth:"Mese successivo",selectYear:"Seleziona anno",previousYear:"Anno precedente",nextYear:"Anno successivo",selectDecade:"Seleziona decennio",previousDecade:"Decennio precedente",nextDecade:"Decennio successivo",previousCentury:"Secolo precedente",nextCentury:"Secolo successivo",pickHour:"Seleziona l'ora",incrementHour:"Incrementa l'ora",decrementHour:"Decrementa l'ora",pickMinute:"Seleziona i minuti",incrementMinute:"Incrementa i minuti",decrementMinute:"Decrementa i minuti",pickSecond:"Seleziona i secondi",incrementSecond:"Incrementa i secondi",decrementSecond:"Decrementa i secondi",toggleMeridiem:"Scambia AM-PM",selectTime:"Seleziona l'ora",selectDate:"Seleziona una data",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"it",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd/MM/yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd d MMMM yyyy HH:mm"},ordinal:e=>`${e}º`,format:"L LT"};e.localization=o,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/77.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[77],{77:function(e,t){!function(e){"use strict";const t="ro",n={today:"Mergi la ziua de astăzi",clear:"Șterge selecția",close:"Închide calendarul",selectMonth:"Selectează luna",previousMonth:"Luna precedentă",nextMonth:"Luna următoare",selectYear:"Selectează anul",previousYear:"Anul precedent",nextYear:"Anul următor",selectDecade:"Selectează deceniul",previousDecade:"Deceniul precedent",nextDecade:"Deceniul următor",previousCentury:"Secolul precedent",nextCentury:"Secolul următor",pickHour:"Alege ora",incrementHour:"Incrementează ora",decrementHour:"Decrementează ora",pickMinute:"Alege minutul",incrementMinute:"Incrementează minutul",decrementMinute:"Decrementează minutul",pickSecond:"Alege secunda",incrementSecond:"Incrementează secunda",decrementSecond:"Decrementează secunda",toggleMeridiem:"Comută modul AM/PM",selectTime:"Selectează ora",selectDate:"Selectează data",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"ro",startOfTheWeek:1,dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd.MM.yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy H:mm",LLLL:"dddd, d MMMM yyyy H:mm"},ordinal:e=>e,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/785.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[785],{166:function(e,t){!function(e){"use strict";const t="ar",n={today:"اليوم",clear:"مسح",close:"إغلاق",selectMonth:"اختر الشهر",previousMonth:"الشهر السابق",nextMonth:"الشهر التالي",selectYear:"اختر السنة",previousYear:"العام السابق",nextYear:"العام التالي",selectDecade:"اختر العقد",previousDecade:"العقد السابق",nextDecade:"العقد التالي",previousCentury:"القرن السابق",nextCentury:"القرن التالي",pickHour:"اختر الساعة",incrementHour:"أضف ساعة",decrementHour:"أنقص ساعة",pickMinute:"اختر الدقيقة",incrementMinute:"أضف دقيقة",decrementMinute:"أنقص دقيقة",pickSecond:"اختر الثانية",incrementSecond:"أضف ثانية",decrementSecond:"أنقص ثانية",toggleMeridiem:"تبديل الفترة",selectTime:"اخر الوقت",selectDate:"اختر التاريخ",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"ar",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"d/M/yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd d MMMM yyyy HH:mm"},ordinal:e=>e,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/854.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[854],{854:function(e,r){!function(e){"use strict";const r="ca",n={today:"Avui",clear:"Esborrar selecció",close:"Tancar selector",selectMonth:"Seleccionar mes",previousMonth:"Mes anterior",nextMonth:"Pròxim mes",selectYear:"Seleccionar any",previousYear:"Any anterior",nextYear:"Pròxim any",selectDecade:"Seleccionar dècada",previousDecade:"Dècada anterior",nextDecade:"Pròxima dècada",previousCentury:"Segle anterior",nextCentury:"Pròxim segle",pickHour:"Escollir hora",incrementHour:"Incrementar hora",decrementHour:"Decrementar hora",pickMinute:"Escollir minut",incrementMinute:"Incrementar minut",decrementMinute:"Decrementar minut",pickSecond:"Escollir segon",incrementSecond:"Incrementar segon",decrementSecond:"Decrementar segon",toggleMeridiem:"Canviar AM/PM",selectTime:"Seleccionar temps",selectDate:"Seleccionar data",dayViewHeaderFormat:{month:"long",year:"2-digit"},startOfTheWeek:1,locale:"ca",dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd/MM/yyyy",LL:"d [de] MMMM [de] yyyy",LLL:"d [de] MMMM [de] yyyy H:mm",LLLL:"dddd, d [de] MMMM [de] yyyy H:mm"},ordinal:e=>`${e}º`,format:"L LT"};e.localization=n,e.name=r,Object.defineProperty(e,"__esModule",{value:!0})}(r)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/876.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[876],{876:function(e,t){!function(e){"use strict";const t="cs",n={today:"Dnes",clear:"Vymazat výběr",close:"Zavřít výběrové okno",selectMonth:"Vybrat měsíc",previousMonth:"Předchozí měsíc",nextMonth:"Následující měsíc",selectYear:"Vybrat rok",previousYear:"Předchozí rok",nextYear:"Následující rok",selectDecade:"Vybrat desetiletí",previousDecade:"Předchozí desetiletí",nextDecade:"Následující desetiletí",previousCentury:"Předchozí století",nextCentury:"Následující století",pickHour:"Vybrat hodinu",incrementHour:"Zvýšit hodinu",decrementHour:"Snížit hodinu",pickMinute:"Vybrat minutu",incrementMinute:"Zvýšit minutu",decrementMinute:"Snížit minutu",pickSecond:"Vybrat sekundu",incrementSecond:"Zvýšit sekundu",decrementSecond:"Snížit sekundu",toggleMeridiem:"Přepnout ráno / odpoledne",selectTime:"Vybrat čas",selectDate:"Vybrat datum",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"de",startOfTheWeek:1,dateFormats:{LTS:"HH:mm:ss",LT:"HH:mm",L:"dd.MM.yyyy",LL:"d. MMMM yyyy",LLL:"d. MMMM yyyy HH:mm",LLLL:"dddd, d. MMMM yyyy HH:mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/929.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[929],{929:function(e,n){!function(e){"use strict";const n="de",t={today:"Heute",clear:"Auswahl löschen",close:"Auswahlbox schließen",selectMonth:"Monat wählen",previousMonth:"Letzter Monat",nextMonth:"Nächster Monat",selectYear:"Jahr wählen",previousYear:"Letztes Jahr",nextYear:"Nächstes Jahr",selectDecade:"Jahrzehnt wählen",previousDecade:"Letztes Jahrzehnt",nextDecade:"Nächstes Jahrzehnt",previousCentury:"Letztes Jahrhundert",nextCentury:"Nächstes Jahrhundert",pickHour:"Stunde wählen",incrementHour:"Stunde erhöhen",decrementHour:"Stunde verringern",pickMinute:"Minute wählen",incrementMinute:"Minute erhöhen",decrementMinute:"Minute verringern",pickSecond:"Sekunde wählen",incrementSecond:"Sekunde erhöhen",decrementSecond:"Sekunde verringern",toggleMeridiem:"Tageszeit umschalten",selectTime:"Zeit wählen",selectDate:"Datum wählen",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"de",startOfTheWeek:1,dateFormats:{LTS:"HH:mm:ss",LT:"HH:mm",L:"dd.MM.yyyy",LL:"d. MMMM yyyy",LLL:"d. MMMM yyyy HH:mm",LLLL:"dddd, d. MMMM yyyy HH:mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=t,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/964.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[964],{964:function(e,n){!function(e){"use strict";const n="tr",i={today:"Bugün",clear:"Temizle",close:"Kapat",selectMonth:"Ay seçin",previousMonth:"Önceki Ay",nextMonth:"Sonraki Ay",selectYear:"Yıl seçin",previousYear:"Önceki yıl",nextYear:"Sonraki yıl",selectDecade:"On yıl seçin",previousDecade:"Önceki on yıl",nextDecade:"Sonraki on yıl",previousCentury:"Önceki yüzyıl",nextCentury:"Sonraki yüzyıl",pickHour:"Saat seçin",incrementHour:"Saati ilerlet",decrementHour:"Saati gerilet",pickMinute:"Dakika seçin",incrementMinute:"Dakikayı ilerlet",decrementMinute:"Dakikayı gerilet",pickSecond:"Saniye seç",incrementSecond:"Saniyeyi ilerlet",decrementSecond:"Saniyeyi gerilet",toggleMeridiem:"Meridemi Değiştir AM-PM",selectTime:"Saat seçin",selectDate:"Tarih seçin",dayViewHeaderFormat:{month:"long",year:"numeric"},locale:"tr",startOfTheWeek:1,dateFormats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"dd.MM.yyyy",LL:"d MMMM yyyy",LLL:"d MMMM yyyy HH:mm",LLLL:"dddd, d MMMM yyyy HH:mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=i,e.name=n,Object.defineProperty(e,"__esModule",{value:!0})}(n)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/969.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[969],{350:function(e,t){!function(e){"use strict";const t="sl",n={today:"Danes",clear:"Počisti",close:"Zapri",selectMonth:"Izberite mesec",previousMonth:"Prejšnji mesec",nextMonth:"Naslednji mesec",selectYear:"Izberite leto",previousYear:"Prejšnje Leto",nextYear:"Naslednje leto",selectDecade:"Izberite desetletje",previousDecade:"Prejšnje desetletje",nextDecade:"Naslednje desetletje",previousCentury:"Prejšnje stoletje",nextCentury:"Naslednje stoletje",pickHour:"Izberite uro",incrementHour:"Povečaj ure",decrementHour:"Zmanjšaj uro",pickMinute:"Izberite minuto",incrementMinute:"Povečaj minuto",decrementMinute:"Zmanjšaj minuto",pickSecond:"Izberite drugo",incrementSecond:"Povečaj sekundo",decrementSecond:"Zmanjšaj sekundo",toggleMeridiem:"Preklop dopoldne/popoldne",selectTime:"Izberite čas",selectDate:"Izberite Datum",dayViewHeaderFormat:{month:"long",year:"numeric"},locale:"sl",startOfTheWeek:1,dateFormats:{LT:"H:mm",LTS:"H:mm:ss",L:"dd.MM.yyyy",LL:"d. MMMM yyyy",LLL:"d. MMMM yyyy H:mm",LLLL:"dddd, d. MMMM yyyy H:mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/987.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunksonata_project_form_extensions=self.webpackChunksonata_project_form_extensions||[]).push([[987],{987:function(e,t){!function(e){"use strict";const t="fi",n={today:"Tänään",clear:"Tyhjennä",close:"Sulje",selectMonth:"Valitse kuukausi",previousMonth:"Edellinen kuukausi",nextMonth:"Seuraava kuukausi",selectYear:"Valitse vuosi",previousYear:"Edellinen vuosi",nextYear:"Seuraava vuosi",selectDecade:"Valitse vuosikymmen",previousDecade:"Edellinen vuosikymmen",nextDecade:"Seuraava vuosikymmen",previousCentury:"Edellinen vuosisata",nextCentury:"Seuraava vuosisata",pickHour:"Valitse tunnit",incrementHour:"Vähennä tunteja",decrementHour:"Lisää tunteja",pickMinute:"Valitse minuutit",incrementMinute:"Vähennä minuutteja",decrementMinute:"Lisää minuutteja",pickSecond:"Valitse sekuntit",incrementSecond:"Vähennä sekunteja",decrementSecond:"Lisää sekunteja",toggleMeridiem:"Vaihda kellonaikaa",selectTime:"Valitse aika",selectDate:"Valise päivä",dayViewHeaderFormat:{month:"long",year:"2-digit"},locale:"fi",startOfTheWeek:1,dateFormats:{LT:"HH.mm",LTS:"HH.mm.ss",L:"dd.MM.yyyy",LL:"d. MMMM[ta] yyyy",LLL:"d. MMMM[ta] yyyy, [klo] HH.mm",LLLL:"dddd, d. MMMM[ta] yyyy, [klo] HH.mm"},ordinal:e=>`${e}.`,format:"L LT"};e.localization=n,e.name=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)}}]); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/app.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var t,e,n,r,o={167:function(t){t.exports=stimulus}},i={};function u(t){var e=i[t];if(void 0!==e)return e.exports;var n=i[t]={exports:{}};return o[t].call(n.exports,n,n.exports,u),n.exports}u.m=o,u.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return u.d(e,{a:e}),e},e=Object.getPrototypeOf?function(t){return Object.getPrototypeOf(t)}:function(t){return t.__proto__},u.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var o=Object.create(null);u.r(o);var i={};t=t||[null,e({}),e([]),e(e)];for(var a=2&r&&n;"object"==typeof a&&!~t.indexOf(a);a=e(a))Object.getOwnPropertyNames(a).forEach((function(t){i[t]=function(){return n[t]}}));return i.default=function(){return n},u.d(o,i),o},u.d=function(t,e){for(var n in e)u.o(e,n)&&!u.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},u.f={},u.e=function(t){return Promise.all(Object.keys(u.f).reduce((function(e,n){return u.f[n](t,e),e}),[]))},u.u=function(t){return t+".js"},u.miniCssF=function(t){},u.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),u.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n={},r="sonata-project-form-extensions:",u.l=function(t,e,o,i){if(n[t])n[t].push(e);else{var a,c;if(void 0!==o)for(var f=document.getElementsByTagName("script"),s=0;st.identifier===this.identifier&&t.__stimulusLazyController))||Promise.all([u.e(639),u.e(466)]).then(u.bind(u,466)).then((t=>{this.application.register(this.identifier,t.default)}))}},{sonataApplication:f}=u.g;f.register("datepicker",c)}(); -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/entrypoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "entrypoints": { 3 | "app": { 4 | "css": [ 5 | "/bundles/sonataform/app.css" 6 | ], 7 | "js": [ 8 | "/bundles/sonataform/app.js" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundles/sonataform/app.css": "/bundles/sonataform/app.css", 3 | "bundles/sonataform/app.js": "/bundles/sonataform/app.js", 4 | "bundles/sonataform/466.js": "/bundles/sonataform/466.js", 5 | "bundles/sonataform/200.js": "/bundles/sonataform/200.js", 6 | "bundles/sonataform/785.js": "/bundles/sonataform/785.js", 7 | "bundles/sonataform/358.js": "/bundles/sonataform/358.js", 8 | "bundles/sonataform/854.js": "/bundles/sonataform/854.js", 9 | "bundles/sonataform/876.js": "/bundles/sonataform/876.js", 10 | "bundles/sonataform/929.js": "/bundles/sonataform/929.js", 11 | "bundles/sonataform/14.js": "/bundles/sonataform/14.js", 12 | "bundles/sonataform/987.js": "/bundles/sonataform/987.js", 13 | "bundles/sonataform/102.js": "/bundles/sonataform/102.js", 14 | "bundles/sonataform/329.js": "/bundles/sonataform/329.js", 15 | "bundles/sonataform/51.js": "/bundles/sonataform/51.js", 16 | "bundles/sonataform/0.js": "/bundles/sonataform/0.js", 17 | "bundles/sonataform/38.js": "/bundles/sonataform/38.js", 18 | "bundles/sonataform/77.js": "/bundles/sonataform/77.js", 19 | "bundles/sonataform/459.js": "/bundles/sonataform/459.js", 20 | "bundles/sonataform/969.js": "/bundles/sonataform/969.js", 21 | "bundles/sonataform/964.js": "/bundles/sonataform/964.js", 22 | "bundles/sonataform/639.js": "/bundles/sonataform/639.js" 23 | } -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.ar.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | إضافة جديدة 8 | 9 | 10 | label_type_yes 11 | نعم 12 | 13 | 14 | label_type_no 15 | لا يحتوي 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.bg.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Добавяне 8 | 9 | 10 | label_type_yes 11 | да 12 | 13 | 14 | label_type_no 15 | не 16 | 17 | 18 | date_range_start 19 | От 20 | 21 | 22 | date_range_end 23 | до 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.ca.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Afegeix 8 | 9 | 10 | label_type_yes 11 | 12 | 13 | 14 | label_type_no 15 | no 16 | 17 | 18 | date_range_start 19 | Data inicial 20 | 21 | 22 | date_range_end 23 | Data final 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.cs.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Přidat nový 8 | 9 | 10 | label_type_yes 11 | ano 12 | 13 | 14 | label_type_no 15 | ne 16 | 17 | 18 | date_range_start 19 | Od 20 | 21 | 22 | date_range_end 23 | Do 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.de.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Neu 8 | 9 | 10 | label_type_yes 11 | ja 12 | 13 | 14 | label_type_no 15 | nein 16 | 17 | 18 | date_range_start 19 | Von 20 | 21 | 22 | date_range_end 23 | Bis 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.en.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Add new 8 | 9 | 10 | label_type_yes 11 | yes 12 | 13 | 14 | label_type_no 15 | no 16 | 17 | 18 | date_range_start 19 | From 20 | 21 | 22 | date_range_end 23 | To 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.es.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Agregar nuevo 8 | 9 | 10 | label_type_yes 11 | 12 | 13 | 14 | label_type_no 15 | no 16 | 17 | 18 | date_range_start 19 | Fecha inicial 20 | 21 | 22 | date_range_end 23 | Fecha final 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.eu.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Berria gehitu 8 | 9 | 10 | label_type_yes 11 | Bai 12 | 13 | 14 | label_type_no 15 | Ez 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.fa.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | اضافه کردن جدید 8 | 9 | 10 | label_type_yes 11 | بله 12 | 13 | 14 | label_type_no 15 | خیر 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.fi.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Lisää uusi 8 | 9 | 10 | label_type_yes 11 | kyllä 12 | 13 | 14 | label_type_no 15 | ei 16 | 17 | 18 | date_range_start 19 | Alkaen 20 | 21 | 22 | date_range_end 23 | Saakka 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.fr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Ajouter 8 | 9 | 10 | label_type_yes 11 | oui 12 | 13 | 14 | label_type_no 15 | non 16 | 17 | 18 | date_range_start 19 | Du 20 | 21 | 22 | date_range_end 23 | Au 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.hr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Novi unos 8 | 9 | 10 | label_type_yes 11 | da 12 | 13 | 14 | label_type_no 15 | ne 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.hu.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Új hozzáadása 8 | 9 | 10 | label_type_yes 11 | igen 12 | 13 | 14 | label_type_no 15 | nem 16 | 17 | 18 | date_range_start 19 | Intervallum kezdete 20 | 21 | 22 | date_range_end 23 | Intervallum vége 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.it.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Aggiungi nuovo 8 | 9 | 10 | label_type_yes 11 | 12 | 13 | 14 | label_type_no 15 | no 16 | 17 | 18 | date_range_start 19 | da 20 | 21 | 22 | date_range_end 23 | a 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.ja.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | 追加 8 | 9 | 10 | label_type_yes 11 | はい 12 | 13 | 14 | label_type_no 15 | いいえ 16 | 17 | 18 | date_range_start 19 | From 20 | 21 | 22 | date_range_end 23 | To 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.lb.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Nei 8 | 9 | 10 | label_type_yes 11 | jo 12 | 13 | 14 | label_type_no 15 | nee 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.lt.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Sukurti naują 8 | 9 | 10 | label_type_yes 11 | taip 12 | 13 | 14 | label_type_no 15 | ne 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.nl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Nieuwe toevoegen 8 | 9 | 10 | label_type_yes 11 | ja 12 | 13 | 14 | label_type_no 15 | nee 16 | 17 | 18 | date_range_start 19 | vanaf datum 20 | 21 | 22 | date_range_end 23 | tot datum 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.pl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Dodaj 8 | 9 | 10 | label_type_yes 11 | tak 12 | 13 | 14 | label_type_no 15 | nie 16 | 17 | 18 | date_range_start 19 | od 20 | 21 | 22 | date_range_end 23 | do 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.pt.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Novo 8 | 9 | 10 | label_type_yes 11 | sim 12 | 13 | 14 | label_type_no 15 | não 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.pt_BR.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Adicionar novo 8 | 9 | 10 | label_type_yes 11 | sim 12 | 13 | 14 | label_type_no 15 | não 16 | 17 | 18 | date_range_start 19 | De 20 | 21 | 22 | date_range_end 23 | Até 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.ro.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Adăugați 8 | 9 | 10 | label_type_yes 11 | da 12 | 13 | 14 | label_type_no 15 | nu 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.ru.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Добавить новый 8 | 9 | 10 | label_type_yes 11 | да 12 | 13 | 14 | label_type_no 15 | нет 16 | 17 | 18 | sonata_form_template_box_file_found_in 19 | Этот файл можно найти в 20 | 21 | 22 | date_range_start 23 | С 24 | 25 | 26 | date_range_end 27 | По 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.sk.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Pridať nový 8 | 9 | 10 | label_type_yes 11 | áno 12 | 13 | 14 | label_type_no 15 | nie 16 | 17 | 18 | date_range_start 19 | Od 20 | 21 | 22 | date_range_end 23 | Do 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.sl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Dodaj 8 | 9 | 10 | label_type_yes 11 | da 12 | 13 | 14 | label_type_no 15 | ne 16 | 17 | 18 | date_range_start 19 | Od 20 | 21 | 22 | date_range_end 23 | Do 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.uk.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | Додати новий 8 | 9 | 10 | label_type_yes 11 | так 12 | 13 | 14 | label_type_no 15 | немає 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/translations/SonataFormBundle.zh_CN.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | link_add 7 | 添加 8 | 9 | 10 | label_type_yes 11 | 12 | 13 | 14 | label_type_no 15 | 16 | 17 | 18 | date_range_start 19 | date_range_start 20 | 21 | 22 | date_range_end 23 | date_range_end 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/Resources/views/Form/datepicker.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | 3 | This file is part of the Sonata package. 4 | 5 | (c) Thomas Rabaix 6 | 7 | For the full copyright and license information, please view the LICENSE 8 | file that was distributed with this source code. 9 | 10 | #} 11 | 12 | {% block sonata_type_datetime_picker_widget_html %} 13 |
22 | {% set attr = attr|merge({ 23 | 'data-td-target': '#' ~ id ~ '_controller', 24 | }) %} 25 | 26 | {{ block('datetime_widget') }} 27 | 28 | {% if datepicker_use_button %} 29 | 34 | 35 | 36 | {% endif %} 37 |
38 | {% endblock sonata_type_datetime_picker_widget_html %} 39 | 40 | {% block sonata_type_datetime_picker_widget %} 41 | {% if wrap_fields_with_addons %} 42 |
43 | {{ block('sonata_type_datetime_picker_widget_html') }} 44 |
45 | {% else %} 46 | {{ block('sonata_type_datetime_picker_widget_html') }} 47 | {% endif %} 48 | {% endblock sonata_type_datetime_picker_widget %} 49 | -------------------------------------------------------------------------------- /src/Bridge/Symfony/SonataFormBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Bridge\Symfony; 15 | 16 | use Sonata\Form\Bridge\Symfony\DependencyInjection\SonataFormExtension; 17 | use Symfony\Component\HttpKernel\Bundle\Bundle; 18 | 19 | final class SonataFormBundle extends Bundle 20 | { 21 | protected function getContainerExtensionClass(): string 22 | { 23 | return SonataFormExtension::class; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/DataTransformer/BooleanTypeToBooleanTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\DataTransformer; 15 | 16 | use Sonata\Form\Type\BooleanType; 17 | use Symfony\Component\Form\DataTransformerInterface; 18 | 19 | /** 20 | * @phpstan-implements DataTransformerInterface 21 | */ 22 | final class BooleanTypeToBooleanTransformer implements DataTransformerInterface 23 | { 24 | /** 25 | * @phpstan-param mixed $value 26 | */ 27 | public function transform(mixed $value): ?int 28 | { 29 | if (true === $value || BooleanType::TYPE_YES === (int) $value) { 30 | return BooleanType::TYPE_YES; 31 | } 32 | if (false === $value || BooleanType::TYPE_NO === (int) $value) { 33 | return BooleanType::TYPE_NO; 34 | } 35 | 36 | return null; 37 | } 38 | 39 | /** 40 | * @phpstan-param mixed $value 41 | */ 42 | public function reverseTransform(mixed $value): ?bool 43 | { 44 | if (BooleanType::TYPE_YES === $value) { 45 | return true; 46 | } 47 | if (BooleanType::TYPE_NO === $value) { 48 | return false; 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Date/JavaScriptFormatConverter.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Date; 15 | 16 | /** 17 | * Handles JavaScript <-> PHP date format conversion. 18 | * 19 | * @author Hugo Briand 20 | * @author Andrej Hudec 21 | */ 22 | final class JavaScriptFormatConverter 23 | { 24 | /** 25 | * This defines the mapping between PHP ICU date format (key) and JavaScript date format (value) 26 | * For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax 27 | * For JavaScript formats see https://github.com/Eonasdan/tempus-dominus/blob/master/src/js/datetime.ts#L922-L947. 28 | */ 29 | private const FORMAT_CONVERT_RULES = [ 30 | 'yyyy' => 'yyyy', 'yy' => 'yy', 'y' => 'yyyy', 31 | 'EEEE' => 'dddd', 'EE' => 'ddd', 'E' => 'ddd', 32 | 'a' => 'T', 33 | ]; 34 | 35 | /** 36 | * Returns associated JavaScript format. 37 | * 38 | * @param string $format PHP Date format 39 | * 40 | * @return string JavaScript date format 41 | */ 42 | public function convert(string $format): string 43 | { 44 | $size = \strlen($format); 45 | 46 | $output = ''; 47 | // process the format string letter by letter 48 | for ($i = 0; $i < $size; ++$i) { 49 | // if finds a ' 50 | if ("'" === $format[$i]) { 51 | // if the next character are T' forming 'T', send a T to the 52 | // output 53 | if ('T' === $format[$i + 1] && '\'' === $format[$i + 2]) { 54 | $output .= 'T'; 55 | $i += 2; 56 | } else { 57 | // if it's no a 'T' then send whatever is inside the '' to 58 | // the output, but send it inside [] (useful for cases like 59 | // the brazilian translation that uses a 'de' in the date) 60 | $output .= '['; 61 | $temp = current(explode("'", substr($format, $i + 1))); 62 | $output .= $temp; 63 | $output .= ']'; 64 | $i += \strlen($temp) + 1; 65 | } 66 | } else { 67 | // if no ' is found, then search all the rules, see if any of 68 | // them matchs 69 | $foundOne = false; 70 | foreach (self::FORMAT_CONVERT_RULES as $key => $value) { 71 | if (substr($format, $i, \strlen($key)) === $key) { 72 | $output .= $value; 73 | $foundOne = true; 74 | $i += \strlen($key) - 1; 75 | 76 | break; 77 | } 78 | } 79 | // if no rule is matched, then just add the character to the 80 | // output 81 | if (!$foundOne) { 82 | $output .= $format[$i]; 83 | } 84 | } 85 | } 86 | 87 | return $output; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/EventListener/FixCheckboxDataListener.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\EventListener; 15 | 16 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 17 | use Symfony\Component\Form\Extension\Core\DataTransformer\BooleanToStringTransformer; 18 | use Symfony\Component\Form\FormEvent; 19 | use Symfony\Component\Form\FormEvents; 20 | 21 | /** 22 | * Using BooleanToStringTransform in a checkbox form type 23 | * will set false value to '0' instead of null which will end up 24 | * returning true value when the form is bind. 25 | * 26 | * @author Sylvain Rascar 27 | */ 28 | final class FixCheckboxDataListener implements EventSubscriberInterface 29 | { 30 | public static function getSubscribedEvents(): array 31 | { 32 | return [FormEvents::PRE_SUBMIT => 'preSubmit']; 33 | } 34 | 35 | public function preSubmit(FormEvent $event): void 36 | { 37 | $data = $event->getData(); 38 | $transformers = $event->getForm()->getConfig()->getViewTransformers(); 39 | 40 | if (1 === \count($transformers) && $transformers[0] instanceof BooleanToStringTransformer && '0' === $data) { 41 | $event->setData(null); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EventListener/ResizeFormListener.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\EventListener; 15 | 16 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 17 | use Symfony\Component\Form\Exception\UnexpectedTypeException; 18 | use Symfony\Component\Form\FormEvent; 19 | use Symfony\Component\Form\FormEvents; 20 | 21 | /** 22 | * Resize a collection form element based on the data sent from the client. 23 | * 24 | * @author Bernhard Schussek 25 | */ 26 | final class ResizeFormListener implements EventSubscriberInterface 27 | { 28 | /** 29 | * @param array $typeOptions 30 | */ 31 | public function __construct( 32 | private string $type, 33 | private array $typeOptions = [], 34 | private bool $resizeOnSubmit = false, 35 | private ?\Closure $preSubmitDataCallback = null, 36 | ) { 37 | } 38 | 39 | public static function getSubscribedEvents(): array 40 | { 41 | return [ 42 | FormEvents::PRE_SET_DATA => 'preSetData', 43 | FormEvents::PRE_SUBMIT => 'preSubmit', 44 | FormEvents::SUBMIT => 'onSubmit', 45 | ]; 46 | } 47 | 48 | /** 49 | * @throws UnexpectedTypeException 50 | */ 51 | public function preSetData(FormEvent $event): void 52 | { 53 | $form = $event->getForm(); 54 | $data = $event->getData(); 55 | 56 | if (null === $data) { 57 | $data = []; 58 | } 59 | 60 | if (!\is_array($data) && !$data instanceof \Traversable) { 61 | throw new UnexpectedTypeException($data, 'array or \Traversable'); 62 | } 63 | 64 | // First remove all rows except for the prototype row 65 | // Type cast to string, because Symfony form can return integer keys 66 | foreach ($form as $name => $child) { 67 | // @phpstan-ignore-next-line -- Remove this and the casting when dropping support of < Symfony 6.2 68 | $form->remove((string) $name); 69 | } 70 | 71 | // Then add all rows again in the correct order 72 | foreach ($data as $name => $value) { 73 | $options = array_merge($this->typeOptions, [ 74 | 'property_path' => '['.$name.']', 75 | 'data' => $value, 76 | ]); 77 | 78 | $name = \is_int($name) ? (string) $name : $name; 79 | 80 | $form->add($name, $this->type, $options); 81 | } 82 | } 83 | 84 | /** 85 | * @psalm-suppress PossibleRawObjectIteration -- https://github.com/vimeo/psalm/issues/9489 86 | * 87 | * @throws UnexpectedTypeException 88 | */ 89 | public function preSubmit(FormEvent $event): void 90 | { 91 | if (!$this->resizeOnSubmit) { 92 | return; 93 | } 94 | 95 | $form = $event->getForm(); 96 | $data = $event->getData(); 97 | 98 | if (null === $data || '' === $data) { 99 | $data = []; 100 | } 101 | 102 | if ( 103 | !\is_array($data) 104 | && (!$data instanceof \Traversable || !$data instanceof \ArrayAccess) 105 | ) { 106 | throw new UnexpectedTypeException($data, 'array or \Traversable&\ArrayAccess'); 107 | } 108 | 109 | // Remove all empty rows except for the prototype row 110 | // Type cast to string, because Symfony form can return integer keys 111 | foreach ($form as $name => $child) { 112 | // @phpstan-ignore-next-line -- Remove this and the casting when dropping support of < Symfony 6.2 113 | $form->remove((string) $name); 114 | } 115 | 116 | // Add all additional rows 117 | foreach ($data as $name => $value) { 118 | // remove selected elements before adding them again 119 | if (isset($value['_delete'])) { 120 | unset($data[$name]); 121 | 122 | continue; 123 | } 124 | 125 | // Type cast to string, because Symfony form can returns integer keys 126 | if (!$form->has((string) $name)) { 127 | $buildOptions = [ 128 | 'property_path' => '['.$name.']', 129 | ]; 130 | 131 | if (null !== $this->preSubmitDataCallback) { 132 | $buildOptions['data'] = \call_user_func($this->preSubmitDataCallback, $value); 133 | } 134 | 135 | $options = array_merge($this->typeOptions, $buildOptions); 136 | 137 | $name = \is_int($name) ? (string) $name : $name; 138 | 139 | $form->add($name, $this->type, $options); 140 | } 141 | } 142 | 143 | $event->setData($data); 144 | } 145 | 146 | /** 147 | * @throws UnexpectedTypeException 148 | */ 149 | public function onSubmit(FormEvent $event): void 150 | { 151 | if (!$this->resizeOnSubmit) { 152 | return; 153 | } 154 | 155 | $form = $event->getForm(); 156 | $data = $event->getData(); 157 | 158 | if (null === $data) { 159 | $data = []; 160 | } 161 | 162 | if ( 163 | !\is_array($data) 164 | && (!$data instanceof \Traversable || !$data instanceof \ArrayAccess) 165 | ) { 166 | throw new UnexpectedTypeException($data, 'array or \Traversable&\ArrayAccess'); 167 | } 168 | 169 | /** 170 | * @psalm-suppress PossibleRawObjectIteration 171 | * 172 | * @see https://github.com/vimeo/psalm/issues/7928 173 | */ 174 | foreach ($data as $name => $child) { 175 | // Type cast to string, because Symfony form can returns integer keys 176 | if (!$form->has((string) $name)) { 177 | unset($data[$name]); 178 | } 179 | } 180 | 181 | $event->setData($data); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Fixtures/StubTranslator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Fixtures; 15 | 16 | use Symfony\Contracts\Translation\TranslatorInterface; 17 | 18 | final class StubTranslator implements TranslatorInterface 19 | { 20 | /** 21 | * @param mixed[] $parameters 22 | */ 23 | public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string 24 | { 25 | return '[trans]'.strtr($id, $parameters).'[/trans]'; 26 | } 27 | 28 | public function getLocale(): string 29 | { 30 | return 'en'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Test/AbstractWidgetTestCase.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Test; 15 | 16 | use Sonata\Form\Fixtures\StubTranslator; 17 | use Symfony\Bridge\Twig\Extension\FormExtension; 18 | use Symfony\Bridge\Twig\Extension\TranslationExtension; 19 | use Symfony\Bridge\Twig\Form\TwigRendererEngine; 20 | use Symfony\Component\Form\FormRenderer; 21 | use Symfony\Component\Form\FormView; 22 | use Symfony\Component\Form\Test\TypeTestCase; 23 | use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; 24 | use Twig\Environment; 25 | use Twig\Loader\FilesystemLoader; 26 | use Twig\RuntimeLoader\FactoryRuntimeLoader; 27 | 28 | /** 29 | * Base class for tests checking rendering of form widgets. 30 | * 31 | * @author Christian Gripp 32 | */ 33 | abstract class AbstractWidgetTestCase extends TypeTestCase 34 | { 35 | private FormRenderer $renderer; 36 | 37 | protected function setUp(): void 38 | { 39 | parent::setUp(); 40 | 41 | $environment = $this->getEnvironment(); 42 | 43 | $this->renderer = new FormRenderer( 44 | $this->getRenderingEngine($environment), 45 | $this->createMock(CsrfTokenManagerInterface::class) 46 | ); 47 | 48 | $environment->addRuntimeLoader(new FactoryRuntimeLoader([ 49 | FormRenderer::class => fn (): FormRenderer => $this->renderer, 50 | ])); 51 | $environment->addExtension(new FormExtension()); 52 | } 53 | 54 | final public function getRenderer(): FormRenderer 55 | { 56 | return $this->renderer; 57 | } 58 | 59 | protected function getEnvironment(): Environment 60 | { 61 | $loader = new FilesystemLoader($this->getTemplatePaths()); 62 | 63 | $environment = new Environment($loader, [ 64 | 'strict_variables' => true, 65 | ]); 66 | $environment->addExtension(new TranslationExtension(new StubTranslator())); 67 | 68 | return $environment; 69 | } 70 | 71 | /** 72 | * Returns a list of template paths. 73 | * 74 | * @return string[] 75 | */ 76 | protected function getTemplatePaths(): array 77 | { 78 | // this is an workaround for different composer requirements and different TwigBridge installation directories 79 | $twigPaths = array_filter([ 80 | // symfony/twig-bridge (running from this bundle) 81 | __DIR__.'/../../vendor/symfony/twig-bridge/Resources/views/Form', 82 | // symfony/twig-bridge (running from other bundles) 83 | __DIR__.'/../../../../symfony/twig-bridge/Resources/views/Form', 84 | // symfony/symfony (running from this bundle) 85 | __DIR__.'/../../vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form', 86 | // symfony/symfony (running from other bundles) 87 | __DIR__.'/../../../../symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form', 88 | ], 'is_dir'); 89 | 90 | $twigPaths[] = __DIR__.'/../Bridge/Symfony/Resources/views/Form'; 91 | 92 | return $twigPaths; 93 | } 94 | 95 | protected function getRenderingEngine(Environment $environment): TwigRendererEngine 96 | { 97 | return new TwigRendererEngine(['form_div_layout.html.twig'], $environment); 98 | } 99 | 100 | /** 101 | * Renders widget from FormView, in SonataAdmin context, with optional view variables $vars. Returns plain HTML. 102 | * 103 | * @param array $vars 104 | */ 105 | final protected function renderWidget(FormView $view, array $vars = []): string 106 | { 107 | return $this->renderer->searchAndRenderBlock($view, 'widget', $vars); 108 | } 109 | 110 | /** 111 | * Helper method to strip newline and space characters from html string to make comparing easier. 112 | */ 113 | final protected function cleanHtmlWhitespace(string $html): string 114 | { 115 | return preg_replace_callback( 116 | '/\s*>([^<]+) '>'.trim($value[1]).'<', 118 | $html 119 | ) ?? ''; 120 | } 121 | 122 | final protected function cleanHtmlAttributeWhitespace(string $html): string 123 | { 124 | return preg_replace_callback( 125 | '~<([A-Z0-9]+) \K(.*?)>~i', 126 | static fn (array $m): string => preg_replace('~\s*~', '', $m[0]) ?? '', 127 | $html 128 | ) ?? ''; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Type/BasePickerType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Sonata\Form\Date\JavaScriptFormatConverter; 17 | use Symfony\Component\Form\AbstractType; 18 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType; 19 | use Symfony\Component\Form\FormInterface; 20 | use Symfony\Component\Form\FormView; 21 | use Symfony\Component\OptionsResolver\Options; 22 | use Symfony\Component\OptionsResolver\OptionsResolver; 23 | use Symfony\Contracts\Translation\LocaleAwareInterface; 24 | 25 | /** 26 | * Class BasePickerType (to factorize DatePickerType and DateTimePickerType code. 27 | * 28 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 29 | * 30 | * @author Hugo Briand 31 | */ 32 | abstract class BasePickerType extends AbstractType implements LocaleAwareInterface 33 | { 34 | /** 35 | * @var array|string> 36 | */ 37 | private const DATEPICKER_ALLOWED_OPTIONS = [ 38 | 'allowInputToggle' => 'bool', 39 | 'dateRange' => 'bool', 40 | 'debug' => 'bool', 41 | 'defaultDate' => ['string', \DateTimeInterface::class], 42 | 'keepInvalid' => 'bool', 43 | 'multipleDates' => 'bool', 44 | 'multipleDatesSeparator' => 'string', 45 | 'promptTimeOnDateChange' => 'bool', 46 | 'promptTimeOnDateChangeTransitionDelay' => 'integer', 47 | 'stepping' => 'integer', 48 | 'useCurrent' => 'bool', 49 | 'viewDate' => ['string', \DateTimeInterface::class], 50 | ]; 51 | 52 | /** 53 | * @var array|string> 54 | */ 55 | private const RESTRICTIONS_OPTIONS = [ 56 | 'minDate' => ['string', \DateTimeInterface::class], 57 | 'maxDate' => ['string', \DateTimeInterface::class], 58 | 'disabledDates' => ['string[]', 'DateTimeInterface[]'], 59 | 'enabledDates' => ['string[]', 'DateTimeInterface[]'], 60 | 'daysOfWeekDisabled' => 'integer[]', 61 | 'disabledHours' => 'integer[]', 62 | 'enabledHours' => 'integer[]', 63 | ]; 64 | 65 | /** 66 | * @var array|string> 67 | */ 68 | private const LOCALIZATION_OPTIONS = [ 69 | 'locale' => 'string', 70 | 'hourCycle' => 'string', 71 | ]; 72 | 73 | /** 74 | * @var array|string> 75 | */ 76 | private const DISPLAY_OPTIONS = [ 77 | 'sideBySide' => 'bool', 78 | 'calendarWeeks' => 'bool', 79 | 'viewMode' => 'string', 80 | 'toolbarPlacement' => 'string', 81 | 'keepOpen' => 'bool', 82 | 'inline' => 'bool', 83 | 'theme' => 'string', 84 | ]; 85 | 86 | /** 87 | * @var array|string> 88 | */ 89 | private const DISPLAY_ICONS_OPTIONS = [ 90 | 'time' => 'string', 91 | 'date' => 'string', 92 | 'up' => 'string', 93 | 'down' => 'string', 94 | 'previous' => 'string', 95 | 'next' => 'string', 96 | 'today' => 'string', 97 | 'clear' => 'string', 98 | 'close' => 'string', 99 | ]; 100 | 101 | /** 102 | * @var array|string> 103 | */ 104 | private const DISPLAY_BUTTONS_OPTIONS = [ 105 | 'today' => 'bool', 106 | 'clear' => 'bool', 107 | 'close' => 'bool', 108 | ]; 109 | 110 | /** 111 | * @var array|string> 112 | */ 113 | private const DISPLAY_COMPONENTS_OPTIONS = [ 114 | 'calendar' => 'bool', 115 | 'date' => 'bool', 116 | 'month' => 'bool', 117 | 'year' => 'bool', 118 | 'decades' => 'bool', 119 | 'clock' => 'bool', 120 | 'hours' => 'bool', 121 | 'minutes' => 'bool', 122 | 'seconds' => 'bool', 123 | ]; 124 | 125 | public function __construct( 126 | private JavaScriptFormatConverter $formatConverter, 127 | private string $locale, 128 | ) { 129 | } 130 | 131 | public function getLocale(): string 132 | { 133 | return $this->locale; 134 | } 135 | 136 | public function setLocale(string $locale): void 137 | { 138 | $this->locale = $locale; 139 | } 140 | 141 | public function configureOptions(OptionsResolver $resolver): void 142 | { 143 | $resolver->setDefaults($this->getCommonDefaults()); 144 | 145 | $resolver->setDefault('datepicker_options', function (OptionsResolver $datePickerResolver) { 146 | $datePickerResolver->setDefined(array_keys(self::DATEPICKER_ALLOWED_OPTIONS)); 147 | 148 | foreach (self::DATEPICKER_ALLOWED_OPTIONS as $option => $allowedTypes) { 149 | $datePickerResolver->setAllowedTypes($option, $allowedTypes); 150 | } 151 | 152 | $datePickerResolver->setNormalizer('defaultDate', $this->dateTimeNormalizer()); 153 | $datePickerResolver->setNormalizer('viewDate', $this->dateTimeNormalizer()); 154 | 155 | $defaults = $this->getCommonDatepickerDefaults(); 156 | 157 | $datePickerResolver->setDefaults($defaults); 158 | $datePickerResolver->setDefault('localization', $this->defineLocalizationOptions($defaults['localization'] ?? [])); 159 | $datePickerResolver->setDefault('restrictions', $this->defineRestrictionsOptions($defaults['restrictions'] ?? [])); 160 | $datePickerResolver->setDefault('display', $this->defineDisplayOptions($defaults['display'] ?? [])); 161 | }); 162 | 163 | $resolver->setNormalizer( 164 | 'format', 165 | function (Options $options, int|string $format): string { 166 | if (\is_int($format)) { 167 | $timeFormat = \IntlDateFormatter::NONE; 168 | 169 | if (true === ($options['datepicker_options']['display']['components']['clock'] ?? true)) { 170 | $timeFormat = true === ($options['datepicker_options']['display']['components']['seconds'] ?? false) ? 171 | DateTimeType::DEFAULT_TIME_FORMAT : 172 | \IntlDateFormatter::SHORT; 173 | } 174 | 175 | return (new \IntlDateFormatter( 176 | $this->locale, 177 | $format, 178 | $timeFormat, 179 | null, 180 | \IntlDateFormatter::GREGORIAN 181 | ))->getPattern(); 182 | } 183 | 184 | return $format; 185 | } 186 | ); 187 | 188 | $resolver->setAllowedTypes('datepicker_use_button', 'bool'); 189 | } 190 | 191 | public function finishView(FormView $view, FormInterface $form, array $options): void 192 | { 193 | $datePickerOptions = $options['datepicker_options'] ?? []; 194 | 195 | if (isset($datePickerOptions['display']['icons']) 196 | && [] === $datePickerOptions['display']['icons']) { 197 | unset($datePickerOptions['display']['icons']); 198 | } 199 | 200 | if (isset($datePickerOptions['display']['buttons']) 201 | && [] === $datePickerOptions['display']['buttons']) { 202 | unset($datePickerOptions['display']['buttons']); 203 | } 204 | 205 | if (isset($datePickerOptions['display']['components']) 206 | && [] === $datePickerOptions['display']['components']) { 207 | unset($datePickerOptions['display']['components']); 208 | } 209 | 210 | if (isset($datePickerOptions['display']) 211 | && [] === $datePickerOptions['display']) { 212 | unset($datePickerOptions['display']); 213 | } 214 | 215 | if (isset($datePickerOptions['restrictions']) 216 | && [] === $datePickerOptions['restrictions']) { 217 | unset($datePickerOptions['restrictions']); 218 | } 219 | 220 | if (!isset($datePickerOptions['localization'])) { 221 | $datePickerOptions['localization'] = []; 222 | } 223 | 224 | $datePickerOptions['localization']['format'] = $this->formatConverter->convert($options['format'] ?? ''); 225 | 226 | $view->vars['datepicker_options'] = $datePickerOptions; 227 | $view->vars['datepicker_use_button'] = $options['datepicker_use_button'] ?? false; 228 | } 229 | 230 | /** 231 | * Gets base default options for the form types 232 | * (except `datepicker_options` which should be handled with `getCommonDatepickerDefaults()`). 233 | * 234 | * @return array 235 | */ 236 | protected function getCommonDefaults(): array 237 | { 238 | return [ 239 | 'widget' => 'single_text', 240 | 'datepicker_use_button' => true, 241 | 'html5' => false, 242 | ]; 243 | } 244 | 245 | /** 246 | * Gets base default options for the `datepicker_options` option. 247 | * 248 | * @return array 249 | */ 250 | protected function getCommonDatepickerDefaults(): array 251 | { 252 | return [ 253 | 'display' => [ 254 | 'theme' => 'light', 255 | ], 256 | 'localization' => [ 257 | 'locale' => str_replace('_', '-', $this->locale), 258 | ], 259 | ]; 260 | } 261 | 262 | /** 263 | * @param array $defaults 264 | */ 265 | private function defineLocalizationOptions(array $defaults): callable 266 | { 267 | return static function (OptionsResolver $resolver) use ($defaults): void { 268 | $resolver->setDefined(array_keys(self::LOCALIZATION_OPTIONS)); 269 | 270 | foreach (self::LOCALIZATION_OPTIONS as $option => $allowedTypes) { 271 | $resolver->setAllowedTypes($option, $allowedTypes); 272 | } 273 | 274 | $resolver->setDefaults($defaults); 275 | }; 276 | } 277 | 278 | /** 279 | * @param array $defaults 280 | */ 281 | private function defineRestrictionsOptions(array $defaults): callable 282 | { 283 | return function (OptionsResolver $resolver) use ($defaults): void { 284 | $resolver->setDefined(array_keys(self::RESTRICTIONS_OPTIONS)); 285 | 286 | foreach (self::RESTRICTIONS_OPTIONS as $option => $allowedTypes) { 287 | $resolver->setAllowedTypes($option, $allowedTypes); 288 | } 289 | 290 | $resolver->setAllowedValues( 291 | 'daysOfWeekDisabled', 292 | static fn (array $value) => array_filter( 293 | $value, 294 | static fn ($day) => \is_int($day) && $day >= 0 && $day <= 6 295 | ) === $value 296 | ); 297 | 298 | $resolver->setAllowedValues( 299 | 'enabledHours', 300 | static fn (array $value) => array_filter( 301 | $value, 302 | static fn ($hour) => \is_int($hour) && $hour >= 0 && $hour <= 23 303 | ) === $value 304 | ); 305 | 306 | $resolver->setAllowedValues( 307 | 'disabledHours', 308 | static fn (array $value) => array_filter( 309 | $value, 310 | static fn ($hour) => \is_int($hour) && $hour >= 0 && $hour <= 23 311 | ) === $value 312 | ); 313 | 314 | $resolver->setNormalizer('minDate', $this->dateTimeNormalizer()); 315 | $resolver->setNormalizer('maxDate', $this->dateTimeNormalizer()); 316 | $resolver->setNormalizer('disabledDates', $this->dateTimeNormalizer()); 317 | $resolver->setNormalizer('enabledDates', $this->dateTimeNormalizer()); 318 | 319 | $resolver->setDefaults($defaults); 320 | }; 321 | } 322 | 323 | /** 324 | * @param array $defaults 325 | */ 326 | private function defineDisplayOptions(array $defaults): callable 327 | { 328 | return function (OptionsResolver $resolver) use ($defaults): void { 329 | $resolver->setDefined(array_keys(self::DISPLAY_OPTIONS)); 330 | 331 | foreach (self::DISPLAY_OPTIONS as $option => $allowedTypes) { 332 | $resolver->setAllowedTypes($option, $allowedTypes); 333 | } 334 | 335 | $resolver->setAllowedValues('viewMode', ['clock', 'calendar', 'months', 'years', 'decades']); 336 | $resolver->setAllowedValues('toolbarPlacement', ['top', 'bottom']); 337 | $resolver->setAllowedValues('theme', ['light', 'dark', 'auto']); 338 | 339 | $resolver->setDefaults($defaults); 340 | $resolver->setDefault('icons', $this->defineDisplayIconsOptions($defaults['icons'] ?? [])); 341 | $resolver->setDefault('buttons', $this->defineDisplayButtonsOptions($defaults['buttons'] ?? [])); 342 | $resolver->setDefault('components', $this->defineDisplayComponentsOptions($defaults['components'] ?? [])); 343 | }; 344 | } 345 | 346 | /** 347 | * @param array $defaults 348 | */ 349 | private function defineDisplayIconsOptions(array $defaults): callable 350 | { 351 | return static function (OptionsResolver $resolver) use ($defaults): void { 352 | $resolver->setDefined(array_keys(self::DISPLAY_ICONS_OPTIONS)); 353 | 354 | foreach (self::DISPLAY_ICONS_OPTIONS as $option => $allowedTypes) { 355 | $resolver->setAllowedTypes($option, $allowedTypes); 356 | } 357 | 358 | $resolver->setDefaults($defaults); 359 | }; 360 | } 361 | 362 | /** 363 | * @param array $defaults 364 | */ 365 | private function defineDisplayButtonsOptions(array $defaults): callable 366 | { 367 | return static function (OptionsResolver $resolver) use ($defaults): void { 368 | $resolver->setDefined(array_keys(self::DISPLAY_BUTTONS_OPTIONS)); 369 | 370 | foreach (self::DISPLAY_BUTTONS_OPTIONS as $option => $allowedTypes) { 371 | $resolver->setAllowedTypes($option, $allowedTypes); 372 | } 373 | 374 | $resolver->setDefaults($defaults); 375 | }; 376 | } 377 | 378 | /** 379 | * @param array $defaults 380 | */ 381 | private function defineDisplayComponentsOptions(array $defaults): callable 382 | { 383 | return static function (OptionsResolver $resolver) use ($defaults): void { 384 | $resolver->setDefined(array_keys(self::DISPLAY_COMPONENTS_OPTIONS)); 385 | 386 | foreach (self::DISPLAY_COMPONENTS_OPTIONS as $option => $allowedTypes) { 387 | $resolver->setAllowedTypes($option, $allowedTypes); 388 | } 389 | 390 | $resolver->setDefaults($defaults); 391 | }; 392 | } 393 | 394 | private function dateTimeNormalizer(): \Closure 395 | { 396 | return static function (OptionsResolver $options, string|array|\DateTimeInterface $value): string|array { 397 | if ($value instanceof \DateTimeInterface) { 398 | return $value->format(\DateTimeInterface::ATOM); 399 | } 400 | 401 | if (\is_array($value)) { 402 | foreach ($value as $key => $singleValue) { 403 | if ($singleValue instanceof \DateTimeInterface) { 404 | $value[$key] = $singleValue->format(\DateTimeInterface::ATOM); 405 | } 406 | } 407 | } 408 | 409 | return $value; 410 | }; 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /src/Type/BaseStatusType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 22 | */ 23 | abstract class BaseStatusType extends AbstractType 24 | { 25 | /** 26 | * @phpstan-param class-string $class 27 | */ 28 | public function __construct( 29 | protected string $class, 30 | protected string $getter, 31 | protected string $name, 32 | ) { 33 | } 34 | 35 | public function getParent(): string 36 | { 37 | return ChoiceType::class; 38 | } 39 | 40 | public function getBlockPrefix(): string 41 | { 42 | return $this->name; 43 | } 44 | 45 | public function configureOptions(OptionsResolver $resolver): void 46 | { 47 | $callable = [$this->class, $this->getter]; 48 | if (!\is_callable($callable)) { 49 | throw new \RuntimeException(\sprintf( 50 | 'The class "%s" has no method "%s()".', 51 | $this->class, 52 | $this->getter 53 | )); 54 | } 55 | 56 | $resolver->setDefaults([ 57 | 'choices' => \call_user_func($callable), 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Type/BooleanType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Sonata\Form\DataTransformer\BooleanTypeToBooleanTransformer; 17 | use Symfony\Component\Form\AbstractType; 18 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 19 | use Symfony\Component\Form\FormBuilderInterface; 20 | use Symfony\Component\OptionsResolver\OptionsResolver; 21 | 22 | /** 23 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 24 | */ 25 | final class BooleanType extends AbstractType 26 | { 27 | public const TYPE_YES = 1; 28 | 29 | public const TYPE_NO = 2; 30 | 31 | public function buildForm(FormBuilderInterface $builder, array $options): void 32 | { 33 | if (true === $options['transform']) { 34 | $builder->addModelTransformer(new BooleanTypeToBooleanTransformer()); 35 | } 36 | } 37 | 38 | public function configureOptions(OptionsResolver $resolver): void 39 | { 40 | $resolver->setDefaults([ 41 | 'transform' => false, 42 | 'choice_translation_domain' => 'SonataFormBundle', 43 | 'choices' => [ 44 | 'label_type_yes' => self::TYPE_YES, 45 | 'label_type_no' => self::TYPE_NO, 46 | ], 47 | 'translation_domain' => 'SonataFormBundle', 48 | ]); 49 | 50 | $resolver->setAllowedTypes('transform', 'bool'); 51 | } 52 | 53 | public function getParent(): string 54 | { 55 | return ChoiceType::class; 56 | } 57 | 58 | public function getBlockPrefix(): string 59 | { 60 | return 'sonata_type_boolean'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Type/CollectionType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Sonata\Form\EventListener\ResizeFormListener; 17 | use Symfony\Component\Form\AbstractType; 18 | use Symfony\Component\Form\Extension\Core\Type\TextType; 19 | use Symfony\Component\Form\FormBuilderInterface; 20 | use Symfony\Component\Form\FormInterface; 21 | use Symfony\Component\Form\FormView; 22 | use Symfony\Component\OptionsResolver\Options; 23 | use Symfony\Component\OptionsResolver\OptionsResolver; 24 | 25 | /** 26 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 27 | */ 28 | final class CollectionType extends AbstractType 29 | { 30 | public function buildForm(FormBuilderInterface $builder, array $options): void 31 | { 32 | $builder->addEventSubscriber(new ResizeFormListener( 33 | $options['type'], 34 | $options['type_options'], 35 | $options['modifiable'], 36 | $options['pre_bind_data_callback'] 37 | )); 38 | } 39 | 40 | public function buildView(FormView $view, FormInterface $form, array $options): void 41 | { 42 | $view->vars['btn_add'] = $options['btn_add']; 43 | 44 | // NEXT_MAJOR: Remove the btn_catalogue usage. 45 | $view->vars['btn_translation_domain'] = 46 | 'SonataFormBundle' !== $options['btn_translation_domain'] 47 | ? $options['btn_translation_domain'] 48 | : $options['btn_catalogue']; 49 | $view->vars['btn_catalogue'] = $options['btn_catalogue']; 50 | } 51 | 52 | public function configureOptions(OptionsResolver $resolver): void 53 | { 54 | $resolver->setDefaults([ 55 | 'modifiable' => false, 56 | 'type' => TextType::class, 57 | 'type_options' => [], 58 | 'pre_bind_data_callback' => null, 59 | 'btn_add' => 'link_add', 60 | 'btn_catalogue' => 'SonataFormBundle', // NEXT_MAJOR: Remove this option. 61 | 'btn_translation_domain' => 'SonataFormBundle', 62 | ]); 63 | 64 | $resolver->setDeprecated( 65 | 'btn_catalogue', 66 | 'sonata-project/form-extensions', 67 | '2.1', 68 | static function (Options $options, mixed $value): string { 69 | if ('SonataFormBundle' !== $value) { 70 | return 'Passing a value to option "btn_catalogue" is deprecated! Use "btn_translation_domain" instead!'; 71 | } 72 | 73 | return ''; 74 | }, 75 | ); // NEXT_MAJOR: Remove this deprecation notice. 76 | 77 | $resolver->setAllowedTypes('modifiable', 'bool'); 78 | $resolver->setAllowedTypes('type', 'string'); 79 | $resolver->setAllowedTypes('type_options', 'array'); 80 | $resolver->setAllowedTypes('pre_bind_data_callback', ['null', 'callable']); 81 | $resolver->setAllowedTypes('btn_add', ['null', 'bool', 'string']); 82 | $resolver->setAllowedTypes('btn_catalogue', ['null', 'bool', 'string']); 83 | $resolver->setAllowedTypes('btn_translation_domain', ['null', 'bool', 'string']); 84 | } 85 | 86 | public function getBlockPrefix(): string 87 | { 88 | return 'sonata_type_collection'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Type/DatePickerType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\Extension\Core\Type\DateType; 17 | 18 | /** 19 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 20 | * 21 | * @author Hugo Briand 22 | */ 23 | final class DatePickerType extends BasePickerType 24 | { 25 | public function getParent(): string 26 | { 27 | return DateType::class; 28 | } 29 | 30 | public function getBlockPrefix(): string 31 | { 32 | return 'sonata_type_datetime_picker'; 33 | } 34 | 35 | protected function getCommonDefaults(): array 36 | { 37 | return array_merge(parent::getCommonDefaults(), [ 38 | 'format' => DateType::DEFAULT_FORMAT, 39 | ]); 40 | } 41 | 42 | protected function getCommonDatepickerDefaults(): array 43 | { 44 | return array_merge_recursive(parent::getCommonDatepickerDefaults(), [ 45 | 'display' => [ 46 | 'components' => [ 47 | 'clock' => false, 48 | ], 49 | ], 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Type/DateRangePickerType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 20 | * 21 | * @author Andrej Hudec 22 | */ 23 | final class DateRangePickerType extends DateRangeType 24 | { 25 | public function configureOptions(OptionsResolver $resolver): void 26 | { 27 | parent::configureOptions($resolver); 28 | 29 | $resolver->setDefault('field_type', DatePickerType::class); 30 | $resolver->setDefault('field_options_end', [ 31 | 'datepicker_options' => [ 32 | 'useCurrent' => false, 33 | ], 34 | ]); 35 | } 36 | 37 | public function getBlockPrefix(): string 38 | { 39 | return 'sonata_type_datetime_range_picker'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Type/DateRangeType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\Extension\Core\Type\DateType; 18 | use Symfony\Component\Form\FormBuilderInterface; 19 | use Symfony\Component\Form\FormInterface; 20 | use Symfony\Component\Form\FormView; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | /** 24 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 25 | */ 26 | class DateRangeType extends AbstractType 27 | { 28 | public function buildForm(FormBuilderInterface $builder, array $options): void 29 | { 30 | $options['field_options_start'] = array_merge( 31 | [ 32 | 'label' => 'date_range_start', 33 | 'translation_domain' => 'SonataFormBundle', 34 | ], 35 | $options['field_options_start'] 36 | ); 37 | 38 | $options['field_options_end'] = array_merge( 39 | [ 40 | 'label' => 'date_range_end', 41 | 'translation_domain' => 'SonataFormBundle', 42 | ], 43 | $options['field_options_end'] 44 | ); 45 | 46 | $builder->add( 47 | 'start', 48 | $options['field_type'], 49 | array_merge(['required' => false], $options['field_options'], $options['field_options_start']) 50 | ); 51 | $builder->add( 52 | 'end', 53 | $options['field_type'], 54 | array_merge(['required' => false], $options['field_options'], $options['field_options_end']) 55 | ); 56 | } 57 | 58 | public function finishView(FormView $view, FormInterface $form, array $options): void 59 | { 60 | $view->children['start']->vars['linked_to'] = $view->children['end']->vars['id']; 61 | } 62 | 63 | public function getBlockPrefix(): string 64 | { 65 | return 'sonata_type_date_range'; 66 | } 67 | 68 | public function configureOptions(OptionsResolver $resolver): void 69 | { 70 | $resolver->setDefaults([ 71 | 'field_options' => [], 72 | 'field_options_start' => [], 73 | 'field_options_end' => [], 74 | 'field_type' => DateType::class, 75 | ]); 76 | 77 | $resolver->setAllowedTypes('field_options', 'array'); 78 | $resolver->setAllowedTypes('field_options_start', 'array'); 79 | $resolver->setAllowedTypes('field_options_end', 'array'); 80 | $resolver->setAllowedTypes('field_type', 'string'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Type/DateTimePickerType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType; 17 | 18 | /** 19 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 20 | * 21 | * @author Hugo Briand 22 | */ 23 | final class DateTimePickerType extends BasePickerType 24 | { 25 | public function getParent(): string 26 | { 27 | return DateTimeType::class; 28 | } 29 | 30 | public function getBlockPrefix(): string 31 | { 32 | return 'sonata_type_datetime_picker'; 33 | } 34 | 35 | protected function getCommonDefaults(): array 36 | { 37 | return array_merge(parent::getCommonDefaults(), [ 38 | 'format' => DateTimeType::DEFAULT_DATE_FORMAT, 39 | ]); 40 | } 41 | 42 | protected function getCommonDatepickerDefaults(): array 43 | { 44 | return array_merge_recursive(parent::getCommonDatepickerDefaults(), [ 45 | 'display' => [ 46 | 'components' => [ 47 | 'seconds' => true, 48 | ], 49 | ], 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Type/DateTimeRangePickerType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\OptionsResolver\OptionsResolver; 17 | 18 | /** 19 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 20 | * 21 | * @author Andrej Hudec 22 | * 23 | * NEXT_MAJOR: Declare the class as final. 24 | * 25 | * @final since 2.5.0. 26 | */ 27 | class DateTimeRangePickerType extends DateTimeRangeType 28 | { 29 | public function configureOptions(OptionsResolver $resolver): void 30 | { 31 | parent::configureOptions($resolver); 32 | 33 | $resolver->setDefault('field_type', DateTimePickerType::class); 34 | $resolver->setDefault('field_options_end', [ 35 | 'datepicker_options' => [ 36 | 'useCurrent' => false, 37 | ], 38 | ]); 39 | } 40 | 41 | public function getBlockPrefix(): string 42 | { 43 | return 'sonata_type_datetime_range_picker'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Type/DateTimeRangeType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType; 18 | use Symfony\Component\Form\FormBuilderInterface; 19 | use Symfony\Component\Form\FormInterface; 20 | use Symfony\Component\Form\FormView; 21 | use Symfony\Component\OptionsResolver\OptionsResolver; 22 | 23 | /** 24 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 25 | */ 26 | class DateTimeRangeType extends AbstractType 27 | { 28 | public function buildForm(FormBuilderInterface $builder, array $options): void 29 | { 30 | $options['field_options_start'] = array_merge( 31 | [ 32 | 'label' => 'date_range_start', 33 | 'translation_domain' => 'SonataFormBundle', 34 | ], 35 | $options['field_options_start'] 36 | ); 37 | 38 | $options['field_options_end'] = array_merge( 39 | [ 40 | 'label' => 'date_range_end', 41 | 'translation_domain' => 'SonataFormBundle', 42 | ], 43 | $options['field_options_end'] 44 | ); 45 | 46 | $builder->add( 47 | 'start', 48 | $options['field_type'], 49 | array_merge(['required' => false], $options['field_options'], $options['field_options_start']) 50 | ); 51 | $builder->add( 52 | 'end', 53 | $options['field_type'], 54 | array_merge(['required' => false], $options['field_options'], $options['field_options_end']) 55 | ); 56 | } 57 | 58 | public function finishView(FormView $view, FormInterface $form, array $options): void 59 | { 60 | $view->children['start']->vars['linked_to'] = $view->children['end']->vars['id']; 61 | } 62 | 63 | public function getBlockPrefix(): string 64 | { 65 | return 'sonata_type_datetime_range'; 66 | } 67 | 68 | public function configureOptions(OptionsResolver $resolver): void 69 | { 70 | $resolver->setDefaults([ 71 | 'field_options' => [], 72 | 'field_options_start' => [], 73 | 'field_options_end' => [], 74 | 'field_type' => DateTimeType::class, 75 | ]); 76 | 77 | $resolver->setAllowedTypes('field_options', 'array'); 78 | $resolver->setAllowedTypes('field_options_start', 'array'); 79 | $resolver->setAllowedTypes('field_options_end', 'array'); 80 | $resolver->setAllowedTypes('field_type', 'string'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Type/ImmutableArrayType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Type; 15 | 16 | use Symfony\Component\Form\AbstractType; 17 | use Symfony\Component\Form\FormBuilderInterface; 18 | use Symfony\Component\OptionsResolver\OptionsResolver; 19 | 20 | /** 21 | * @psalm-suppress MissingTemplateParam https://github.com/phpstan/phpstan-symfony/issues/320 22 | */ 23 | final class ImmutableArrayType extends AbstractType 24 | { 25 | public function buildForm(FormBuilderInterface $builder, array $options): void 26 | { 27 | foreach ($options['keys'] as $infos) { 28 | if ($infos instanceof FormBuilderInterface) { 29 | $builder->add($infos); 30 | } else { 31 | [$name, $type, $options] = $infos; 32 | 33 | if (\is_callable($options)) { 34 | $extra = \array_slice($infos, 3); 35 | 36 | $options = $options($builder, $name, $type, $extra); 37 | 38 | if (null === $options) { 39 | $options = []; 40 | } elseif (!\is_array($options)) { 41 | throw new \RuntimeException('the closure must return null or an array'); 42 | } 43 | } 44 | 45 | $builder->add($name, $type, $options); 46 | } 47 | } 48 | } 49 | 50 | public function configureOptions(OptionsResolver $resolver): void 51 | { 52 | $resolver->setDefaults([ 53 | 'keys' => [], 54 | ]); 55 | 56 | $resolver->setAllowedValues('keys', static function (iterable $value): bool { 57 | foreach ($value as $subValue) { 58 | if (!$subValue instanceof FormBuilderInterface && (!\is_array($subValue) || 3 !== \count($subValue))) { 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | }); 65 | } 66 | 67 | public function getBlockPrefix(): string 68 | { 69 | return 'sonata_type_immutable_array'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Validator/Constraints/InlineConstraint.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Validator\Constraints; 15 | 16 | use Symfony\Component\Validator\Constraint; 17 | 18 | /** 19 | * Constraint which allows inline-validation inside services. 20 | * 21 | * @Annotation 22 | * 23 | * @Target({"CLASS"}) 24 | * 25 | * @psalm-suppress PropertyNotSetInConstructor 26 | */ 27 | final class InlineConstraint extends Constraint 28 | { 29 | protected mixed $service = null; 30 | 31 | protected mixed $method = null; 32 | 33 | protected bool $serializingWarning = false; 34 | 35 | public function __construct(mixed $options = null) 36 | { 37 | parent::__construct($options); 38 | 39 | if ((!\is_string($this->service) || !\is_string($this->method)) && true !== $this->serializingWarning) { 40 | throw new \RuntimeException('You are using a closure with the `InlineConstraint`, this constraint'. 41 | ' cannot be serialized. You need to re-attach the `InlineConstraint` on each request.'. 42 | ' Once done, you can set the `serializingWarning` option to `true` to avoid this message.'); 43 | } 44 | } 45 | 46 | public function __sleep(): array 47 | { 48 | // @phpstan-ignore-next-line to initialize "groups" option if it is not set 49 | $this->groups; 50 | 51 | if (!\is_string($this->service) || !\is_string($this->method)) { 52 | return []; 53 | } 54 | 55 | return array_keys(get_object_vars($this)); 56 | } 57 | 58 | public function __wakeup(): void 59 | { 60 | if (\is_string($this->service) && \is_string($this->method)) { 61 | return; 62 | } 63 | 64 | $this->method = static function (): void { 65 | }; 66 | 67 | $this->serializingWarning = true; 68 | } 69 | 70 | public function validatedBy(): string 71 | { 72 | return 'sonata.form.validator.inline'; 73 | } 74 | 75 | public function isClosure(): bool 76 | { 77 | return $this->method instanceof \Closure; 78 | } 79 | 80 | public function getClosure(): mixed 81 | { 82 | return $this->method; 83 | } 84 | 85 | public function getTargets(): string 86 | { 87 | return self::CLASS_CONSTRAINT; 88 | } 89 | 90 | public function getRequiredOptions(): array 91 | { 92 | return [ 93 | 'service', 94 | 'method', 95 | ]; 96 | } 97 | 98 | public function getMethod(): mixed 99 | { 100 | return $this->method; 101 | } 102 | 103 | public function getService(): mixed 104 | { 105 | return $this->service; 106 | } 107 | 108 | public function getSerializingWarning(): bool 109 | { 110 | return $this->serializingWarning; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Validator/ErrorElement.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Validator; 15 | 16 | use Symfony\Component\PropertyAccess\PropertyAccess; 17 | use Symfony\Component\PropertyAccess\PropertyPath; 18 | use Symfony\Component\PropertyAccess\PropertyPathInterface; 19 | use Symfony\Component\Validator\Constraint; 20 | use Symfony\Component\Validator\Context\ExecutionContextInterface; 21 | 22 | /** 23 | * @method self assertBic(mixed[] $options = []) 24 | * @method self assertBlank(mixed[] $options = []) 25 | * @method self assertCallback(mixed[] $options = []) 26 | * @method self assertCardScheme(mixed[] $options = []) 27 | * @method self assertChoice(mixed[] $options = []) 28 | * @method self assertCollection(mixed[] $options = []) 29 | * @method self assertCount(mixed[] $options = []) 30 | * @method self assertCountry(mixed[] $options = []) 31 | * @method self assertCurrency(mixed[] $options = []) 32 | * @method self assertDate(mixed[] $options = []) 33 | * @method self assertDateTime(mixed[] $options = []) 34 | * @method self assertDisableAutoMapping(mixed[] $options = []) 35 | * @method self assertDivisibleBy(mixed[] $options = []) 36 | * @method self assertEmail(mixed[] $options = []) 37 | * @method self assertEnableAutoMapping(mixed[] $options = []) 38 | * @method self assertEqualTo(mixed[] $options = []) 39 | * @method self assertExpression(mixed[] $options = []) 40 | * @method self assertFile(mixed[] $options = []) 41 | * @method self assertGreaterThan(mixed[] $options = []) 42 | * @method self assertGreaterThanOrEqual(mixed[] $options = []) 43 | * @method self assertIban(mixed[] $options = []) 44 | * @method self assertIdenticalTo(mixed[] $options = []) 45 | * @method self assertImage(mixed[] $options = []) 46 | * @method self assertIp(mixed[] $options = []) 47 | * @method self assertIsbn(mixed[] $options = []) 48 | * @method self assertIsFalse(mixed[] $options = []) 49 | * @method self assertIsNull(mixed[] $options = []) 50 | * @method self assertIssn(mixed[] $options = []) 51 | * @method self assertIsTrue(mixed[] $options = []) 52 | * @method self assertJson(mixed[] $options = []) 53 | * @method self assertLanguage(mixed[] $options = []) 54 | * @method self assertLength(mixed[] $options = []) 55 | * @method self assertLessThan(mixed[] $options = []) 56 | * @method self assertLessThanOrEqual(mixed[] $options = []) 57 | * @method self assertLocale(mixed[] $options = []) 58 | * @method self assertLuhn(mixed[] $options = []) 59 | * @method self assertNegative(mixed[] $options = []) 60 | * @method self assertNegativeOrZero(mixed[] $options = []) 61 | * @method self assertNotBlank(mixed[] $options = []) 62 | * @method self assertNotCompromisedPassword(mixed[] $options = []) 63 | * @method self assertNotEqualTo(mixed[] $options = []) 64 | * @method self assertNotIdentificalTo(mixed[] $options = []) 65 | * @method self assertNotNull(mixed[] $options = []) 66 | * @method self assertPositive(mixed[] $options = []) 67 | * @method self assertPositiveOrZero(mixed[] $options = []) 68 | * @method self assertRange(mixed[] $options = []) 69 | * @method self assertRegex(mixed[] $options = []) 70 | * @method self assertTime(mixed[] $options = []) 71 | * @method self assertTimezone(mixed[] $options = []) 72 | * @method self assertTraverse(mixed[] $options = []) 73 | * @method self assertType(mixed[] $options = []) 74 | * @method self assertUnique(mixed[] $options = []) 75 | * @method self assertUrl(mixed[] $options = []) 76 | * @method self assertUuid(mixed[] $options = []) 77 | * @method self assertValid(mixed[] $options = []) 78 | */ 79 | final class ErrorElement 80 | { 81 | private const DEFAULT_TRANSLATION_DOMAIN = 'validators'; 82 | 83 | /** 84 | * @var string[] 85 | */ 86 | private array $stack = []; 87 | 88 | /** 89 | * @var PropertyPathInterface[] 90 | */ 91 | private array $propertyPaths = []; 92 | 93 | private string $current = ''; 94 | 95 | private string $basePropertyPath; 96 | 97 | /** 98 | * @var array, mixed}> 99 | */ 100 | private array $errors = []; 101 | 102 | public function __construct( 103 | private mixed $subject, 104 | private ExecutionContextInterface $context, 105 | private ?string $group, 106 | ) { 107 | $this->basePropertyPath = $this->context->getPropertyPath(); 108 | } 109 | 110 | /** 111 | * @param mixed[] $arguments 112 | * 113 | * @throws \RuntimeException 114 | */ 115 | public function __call(string $name, array $arguments = []): self 116 | { 117 | if (str_starts_with($name, 'assert')) { 118 | $this->validate($this->newConstraint(substr($name, 6), $arguments[0] ?? [])); 119 | } else { 120 | throw new \RuntimeException('Unable to recognize the command'); 121 | } 122 | 123 | return $this; 124 | } 125 | 126 | public function addConstraint(Constraint $constraint): self 127 | { 128 | $this->validate($constraint); 129 | 130 | return $this; 131 | } 132 | 133 | public function with(string $name, bool $key = false): self 134 | { 135 | /* 136 | * Existing code was 137 | * $key = $key ? $name.'.'.$key : $name; 138 | * 139 | * There is certainly a bug here or we should deprecate the key param. 140 | */ 141 | $this->stack[] = $key ? $name.'.1' : $name; 142 | 143 | $this->current = implode('.', $this->stack); 144 | 145 | if (!isset($this->propertyPaths[$this->current])) { 146 | $this->propertyPaths[$this->current] = new PropertyPath($this->current); 147 | } 148 | 149 | return $this; 150 | } 151 | 152 | public function end(): self 153 | { 154 | array_pop($this->stack); 155 | 156 | $this->current = implode('.', $this->stack); 157 | 158 | return $this; 159 | } 160 | 161 | public function getFullPropertyPath(): string 162 | { 163 | $propertyPath = $this->getCurrentPropertyPath(); 164 | if (null !== $propertyPath) { 165 | return \sprintf('%s.%s', $this->basePropertyPath, (string) $propertyPath); 166 | } 167 | 168 | return $this->basePropertyPath; 169 | } 170 | 171 | public function getSubject(): mixed 172 | { 173 | return $this->subject; 174 | } 175 | 176 | /** 177 | * @param array $parameters 178 | * 179 | * @return $this 180 | */ 181 | public function addViolation(string $message, array $parameters = [], mixed $value = null, string $translationDomain = self::DEFAULT_TRANSLATION_DOMAIN): self 182 | { 183 | $subPath = (string) $this->getCurrentPropertyPath(); 184 | 185 | $this->context->buildViolation($message) 186 | ->atPath($subPath) 187 | ->setParameters($parameters) 188 | ->setTranslationDomain($translationDomain) 189 | ->setInvalidValue($value) 190 | ->addViolation(); 191 | 192 | $this->errors[] = [$message, $parameters, $value]; 193 | 194 | return $this; 195 | } 196 | 197 | /** 198 | * @return array, mixed}> 199 | */ 200 | public function getErrors(): array 201 | { 202 | return $this->errors; 203 | } 204 | 205 | private function validate(Constraint $constraint): void 206 | { 207 | $this->context->getValidator() 208 | ->inContext($this->context) 209 | ->atPath((string) $this->getCurrentPropertyPath()) 210 | ->validate($this->getValue(), $constraint, $this->group); 211 | } 212 | 213 | /** 214 | * Return the value linked to. 215 | */ 216 | private function getValue(): mixed 217 | { 218 | if ('' === $this->current) { 219 | return $this->subject; 220 | } 221 | 222 | $propertyPath = $this->getCurrentPropertyPath(); 223 | \assert(null !== $propertyPath); 224 | 225 | $propertyAccessor = PropertyAccess::createPropertyAccessor(); 226 | 227 | return $propertyAccessor->getValue($this->subject, $propertyPath); 228 | } 229 | 230 | /** 231 | * @param array $options 232 | * 233 | * @throws \RuntimeException 234 | * 235 | * @psalm-suppress UnsafeInstantiation -- it is supposed that Constraint constructor is not going to change 236 | */ 237 | private function newConstraint(string $name, array $options = []): Constraint 238 | { 239 | if (str_contains($name, '\\') && class_exists($name)) { 240 | $className = $name; 241 | } else { 242 | $className = 'Symfony\\Component\\Validator\\Constraints\\'.$name; 243 | if (!class_exists($className)) { 244 | throw new \RuntimeException(\sprintf( 245 | 'Cannot find the class "%s".', 246 | $className 247 | )); 248 | } 249 | } 250 | 251 | if (!is_a($className, Constraint::class, true)) { 252 | throw new \RuntimeException(\sprintf( 253 | 'The class "%s" MUST implement "%s".', 254 | $className, 255 | Constraint::class 256 | )); 257 | } 258 | 259 | return new $className($options); 260 | } 261 | 262 | private function getCurrentPropertyPath(): ?PropertyPathInterface 263 | { 264 | if (!isset($this->propertyPaths[$this->current])) { 265 | return null; // global error 266 | } 267 | 268 | return $this->propertyPaths[$this->current]; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/Validator/InlineValidator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\Form\Validator; 15 | 16 | use Sonata\Form\Validator\Constraints\InlineConstraint; 17 | use Symfony\Component\DependencyInjection\ContainerInterface; 18 | use Symfony\Component\Validator\Constraint; 19 | use Symfony\Component\Validator\ConstraintValidator; 20 | use Symfony\Component\Validator\Exception\UnexpectedTypeException; 21 | 22 | final class InlineValidator extends ConstraintValidator 23 | { 24 | /** 25 | * @psalm-suppress ContainerDependency 26 | */ 27 | public function __construct(private ContainerInterface $container) 28 | { 29 | } 30 | 31 | /** 32 | * @param mixed $value 33 | */ 34 | public function validate($value, Constraint $constraint): void 35 | { 36 | if (!$constraint instanceof InlineConstraint) { 37 | throw new UnexpectedTypeException($constraint, InlineConstraint::class); 38 | } 39 | 40 | if ($constraint->isClosure()) { 41 | $function = $constraint->getClosure(); 42 | } else { 43 | if (\is_string($constraint->getService())) { 44 | $service = $this->container->get($constraint->getService()); 45 | } else { 46 | $service = $constraint->getService(); 47 | } 48 | 49 | $function = [$service, $constraint->getMethod()]; 50 | } 51 | 52 | \call_user_func($function, $this->getErrorElement($value), $value); 53 | } 54 | 55 | protected function getErrorElement(mixed $value): ErrorElement 56 | { 57 | return new ErrorElement( 58 | $value, 59 | $this->context, 60 | $this->context->getGroup() 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the Sonata Project package. 3 | * 4 | * (c) Thomas Rabaix 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | // eslint-disable-next-line import/no-unresolved 11 | import { defineConfig } from 'vitest/config'; 12 | // eslint-disable-next-line import/no-extraneous-dependencies 13 | import GithubActionsReporter from 'vitest-github-actions-reporter'; 14 | 15 | export default defineConfig({ 16 | test: { 17 | setupFiles: ['./assets/js/setup.test.js'], 18 | include: ['assets/js/**/*.test.js', '!assets/js/setup.test.js'], 19 | coverage: { 20 | all: true, 21 | include: ['assets/js/**/*.js'], 22 | reporter: ['text', 'json', 'html', 'lcovonly'], 23 | exclude: ['assets/js/**/*.test.js'], 24 | }, 25 | reporters: process.env.GITHUB_ACTIONS ? ['default', new GithubActionsReporter()] : 'default', 26 | environment: 'jsdom', 27 | }, 28 | }); 29 | --------------------------------------------------------------------------------